The Web Forms Programming Model

Calc.aspx demonstrates three important principles of the Web Forms programming model:

You probably noticed the RunAt=“server” attributes sprinkled throughout Calc.aspx. RunAt=“server” is the key that unlocks the door to the magic of Web forms; it signals ASP.NET to “execute” the tag rather than treat it as static HTML. RunAt=“server” is not optional. It must be used in every tag that ASP.NET is to process, including the <form> tag that marks the beginning of a form containing server controls.

Web Controls

TextBox, Button, and Label are server controls. They’re also examples of Web controls—server controls defined in the FCL’s System.Web.UI.WebControls namespace. The Web controls family includes almost 30 different control types that you can use in ASP.NET Web forms. The following table lists the Web controls provided in version 1 of the .NET Framework class library:

Web Controls?

Class Name

Description

AdRotator

Displays rotating banners in Web forms

Button

Generates submit buttons

Calendar

Displays calendars with selectable dates

CheckBox

Displays a check box in a Web form

CheckBoxList

Displays a group of check boxes

CompareValidator

Validates user input by comparing it to another value

CustomValidator

Validates user input using the algorithm of your choice

DataGrid

Displays data in tabular format

DataList

Displays items in single-column or multicolumn lists using HTML templates

DropDownList

Generates HTML drop-down lists

HyperLink

Generates hyperlinks

Image

Displays images in Web forms

ImageButton

Displays graphical push buttons

Label

Generates programmable text fields

LinkButton

Generates hyperlinks that post back to the server

ListBox

Generates HTML list boxes

Literal

Generates literal text in a Web form

Panel

Groups other controls

RadioButton

Displays a radio button in a Web form

RadioButtonList

Displays a group of check boxes

RangeValidator

Verifies that user input falls within a specified range

RegularExpressionValidator

Validates user input using regular expressions

Repeater

Displays items using HTML templates

RequiredFieldValidator

Verifies that an input field isn’t empty

Table

Generates HTML tables

TextBox

Generates text input fields

ValidationSummary

Displays a summary of validation errors

Xml

Displays XML documents and optionally formats them using XSLT

Some Web controls are simple devices that produce equally simple HTML. Others produce more complex HTML, and some even return client-side script. Calendar controls, for example, emit a rich mixture of HTML and JavaScript. It’s not easy to add a calendar to a Web page by hand (especially if you want dates in the calendar to be clickable), but calendars are no big deal in Web forms: you simply include an <asp:Calendar> tag in an ASPX file. DataGrid is another example of a sophisticated control type. One DataGrid control can replace reams of old ASP code that queries a database and returns the results in a richly formatted HTML table. You’ll learn all about the DataGrid and other Web controls in the next chapter.

HTML Controls

Most Web forms are built from Web controls, but ASP.NET supports a second type of server control called HTML controls. HTML controls are instances of classes defined in the FCL’s System.Web.UI.HtmlControls namespace. They’re declared by adding RunAt=“server” (or, if you’d prefer, runat=“server”; capitalization doesn’t matter in HTML) attributes to ordinary HTML tags. For example, the statement

<input?type="text" />

declares a standard HTML text input field. However, the statement

<input?type="text" runat="server" />

declares an HTML control—specifically, an instance of System.Web.UI.HtmlControls.HtmlInputText. At run time, ASP.NET sees the runat=“server” attribute and creates an HtmlInputText object. The HtmlInputText object, in turn, emits an <input type=“text”> tag that’s ultimately returned to the browser.

Without realizing it, you used an HTML control in Calc.aspx. The line

<form?runat="server">

caused an instance of System.Web.UI.HtmlControls.HtmlForm to be created on the server. HtmlForm returned the <form> tag that you saw when you viewed the page’s HTML source code with the View/Source command:

<form?name="_ctl0" method="post" action="calc.aspx" id="_ctl0">

HtmlInputText and HtmlForm are but two of many controls defined in the System.Web.UI.HtmlControls namespace. The following table lists all the HTML controls that the FCL supports and the tags that produce them.

HTML Controls

Tag

Corresponding HTML Control

<a runat=“server”>

HtmlAnchor

<button runat=“server”>

HtmlButton

<form runat=“server”>

HtmlForm

<img runat=“server”>

HtmlImage

<input type=“button” runat=“server”>

HtmlInputButton

<input type=“reset” runat=“server”>

HtmlInputButton

<input type=“submit” runat=“server”>

