Home > Articles > Programming > Windows Programming

  • Print
  • + Share This

The Web Forms Programming Model

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

  • A Web form's user interface is "declared" using a combination of HTML and server controls. Controls can be customized by using control properties as attributes in the tags that declare the controls. Controls are also bona fide objects that are instantiated and executed each time the page that hosts them is requested.

  • Server controls fire events that can be handled by server-side scripts. In effect, ASP.NET abstracts the divide between client and server by creating the illusion that events are fired and handled on the same machine. In reality, events fire on the server when an external stimulus (such as the click of a button) causes the form to post back to the server.

  • Server-side scripts aren't scripts in the conventional sense of the word. Unlike ASP scripts, which are interpreted rather than compiled and therefore run rather slowly, server-side scripts in ASP.NET are compiled to common intermediate language (CIL) and executed by the common language runtime. Although ASP.NET pages incur more processing overhead than static HTML pages, they tend to execute much faster than ASP pages.

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. Table 1 lists the Web controls provided in version 1.0 of the .NET Framework class library.

Table 1: Web Controls

Class Name



Displays rotating banners in Web forms


Generates submit buttons


Displays calendars with selectable dates


Displays a check box in a Web form


Displays a group of check boxes


Validates user input by comparing it to another value


Validates user input using the algorithm of your choice


Displays data in tabular format


Displays items using single-column or multicolumn lists


Generates HTML drop-down lists


Generates hyperlinks


Displays images in Web forms


Displays graphical push buttons


Generates programmable text fields


Generates hyperlinks that post back to the server


Generates HTML list boxes


Generates literal text in a Web form


Groups other controls


Displays a radio button in a Web form


Displays a group of check boxes


Verifies that user input falls within a specified range


Validates user input using regular expressions


Displays items in simple lists


Verifies that an input field isn't empty


Generates HTML tables


Generates text input fields


Displays a summary of validation errors


Displays XML documents and documents generated from XML 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.

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="_ctrl0" method="post" action="calc.aspx" id="_ctrl0">

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

Table 2: HTML Controls


Corresponding HTML Control

<a runat="server">


<button runat="server">


<form runat="server">


<img runat="server">


<input type="button" runat="server">


<input type="reset" runat="server">


<input type="submit" runat="server">


<input type="checkbox" runat="server">


<input type="file" runat="server">


<input type="hidden" runat="server">


<input type="image" runat="server">


<input type="radio" runat="server">


<input type="password" runat="server">


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


<select runat="server">


<table runat="server">


<td runat="server">


<th runat="server">


<tr runat="server">


<textarea runat="server">


Any other tag with runat="server"


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:

  <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" />

<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 ();

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:

  • The attribute that wires the button control to the event handler is named OnServerClick rather than OnClick. Why? Because an <input type="button" runat="server" /> tag translates into an instance of HtmlInputButton, and HtmlInputButton controls, unlike Button controls, don't fire Click events. They fire ServerClick events.

  • OnAdd reads input from the textboxes using the property name Value rather than Text. HtmlInputText controls don't have Text properties as Labels and TextBoxes do; instead, they expose their contents using Value properties.

  • OnAdd writes its output by initializing Sum's InnerText property instead of its Text property. The <span runat="server"> tag creates an instance of HtmlGenericControl. HtmlGenericControl doesn't have a Text property, but it does have an InnerText property.

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 that 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. ASP.NET 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 created 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, runtime) 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 to the bottom of 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 that 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 4.) 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 4 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" />

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"));

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 runtime 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 ();

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 ();

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 article 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. Table 3 lists the directives that ASP.NET supports. Succeeding sections document the most commonly used directives. Other directives are discussed as circumstances warrant.

Table 3: ASP.NET @ Directives



@ 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#" %>

   Response.Write ("Hello, world");

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 need either 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.

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 runtime 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 statement. Its purpose is to import a namespace so that the types in that namespace are known to the compiler. You need @ Import anytime 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.

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 that 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 the 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 can be 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" %>

  Today is <%= DateTime.Now.ToLongDateString () %>

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.) Because today's date changes only every 24 hours, it's wasteful to re-execute 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 re-executes (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. 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 one 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 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 Listings 5 and 6, 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:

  • Because it uses the DataSet class defined in the System.Data namespace, Converter.aspx begins with an @ Import directive importing System.Data.

  • Rather than show a hard-coded list of currency types in the list box, Converter.aspx reads them from Rates.xml. Page_Load reads the XML file and initializes the list box. To add new currency types to the application, simply add new <Rate> elements to Rates.xml. They'll automatically appear in the list box the next time the page is fetched.

  • For good measure, Converter.aspx wires the Convert button to the Click handler named OnConvert programmatically rather than declaratively. The wiring is done in Page_Init.

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 filename. 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 Web form currency converter.

Listing 5: Converter.aspx: Currency Converter Source Code

<%@ Import Namespace=System.Data %>

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

<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";

Listing 6: Rates.xml: XML File Used by Converter.aspx

<?xml version="1.0" encoding="UTF-8"?>
  <Currency>British Pound</Currency>
  <Currency>Canadian Dollar</Currency>
  <Currency>French Franc</Currency>
  <Currency>German Mark</Currency>
  <Currency>Italian Lira</Currency>
  <Currency>Japanese Yen</Currency>
  <Currency>Mexican Peso</Currency>
  <Currency>Swiss Franc</Currency>
  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.