HtmlInputButton

<input type=“checkbox” runat=“server”>

HtmlInputCheckBox

<input type=“file” runat=“server”>

HtmlInputFile

<input type=“hidden” runat=“server”>

HtmlInputHidden

<input type=“image” runat=“server”>

HtmlInputImage

<input type=“radio” runat=“server”>

HtmlInputRadioButton

<input type=“password” runat=“server”>

HtmlInputText

<input type=“text” runat=“server”>

HtmlInputText

<select runat=“server”>

HtmlSelect

<table runat=“server”>

HtmlTable

<td runat=“server”>

HtmlTableCell

<th runat=“server”>

HtmlTableCell

<tr runat=“server”>

HtmlTableRow

<textarea runat=“server”>

HtmlTextArea

Any other tag with runat=“server”

HtmlGenericControl

It’s important to know which HtmlControls class corresponds to a given HTML tag because only by knowing the class name can you consult the documentation to determine which properties you can use with that tag and which events the resulting control fires. For example, here’s the HTML controls version of Calc.aspx:

<html>
??<body>
????<form?runat="server">
??????<input?type="text" id="op1" runat="server" />
??????+
??????<input?type="text" id="op2" runat="server" />
??????<input?type="submit" value=" ?=? " OnServerClick="OnAdd"
????????runat="server" />
??????<span?id="Sum" runat="server" />
????</form>
??</body>
</html>

<script?language="C#" runat="server">
??void?OnAdd?(Object?sender,?EventArgs?e)
??{
??????int?a?=?Convert.ToInt32?(op1.Value);
??????int?b?=?Convert.ToInt32?(op2.Value);
??????Sum.InnerText?=?(a?+?b).ToString?();
??}
</script>

Besides the different way in which the form’s controls are declared, the HTML controls version of this Web form differs from the Web controls version in three important respects:

Once you know which class ASP.NET instantiates as a result of applying a runat=“server” tag to an otherwise ordinary HTML tag, you can figure out from the documentation what the tag’s programmatic interface looks like.

Why does ASP.NET support HTML controls when Web controls do everything HTML controls do and then some? HTML controls simplify the task of turning existing HTML forms into Web forms. It takes a while to convert a couple of hundred <input> tags and <select> tags and other HTML tags into Web controls. It doesn’t take long to add runat=“server” to each of them.

Page-Level Events

Server controls that render HTML and fire events are a cornerstone of the Web Forms programming model, but controls aren’t the only entities that fire events. Pages do, too. To understand page-level events, it helps to understand what goes on behind the scenes when ASP.NET processes the first HTTP request for an ASPX file:

  1. It creates a temporary file containing a class derived from System.Web.UI.Page. The Page class is one of the most important classes in ASP.NET; it represents ASP.NET Web pages.

  2. ASP.NET copies the code in the ASPX file, as well as some code of its own, to the Page-derived class. A method named OnAdd in a <script> block in an ASPX file becomes a member method of the derived class.

  3. ASP.NET compiles the derived class and places the resulting DLL in a system folder. The DLL is cached so that steps 1 and 2 won’t have to be repeated unless the contents of the ASPX file change.

  4. ASP.NET instantiates the derived class and “executes” it by calling a series of methods on it. It is during this execution phase that the Page object instantiates any controls declared inside it and solicits their output.

As a Page object executes, it fires a series of events that can be processed by server-side scripts. The most important are Init, which is fired when the page is first instantiated, and Load, which is fired after the page’s controls are initialized but before the page renders any output. The Load event is particularly important to ASP.NET developers because a Load handler is the perfect place to initialize any controls that require dynamic (that is, run-time) initialization. The next section offers an example.

If you want to see the DLLs that ASP.NET generates from your ASPX files, you’ll find them in subdirectories under the Windows (or Winnt) directory’s Microsoft.NET\Framework\vn.n.nnnn\Temporary ASP.NET Files subdirectory, where n.n.nnnn is the version number of the .NET Framework installed on your PC. Drill down in the directory tree under Temporary ASP.NET Files\root, for example, and you’ll find a DLL containing the class that ASP.NET derived from Page to serve Calc.aspx (assuming you ran Calc.aspx from \Inetpub\wwwroot). If the subdirectory contains several DLLs, open them with ILDASM, and you’ll find one containing a Page-derived class named Calc_aspx. (See Figure 5-8.) That’s the class ASP.NET instantiates each time a request arrives for Calc.aspx. If Calc.aspx changes, ASP.NET recompiles the DLL on the next request. Otherwise, the DLL remains on your hard disk so that ASP.NET can reuse it as needed.

Figure 5-8
DLL generated from Calc.aspx.
The Page.Load Event and the Page.IsPostBack Property

Suppose you want to build a Web form that displays today’s date and the four days following it in a drop-down list. If today is January 1, 2002, one solution is to statically initialize a DropDownList control:

<asp:DropDownList?ID="MyList" RunAt="server">
??<asp:ListItem?Text="January?1,?2002" RunAt="server" />
??<asp:ListItem?Text="January?2,?2002" RunAt="server" />
??<asp:ListItem?Text="January?3,?2002" RunAt="server" />
??<asp:ListItem?Text="January?4,?2002" RunAt="server" />
??<asp:ListItem?Text="January?5,?2002" RunAt="server" />
</asp:DropDownList>

The problem with this approach is obvious: every day you’ll have to modify the form to update the dates. A smarter approach is to write a handler for the page’s Load event that initializes the DropDownList at run time:

<asp:DropDownList?ID="MyList" RunAt="server" />
??.
??.
??.
<script?language="C#" runat="server">
??void?Page_Load?(Object?sender,?EventArgs?e)
??{
??????if?(!IsPostBack)?{
??????????for?(int?i=0;?i<5;?i++)?{
??????????????DateTime?date?=
??????????????????DateTime.Today?+?new?TimeSpan?(i,?0,?0,?0);
??????????????MyList.Items.Add?(date.ToString?("MMMM?dd,?yyyy"));
??????????}
??????}
??}
</script>

A Page_Load method prototyped this way is automatically called by ASP.NET when the page fires a Load event. You don’t have to manually wire the event to the handler as you do for controls. The same is true for all page-level events. You can respond to any event fired by Page by writing a method named Page_EventName, where EventName is the name of the event you want to handle.

The Page_Load handler in the previous example adds items to the DropDownList by calling Add on the control’s Items collection. Items represents the items in the DropDownList. Significantly, this implementation of Page_Load initializes the control only if a value named IsPostBack is false. IsPostBack is one of several properties defined in the Page class. Because all code in an ASPX file executes in the context of a class derived from Page, your code enjoys intrinsic access to Page properties and methods. IsPostBack is a particularly important property because it reveals whether your code is executing because the page was requested from the Web server with an HTTP GET (IsPostBack==false) or because the page was posted back to the server (IsPostBack==true). In general, you don’t want to initialize a Web control during a postback because ASP.NET maintains the control’s state for you. If you call Add on the control’s Items collection the first time the page is fetched and then call it again when the page is posted back, the control will have twice as many items in it following the first postback.

The Page.Init Event

Page_Load methods are handy for performing run-time control initializations. You can also write Page_Init methods that fire in response to Init events. One use for Init events is to create controls and add them to the page at run time. Another is to programmatically wire events to event handlers. For example, instead of connecting Click events to an event handler with an OnClick attribute, like this:

<asp:Button?Text=" ?=? " OnClick="OnAdd" RunAt="server" />
??.
??.
??.
<script?language="C#" runat="server">
??void?OnAdd?(Object?sender,?EventArgs?e)
??{
??????int?a?=?Convert.ToInt32?(op1.Text);
??????int?b?=?Convert.ToInt32?(op2.Text);
??????Sum.Text?=?(a?+?b).ToString?();
??}
</script>

you could connect them programmatically in this manner:

<asp:Button?Text=" ?=? " ID="EqualsButton" RunAt="server" />
??.
??.
??.
<script?language="C#" runat="server">
??void?Page_Init?(Object?sender,?EventArgs?e)
??{
??????EqualsButton.Click?+=?new?EventHandler?(OnAdd);
??}

??void?OnAdd?(Object?sender,?EventArgs?e)
??{
??????int?a?=?Convert.ToInt32?(op1.Text);
??????int?b?=?Convert.ToInt32?(op2.Text);
??????Sum.Text?=?(a?+?b).ToString?();
??}
</script>

This is the technique that Visual Studio .NET uses to wire events to event handlers. You’ll see an example at the end of this chapter when you build a Web Forms application with Visual Studio .NET.

Page-Level Directives

ASP.NET supports a number of commands called page-level directives that you can put in ASPX files. They’re sometimes called @ directives because all directive names begin with an @ sign: @ Page, @ Import, and so on. Page-level directives appear between <% and %> symbols and must be positioned at the top of an ASPX file. In practice, @ directives appear in all but the simplest of ASPX files. The following table lists the directives that ASP.NET supports. Succeeding sections document the most commonly used directives. Other directives are discussed as circumstances warrant elsewhere in the book.

ASP.NET @ Directives?

Directive

Description

@ Page

Defines general attributes and compilation settings for ASPX files

@ Control

Defines general attributes and compilation settings for ASCX files

@ Import

Imports a namespace

@ Assembly

Enables linkage to assemblies not linked to by default

@ Register

Registers user controls and custom controls for use in a Web form

@ OutputCache

Exerts declarative control over page caching and fragment caching

@ Reference

Adds a reference to an external ASPX or ASCX file

@ Implements

Identifies an interface implemented by a Web page

The @ Page Directive

Of the various page-level directives that ASP.NET supports, @ Page is the one used most often. The following @ Page directive changes the default language for all scripts that don’t specify otherwise from Visual Basic .NET to C#. It’s especially useful when you “inline” code in an ASPX file by placing it between <% and %> tags:

<%@?Page?Language="C#" %>

And here’s an ASPX file that uses it:

<%@?Page?Language="C#" %>

<html>
??<body>
????<%
??????Response.Write?("Hello,?world");
????%>
??</body>
</html>

As this example demonstrates, ASP.NET pages can use Response and other intrinsic objects in the same way ASP pages can. Because you can’t include Language=“C#” attributes in <% %> blocks, you either need an @ Page directive telling ASP.NET which compiler to pass your code to or a Web.config file that changes the default language on a directory-wide basis. (If you’re not familiar with Web.config files just yet, don’t worry about it for now. You’ll learn all about them in Chapter 9.)

Another common use for @ Page directives is to enable debugging support. By default, ASP.NET builds release-build DLLs from your ASPX files. If you encounter a run-time error and need to debug it, you need DLLs with debugging symbols. The statement

<%@?Page?Debug="true" %>

commands ASP.NET to create debug DLLs rather than release DLLs and enriches the information available to you when you debug a malfunctioning Web form.

As you can see, @ Page is overloaded to support a variety of uses. In all, it supports some 28 different attributes such as Language and Debug. A page can have only one @ Page directive, but that directive can contain any number of attributes. For example, the statement

<%@?Page?Language="C#" Debug="true" %>

enables debugging and sets the page’s default language to C#.

The @ Import Directive

Next to @ Page, the directive that ASP.NET programmers use the most is @ Import. The @ Import directive is ASP.NET’s equivalent of C#’s using directive. Its purpose is to import a namespace so that the types in that namespace are known to the compiler. You need @ Import any time you use an FCL data type that’s defined in a namespace that ASP.NET doesn’t import by default. For example, the statement

<%@?Import?Namespace="System.Data" %>

makes all the data types defined in System.Data available to a Web form.

What namespaces does ASP.NET import by default? Here’s a complete list:

  • System

  • System.Collections

  • System.Collections.Specialized

  • System.Configuration

  • System.IO

  • System.Text

  • System.Text.RegularExpressions

  • System.Web

  • System.Web.Caching

  • System.Web.Security

  • System.Web.SessionState

  • System.Web.UI

  • System.Web.UI.HtmlControls

  • System.Web.UI.WebControls

Because System.Data isn’t imported automatically, you must import it yourself if you want to use System.Data types (for example, DataSet) in a Web form. Otherwise, you’ll receive an error message the first time ASP.NET attempts to compile the page. System.Web.Mail is another example of a commonly used namespace that isn’t imported automatically. Look back at Chapter 3’s SendMail program (Figure 3-7), and you’ll see an @ Import statement importing System.Web.Mail on the very first line of the ASPX file.

Unlike @ Page, @ Import can appear multiple times in a Web page. The following statements import three namespaces and are often used together in ASPX files that access SQL Server databases:

<%@?Import?Namespace="System.Data" %>
<%@?Import?Namespace="System.Data.SqlClient" %>
<%@?Import?Namespace="System.Data.SqlTypes" %>
The @ Assembly Directive

The @ Import directive identifies namespaces containing an application’s data types; @ Assembly identifies assemblies. The .NET Framework class library is implemented in a series of single-file assemblies: Mscorlib.dll, System.dll, and others. If ASP.NET is to compile your page, it must know which assemblies the page references so that it can provide that information to the compiler. The following assembly names are provided to the compiler by default and therefore require no @ Assembly directive:

  • Mscorlib.dll

  • System.dll

  • System.Data.dll

  • System.Drawing.dll

  • System.EnterpriseServices.dll

  • System.Web.dll

  • System.Web.Services.dll

  • System.Xml.dll

These assemblies include the data types that Web forms are most likely to use. But suppose you want to use the FCL’s System.DirectoryServices.DirectorySearcher class in a <script> block to perform a query against Active Directory. Because DirectorySearcher lives in an assembly (System.DirectoryServices.dll) that ASP.NET doesn’t reference by default, its use requires an @ Assembly directive. In the following example, @ Import is required also because DirectorySearcher is defined in a nondefault namespace:

<%@?Import?Namespace="System.DirectoryServices" %>
<%@?Assembly?Name="System.DirectoryServices" %>

It’s coincidental that the namespace name and assembly name are one and the same; that’s not always the case. Note that an assembly name passed to @ Assembly must not include the filename extension (.dll). In addition, the list of “default” assemblies can be changed by editing a machine-wide configuration file named Machine.config or augmented by dropping a Web.config file containing an <assemblies> section into an application root. Like @ Import, @ Assembly can appear multiple times in a Web page.

The @ OutputCache Directive

One of the best ways to optimize the performance of ASP.NET applications is to cache Web pages generated from ASPX files so that they can be delivered straight from the cache if they’re requested again. ASP.NET supports two forms of caching: page caching, which caches entire pages, and fragment (or subpage) caching, which caches portions of pages. The @ OutputCache directive enables an application to exert declarative control over page and fragment caching.

Because examples have a way of lending clarity to a subject (funny how that works, isn’t it?), here’s a simple one that demonstrates @ OutputCache:

<%@?Page?Language="C#" %>
<%@?OutputCache?Duration="60" VaryByParam="None" %>

<html>
??<body>
????Today?is?<%=?DateTime.Now.ToLongDateString?()?%>
??</body>
</html>

This ASPX file displays today’s date in a Web page. (The <%= ... %> syntax is an alternative to using Response.Write. It’s an easy way to inject text into the page’s output.) Since today’s date changes only every 24 hours, it’s wasteful to reexecute this page every time it’s requested. Therefore, the page includes an @ OutputCache directive that caches the output for 60 seconds at a time. Subsequent requests for the page come straight from the cache. When the cache expires and ASP.NET receives another request for the page, ASP.NET reexecutes (and recaches) the page. The Duration attribute controls the length of time that the cached page output is valid.

In real life, Web pages are rarely this simple. The output from an ASPX file often varies based on input provided by users. The designers of ASP.NET anticipated this and gave the page cache the ability to hold multiple versions of a page, qualified by the user input that produced each version. Imagine, for example, that you wrote a Web form that takes a city name and state name as input and returns a satellite image of that city from a database. (Sound far-fetched? It’s not. Chapter 11 includes an application that does just that.) If the city and state names accompanying each request are transmitted in variables called city and state, the following directive caches a different version of the page for each city and state requested for up to 1 hour:

<%@?OutputCache?Duration="3600" VaryByParam="city;state" %>

It’s that simple. You can even use a shortened form of the VaryByParam attribute to cache a separate version of the page for every different input:

<%@?OutputCache?Duration="3600" VaryByParam="*" %>

Now if two users request a satellite image of Knoxville, Tennessee, 30 minutes apart, the second of the two requests will be fulfilled very quickly.

A Web Forms Currency Converter

Figure 5-9 shows a Web form that performs currency conversions using exchange rates stored in an XML file. To see it in action, copy Converter.aspx and Rates.xml, which are listed in Figures 5-10 and 5-11, to \Inetpub\wwwroot and type http://localhost/converter.aspx in your browser’s address bar. Then pick a currency, enter an amount in U.S. dollars, and click the Convert button to convert dollars to the currency of your choice.

Here are some points of interest regarding the source code:

Notice how easily Converter.aspx reads XML from Rates.xml. It doesn’t parse any XML; it simply calls ReadXml on a DataSet and provides an XML file name. ReadXml parses the file and initializes the DataSet with the file’s contents. Each Rate element in the XML file becomes a row in the DataSet, and each row, in turn, contains fields named “Currency” and “Exchange”. Enumerating all the currency types is a simple matter of enumerating the DataSet’s rows and reading each row’s “Currency” field. Retrieving the exchange rate for a given currency is almost as easy. OnConvert uses DataTable.Select to query the DataSet for all rows matching the currency type. Then it reads the Exchange field from the row returned and converts it to a decimal value with Convert.ToDecimal.

One reason I decided to use a DataSet to read the XML file is that a simple change would enable the Web form to read currencies and exchange rates from a database. Were Converter.aspx to open the XML file and parse it using the FCL’s XML classes, more substantial changes would be required to incorporate database input.

A word of caution regarding this Web form: Don’t use it to perform real currency conversions! The exchange rates in Rates.xml were accurate when I wrote them, but they’ll be outdated by the time you read this. Unless you devise an external mechanism for updating Rates.xml in real time, consider the output from Converter.aspx to be for educational purposes only.

Figure 5-9
Web form currency converter.
Converter.aspx
<%@?Import?Namespace=System.Data?%>

<html>
??<body>
????<h1>Currency?Converter</h1>
????<hr>
????<form?runat="server">
??????Target?Currency<br>
??????<asp:ListBox?ID="Currencies" Width="256" RunAt="server" /><br>
??????<br>
??????Amount?in?U.S.?Dollars<br>
??????<asp:TextBox?ID="USD" Width="256" RunAt="server" /><br>
??????<br>
??????<asp:Button?Text="Convert" ID="ConvertButton" Width="256"
????????RunAt="server" /><br>
??????<br>
??????<asp:Label?ID="Output" RunAt="server" />
????</form>
??</body>
</html>

<script?language="C#" runat="server">
??void?Page_Init?(Object?sender,?EventArgs?e)
??{
??????//?Wire?the?Convert?button?to?OnConvert
??????ConvertButton.Click?+=?new?EventHandler?(OnConvert);
??}

??void?Page_Load?(Object?sender,?EventArgs?e)
??{
??????//?If?this?isn't?a?postback,?initialize?the?ListBox
??????if?(!IsPostBack)?{
??????????DataSet?ds?=?new?DataSet?();
??????????ds.ReadXml?(Server.MapPath?("Rates.xml"));
??????????foreach?(DataRow?row?in?ds.Tables[0].Rows)
??????????????Currencies.Items.Add?(row["Currency"].ToString?());
??????????Currencies.SelectedIndex?=?0;
??????}
??}

??void?OnConvert?(Object?sender,?EventArgs?e)
??{
??????//?Perform?the?conversion?and?display?the?results
??????try?{
??????????decimal?dollars?=?Convert.ToDecimal?(USD.Text);
??????????DataSet?ds?=?new?DataSet?();
??????????ds.ReadXml?(Server.MapPath?("Rates.xml"));
??????????DataRow[]?rows?=?ds.Tables[0].Select?("Currency?=?'" +
??????????????Currencies.SelectedItem.Text?+ "'");
??????????decimal?rate?=?Convert.ToDecimal?(rows[0]["Exchange"]);
??????????decimal?amount?=?dollars?*?rate;
??????????Output.Text?=?amount.ToString?("f2");
??????}
??????catch?(FormatException)?{
??????????Output.Text?= "Error";
??????}
??}
</script>
Figure 5-10
Currency converter source code.
Rates.xml
<?xml?version="1.0"?>
<Rates>
??<Rate>
????<Currency>British?Pound</Currency>
????<Exchange>0.698544</Exchange>
??</Rate>
??<Rate>
????<Currency>Canadian?Dollar</Currency>
????<Exchange>1.57315</Exchange>
??</Rate>
??<Rate>
????<Currency>French?Franc</Currency>
????<Exchange>7.32593</Exchange>
??</Rate>
??<Rate>
????<Currency>German?Mark</Currency>
????<Exchange>2.18433</Exchange>
??</Rate>
??<Rate>
????<Currency>Italian?Lira</Currency>
????<Exchange>2162.67</Exchange>
??</Rate>
??<Rate>
????<Currency>Japanese?Yen</Currency>
????<Exchange>122.742</Exchange>
??</Rate>
??<Rate>
????<Currency>Mexican?Peso</Currency>
????<Exchange>9.22841</Exchange>
??</Rate>
??<Rate>
????<Currency>Swiss?Franc</Currency>
????<Exchange>1.64716</Exchange>
??</Rate>
</Rates>
Figure 5-11
XML file used by Converter.aspx.