Chapter 5. Web Forms

In recent years, the cutting edge of software development has shifted from traditional “fat client” apps to Web apps. The integration of back-end systems and seamless data sharing, once the holy grail of corporate IT departments, have given way to concerns over lower total cost of ownership (TCO), zero-footprint installs, and the ability to run applications from anywhere an Internet connection is available. The number one challenge that confronts developers today? “Make this software run on the Web.” Unfortunately, Web programming isn’t easy. Writing applications like Microsoft Word and Microsoft Excel is a well understood art form. Writing applications like eBay and Amazon.com is not. To make matters worse, Web development today is practiced with first-generation tools and technologies that have more in common with 1960s-era Dartmouth BASIC than the modern platforms and development environments that developers have become accustomed to.

Microsoft’s answer to the sordid state of Web programming today is a second-generation programming model called Web Forms. The Web Forms model is part of ASP.NET, which in turn is part of the Microsoft .NET Framework. Just as Active Server Pages (ASP) revolutionized Web programming in the 1990s with an easy-to-use model for dynamically generating HTML on Web servers, ASP.NET advances the state of the art in Web programming by introducing reusable server controls that render HTML to browser clients and fire events that can be processed by server-side scripts. That’s Web Forms in a nutshell: Web pages built around controls and event handlers. If the concept is alluring, the implementation is downright brilliant. Once you learn to build Web apps the Web Forms way, you’ll never want to build them any other way again.

This chapter introduces the Web Forms programming model by describing how to build Web forms both with and without Visual Studio .NET. First you’ll nail down the basics by building Web forms by hand. Then you’ll switch to Visual Studio .NET and experience rapid application development (RAD), Internet-style. Along the way, you’ll be introduced to important Web Forms programming techniques such as code-behind and dynamic control initialization.

Before you begin, it’s worth noting what software you need to run this chapter’s sample programs. First and foremost, you need the .NET Framework. Second, you need Microsoft Internet Information Services (IIS), which is Microsoft’s Web server software. Finally, you need ASP.NET. ASP.NET is automatically installed when you install the .NET Framework SDK on a platform that supports ASP.NET. Currently those platforms include Windows 2000 and Windows XP. Be sure to install IIS before you install the Framework SDK, or you’ll have to go back and install ASP.NET separately.

Web Application Primer

The most proficient developers are those who possess an intimate understanding of the platforms they program and the tools they use to program them. Because it’s difficult to understand how Web forms work if you lack a more general understanding of Web applications and the protocols that drive them, the next several sections provide a working introduction to the operation of Web apps. They’re for developers who have little or no Web programming experience. If you’re already familiar with HTTP, HTML forms, and other Web-related technologies, feel free to skip ahead to the section entitled "Your First Web Form.” If, however, Web apps are a new frontier for you, you’ll find the following discussion helpful in building an in-depth understanding of the Web Forms programming model.

Hypertext Transfer Protocol

The Hypertext Transfer Protocol, better known as HTTP, is the protocol that drives the World Wide Web. Invented by Tim Berners-Lee (“father of the Web”) and documented in RFC 2068, which is available online at http://www.w3.org/Protocols/rfc2068/rfc2068, HTTP is arguably the most important network protocol ever invented, with the notable exception of TCP/IP.

HTTP defines how Web browsers and Web servers communicate with each other. It’s entirely text based, and it’s typically transmitted over TCP connections linking Web browsers to Web servers. Suppose the following HTML file is deployed on a Web server, that its name is Simple.html, and that its URL is www.wintellect.com/simple.html:

<html>
<body>
Hello, world
</body>
</html>

If a user types http://www.wintellect.com/simple.html into Internet Explorer’s address bar, Internet Explorer (IE) uses the Internet’s Domain Name System (DNS) to convert www.wintellect.com into an IP address (for example, 66.45.26.25). Then IE opens a socket connection to the server at that address using a well-known port number (port 80) and transmits an HTTP request similar to this one:

GET /simple.html HTTP/1.1
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
If-Modified-Since: Wed, 24 Oct 2001 14:12:36 GMT
If-None-Match: "50b0d3ee955cc11:a78"
User-Agent: Mozilla/4.0.(compatible; MSIE.6.0; Windows NT 5.1)
Host: www.wintellect.com
Connection: Keep-Alive
[blank line]

The first line of the request is called the start line. It consists of a method name (GET), the name of the resource being requested (simple.html), and an HTTP version number (1.1). GET is one of seven methods defined in HTTP 1.1; it requests a resource from a Web server. The next eight lines make up the message header. Each line, or header, contains additional information about the request, including information about the browser that originated the request (User-Agent). A blank line (a simple carriage return/line feed pair) marks the end of the message header and also the end of the request.

How does the Web server respond to the GET command? Assuming /simple.html is a valid resource identifier and security settings don’t prevent the file from being returned, the server transmits an HTTP response like this one:

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Wed, 24 Oct 2001 14:12:37 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Wed, 24 Oct 2001 14:00:53 GMT
ETag: "d02acf81975cc11:a78"
Content-Length: 46
[blank line]
<html>
<body>
Hello, world
</body>
</html>

Upon receiving the response, the browser parses the HTML returned by the Web server and displays the resulting Web page. The Content-Type header identifies the returned data as HTML, while Content-Length tells the browser how much HTML was returned. The “200” in the first line of the response is an HTTP status code signifying that the server fulfilled the browser’s request. The HTTP specification defines about 40 different status codes, including the infamous 401 (“Unauthorized”) code indicating that the user isn’t authorized to view this resource.

Conversations such as these form the backbone for communications over the Web. As you surf the Web by typing URLs and clicking hyperlinks, your browser issues one GET command after another. Tools such as NetMon—the network packet-sniffing utility that comes with server editions of Windows—let you spy on the HTTP traffic flying back and forth. You don’t have to be an HTTP guru to write ASP.NET applications, but a knowledge of basic HTTP semantics and a familiarity with commonly used request and response headers are a big help in understanding the ASP.NET object model.

HTML Forms

Simple.html is a far cry from a full-blown Web application. It’s a static HTML file that solicits no user input. Real Web applications like the ones located at http://www.amazon.com and http://www.ebay.com accept input from their users, and they vary the HTML that they return to Web browsers based on the contents of that input.

At the heart of almost every genuine Web application is an HTML form. An HTML form is the portion of an HTML document that appears between <form> and </form> tags. The HTML in Example 5-10 describes a simple form representing a Web-based adding machine. The form contains two text input fields for the user to type numbers into and an equals button that submits the form back to the server. Example 5-25 shows how the form appears in Internet Explorer. As you can see, the browser renders an <input type="text"> tag as a text input field and an <input type="submit"> tag as a push button. Similar tags can be used to create check boxes, radio buttons, list boxes, and other basic control types.

Example 5-1. A simple HTML form.

Calc.html

<html>
  <body>
    <form>
      <input type="text" name="op1" />
      +
      <input type="text" name="op2" />
      <input type="submit" value="  =  " />
    </form>
  </body>
</html>
Calc.html displayed in Internet Explorer.
Figure 5-2. Calc.html displayed in Internet Explorer.

A submit button (<input type="submit">) plays a special role in an HTML form. When clicked, it submits the form to a Web server. To be more precise, the browser submits the form along with any input in the form’s controls. How the form is submitted depends on whether the <form> tag includes a Method attribute and the value of that attribute, if present. If the <form> tag lacks a Method attribute or includes a method=“get” attribute, the browser sends an HTTP GET command to the server with the user’s input appended to the URL in the form of a query string:

GET /calc.html?op1=2&op2=2 HTTP/1.1
  .
  .
  .
Connection: Keep-Alive
[blank line]

If, on the other hand, the <form> tag includes a method=“post” attribute, the form is submitted to the server using an HTTP POST command. Rather than transmit user input in the URL, with a POST command the browser passes it in the body of the HTTP request:

POST /calc.html HTTP/1.1
  .
  .
  .
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
[blank line]
op1=2&op2=2

Regardless of whether a GET or a POST command is used, when input from an HTML form is submitted back to the server, we say that a “postback” has occurred. Remember that term because you’ll encounter it repeatedly in this and the next several chapters.

For a first-hand look at HTML forms in action, copy Calc.html to your PC’s Inetpubwwwroot directory and call it up in Internet Explorer by typing the following URL:

http://localhost/calc.html

Now type 2 into each of the form’s text boxes and click the = button. As evidence that a postback occurred, observe what appears in the browser’s address bar (shown in Example 15-3). If you change the <form> tag to

<form method="post">

and repeat the experiment, you won’t see any change in the URL. But the postback occurs just the same, and the Web server can access the user’s input by examining the body of the request.

Calc.html following a postback.
Figure 5-3. Calc.html following a postback.

Server-Side Processing

So far, so good. As Calc.html demonstrates, building the client half of a Web application is easy. After all, it’s just HTML. The hard part is building the code that runs on the Web server. Something has to be running there to extract the user input from the URL (or from the body of the HTTP request if the postback was performed with POST instead of GET) and generate a new Web page that displays the sum of the inputs next to the = button. In other words, if the user enters 2 and 2 and clicks the = button, we’d like the Web server to respond by returning the following HTML:

<html>
  <body>
    <form>
      <input type="text" name="op1" value="2" />
      +
      <input type="text" name="op2" value="2" />
      <input type="submit" value="  =  " />
      4
    </form>
  </body>
</html>

Note the Value attributes added to the <input type="text"> tags. Including the inputs in the page returned from the Web server following a postback perpetuates the illusion that the user is seeing one Web page when in fact he or she is seeing two in succession.

There are many ways to write applications that process input from HTML forms. One solution is an application that uses the Common Gateway Interface (CGI). CGI defines a low-level programmatic interface between Web servers and applications that run on Web servers. Applications that use it are typically written in a programming language called Perl, but they can be written in other languages as well. CGI applications read the input accompanying postbacks through server environment variables and standard input (stdin), and they write HTTP responses to standard output (stdout). CGI has a reputation for being slow because many implementations of it launch a new process to handle each incoming request. Despite this, CGI enjoys widespread use on UNIX-based Web servers. It’s rarely used on the Windows platform.

Another solution—one that’s more likely to find favor among Windows developers—is an ISAPI extension DLL. ISAPI stands for Internet Server Application Programming Interface. ISAPI extensions are Windows DLLs that are hosted by Internet Information Services. They’re referenced by URL just like HTML files (for example, http://www.wintellect.com/calc.dll). IIS forwards HTTP requests to an ISAPI DLL by calling a special function exported from the DLL. The DLL, in turn, generates HTTP responses. ISAPI DLLs are faster than CGI applications because they (typically) run in the same process as IIS. And once loaded, they remain in memory awaiting subsequent requests. The downside to ISAPI DLLs is that they’re difficult to write. An ISAPI developer must be comfortable with the architecture of Windows DLLs and also be willing to deal with HTTP messages at a very low level.

Curious to know what an ISAPI DLL looks like? Example 15-4 shows the C++ source code for an ISAPI DLL that implements a Web calculator identical to the one shown in Example 5-25. The heart of the DLL is the HttpExtensionProc function, which IIS calls on each and every request. The pECB parameter points to a structure containing information about the request, including a pointer to the query string (if any) accompanying the request. If the query string is empty, this implementation of HttpExtensionProc returns an HTML page depicting an empty calculator. Following a postback, however, it parses the op1 and op2 parameters from the query string and returns an HTML page that includes the sum of the inputs. In other words, it returns precisely the HTML we set as our goal a moment ago.

Example 5-4. Source code for an ISAPI DLL.

Calc.cpp

#include <windows.h>
#include <httpext.h>
#include <string.h>
#include <stdlib.h>

int GetParameter (LPSTR pszQueryString, LPSTR pszParameterName);

BOOL WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason,
    LPVOID lpvReserved)
{
    return (TRUE);
}

BOOL WINAPI GetExtensionVersion (HSE_VERSION_INFO* pVer)
{
    pVer->dwExtensionVersion = 
        MAKELONG (HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
    lstrcpy (pVer->lpszExtensionDesc, "Calc ISAPI Extension");
    return (TRUE);
}

DWORD WINAPI HttpExtensionProc (EXTENSION_CONTROL_BLOCK* pECB)
{
    static char* szPrePostbackMessage = 
        "<html>
"                                    
        "<body>
"                                    
        "<form>
"                                    
        "<input type="text" name="op1" />
"      
        "+
"                                         
        "<input type="text" name="op2" />
"      
        "<input type="submit" value="  =  " />
" 
        "</form>
"                                   
        "</body>
"                                   
        "</html>";

    static char* szPostPostbackMessage = 
        "<html>
"                                            
        "<body>
"                                            
        "<form>
"                                            
        "<input type="text" name="op1" value="%d" />
" 
        "+
"                                                 
        "<input type="text" name="op2" value="%d" />
" 
        "<input type="submit" value="  =  " />
"         
        "%d
"                                                
        "</form>
"                                           
        "</body>
"                                           
        "</html>";

    //
    // Build the response message body.
    //
    char szMessage[512];

    if (lstrlen (pECB->lpszQueryString) == 0) {
        // If the query string is empty, return a page that shows
        // an empty calculator.
        lstrcpy (szMessage, szPrePostbackMessage);
    }
    else {
        // If the query string is not empty, extract the user input,
        // process it, and return a page that shows inputs and outputs.
        int a = GetParameter (pECB->lpszQueryString, "op1");
        int b = GetParameter (pECB->lpszQueryString, "op2");
        wsprintf (szMessage, szPostPostbackMessage, a, b, a + b);
    }

    //
    // Build the response message header.
    //
    char szHeader[128];
    DWORD dwCount = lstrlen (szMessage);

    wsprintf (szHeader, "Content-type: text/html
" 
        "Content-Length: %lu

", dwCount);

    //
    // Output the response message header.
    //
    HSE_SEND_HEADER_EX_INFO shei;
    shei.pszStatus = "200 OK";
    shei.pszHeader = szHeader;
    shei.cchStatus = lstrlen (shei.pszStatus);
    shei.cchHeader = lstrlen (shei.pszHeader);
    shei.fKeepConn = FALSE;
	
    pECB->ServerSupportFunction (pECB->ConnID,
        HSE_REQ_SEND_RESPONSE_HEADER_EX, &shei, NULL, NULL);  

    //
    // Output the response message body.
    //
    pECB->WriteClient (pECB->ConnID, szMessage, &dwCount, 0); 

    //
    // Indicate that the request was processed successfully.
    //
    return HSE_STATUS_SUCCESS;
}

int GetParameter (LPSTR pszQueryString, LPSTR pszParameterName)
{
    char* p = strstr (pszQueryString, pszParameterName);

    if (p != NULL) {
        p += strlen (pszParameterName) + 1;
        for (char* tmp = p; *tmp != ’&’ && *tmp != 0; tmp++)
            ;
        int len = tmp - p;
        char* buffer = new char[len + 1];
        strncpy (buffer, p, len);
        int val = atoi (buffer);
        delete buffer;
        return val;
    }
    return 0;
}

The Active Server Pages Solution

A third solution to the problem of processing input from HTML forms on Web servers, and the one that made Windows a popular platform for Web applications in the second half of the 1990s, is Active Server Pages (ASP). Active Server Pages lower the barrier to entry for Web developers by allowing HTML and server-side script to be freely mixed in ASP files. Scripts are typically written in JScript (Microsoft’s version of JavaScript) or VBScript, but they can be written in other languages as well. Intrinsic objects available to those scripts abstract the low-level details of HTTP and make it exceedingly easy to write code that generates HTML content dynamically. Just how easy is ASP? Compare the code in Example 5-4 and Example 5-5 and judge for yourself.

When an Active Server Page is requested, ASP parses the page and executes any scripts contained inside it. Scripts access the input accompanying the request by using the ASP Request object, and they write HTML to the HTTP response using the ASP Response object. Example 5-5 shows the ASP version of Calc.html. The VBScript between <% and %> tags checks the incoming request for inputs named op1 and op2. If the inputs aren’t present, an empty calculator is rendered back to the client. If the inputs are present—that is, if Request (“op1”) and Request (“op2”) evaluate to non-null strings—the server-side script converts the inputs to integers, adds them together, converts the result to a string, and writes the string to the HTTP response using Response.Write.

To prevent the numbers typed into the text boxes from disappearing following a postback, Calc.asp uses ASP’s inline output syntax (<%= … %>) to initialize the Value attributes returned in the <input type="text"> tags. When the page is first requested from the server, Request (“op1”) and Request (“op2”) return empty strings, so the tags output to the client produce empty text boxes:

<input type="text" name="op1" value=""/>
<input type="text" name="op2" value=""/>

But when the form is rendered again following a postback, Request (“op1”) and Request (“op2”) return the values input by the user and are echoed to the client in the tags’ Value attributes:

<input type="text" name="op1" value="2"/>
<input type="text" name="op2" value="2"/>

To verify that this is the case, drop Calc.asp into Inetpubwwwroot and bring it up by typing http://localhost/calc.asp. Then enter a couple of numbers, click the = button, and use the View/Source command in Internet Explorer to view the HTML returned by ASP.

The appeal of ASP—and the reason it caught on so quickly after its introduction in 1996—is that it provides an easy-to-use model for dynamically generating HTML on Web servers. ASP provides a higher level of abstraction than either CGI or ISAPI, which means a flatter learning curve and faster time to market. And ASP integrates seamlessly with ActiveX Data Objects (ADO), which makes it a great solution for writing Web apps that interact with back-end databases.

Example 5-5. ASP calculator applet.

Calc.asp

<%@ Language="VBScript" %>

<html>
  <body>
    <form>
      <input type="text" name="op1" value="<%= Request ("op1") %>"/>
      +
      <input type="text" name="op2" value="<%= Request ("op2") %>" />
      <input type="submit" value="  =  " />
      <%
        If Request ("op1") <> "" And Request ("op2") <> "" Then
            a = CInt (Request ("op1"))
            b = CInt (Request ("op2"))
            Response.Write (CStr (a + b))
        End If
      %>
    </form>
  </body>
</html>

Your First Web Form

ASP is a fine solution for performing server-side processing of HTML form input and dynamically generating HTML, but despite its youth, ASP has already grown long in the tooth. What’s wrong with ASP? For starters, it’s slow. ASP scripts are interpreted rather than compiled, so you incur the cost of recompiling your scripts on each and every page access. Another problem is that ASP lacks a true encapsulation model. It’s not possible, for example, to build reusable ASP controls that encapsulate complex rendering and behavioral logic without resorting to COM.

Enter ASP.NET Web forms. Web forms bring object-oriented programming to the Web. They also combine ASP’s ease of use with the speed of compiled code. Example 5-6 holds the source code for the Web Forms version of Calc.asp. The .aspx file name extension identifies the file as an ASP.NET resource. Figure 5-7 shows how Calc.aspx appears in Internet Explorer. Here’s how to run it on your PC:

  1. Copy Calc.aspx to your PC’s Inetpubwwwroot directory.

  2. Start Internet Explorer or the browser of your choice and type http://localhost/calc.aspx in the browser’s address bar. The Web form will appear in the browser window.

  3. Type 2 and 2 into the input fields and click the = button. The number 4 should appear to the right of the button.

The Inetpubwwwroot directory is an IIS virtual directory; it’s created automatically when you install IIS. If you’d prefer not to clutter Inetpubwwwroot, you can set up virtual directories of your own using the Internet Services Manager applet found under Administrative Tools. You could, for example, put Calc.aspx in a directory named Samples and make Samples a virtual directory. If you assign the Samples directory the logical name “Samples” (virtual directory names don’t have to equal physical directory names, although they often do), you’d run Calc by typing http://localhost/samples/calc.aspx in the browser’s address bar. The same goes for other ASPX files presented in this chapter and throughout the remainder of the book.

Example 5-6. ASP.NET Web form calculator.

Calc.aspx

<html>
  <body>
    <form runat="server">
      <asp:TextBox ID="op1" RunAt="server" />
      +
      <asp:TextBox ID="op2" RunAt="server" />
      <asp:Button Text="  =  " OnClick="OnAdd" RunAt="server" />
      <asp:Label ID="Sum" RunAt="server" />
    </form>
  </body>
</html>

<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>
Calc.aspx in action.
Figure 5-7. Calc.aspx in action.

Web forms are built from a combination of HTML and server controls. Calc.aspx contains four server controls: two TextBox controls, a Button control, and a Label control. TextBox, Button, and Label are classes defined in the System.Web.UI.WebControls namespace in the .NET Framework class library (FCL). Each time Calc.aspx is requested, ASP.NET instantiates TextBox, Button, and Label objects and asks each object to render itself into HTML. The HTML returned by the controls is included in the HTTP response. Execute a View/Source command while Calc.aspx is displayed in Internet Explorer and you’ll see the following HTML:

<html>
  <body>
    <form name="_ctl0" method="post" action="calc.aspx" id="_ctl0">
      <input type="hidden" name="__VIEWSTATE" value="dDwxOTE0NDY4ODE2Ozs+" />

      <input name="op1" type="text" id="op1" />
      +
      <input name="op2" type="text" id="op2" />
      <input type="submit" name="_ctl1" value="  =  " />
      <span id="Sum"></span>
    </form>
  </body>
</html>

The TextBox controls turned into <input type="text"> tags, the Button control turned into an <input type="submit"> tag, and the Label control turned into a <span> tag. In effect, these controls “project” a user interface to the browser by rendering themselves into HTML.

What about the <input> tag named __VIEWSTATE in the HTML returned by Calc.aspx? That’s the mechanism ASP.NET uses to round-trip data from the server to the client and back to the server again. You’ll learn all about it in Chapter 8.

Control Properties

Server controls do more than render HTML. They also implement methods, properties, and events that make them highly programmable. For example, TextBox, Button, and Label controls each expose text through a read/write property named Text. If you wanted “2” to appear in the TextBox controls by default, you could modify the control tags as follows:

<asp:TextBox Text="2" ID="op1"_runat="server" />
<asp:TextBox Text="2" ID="op2" RunAt="server" />

Any public property that a control implements and that can be represented as a name/value pair can be initialized by using the property name as an attribute in the tag that declares the control.

Properties can also be accessed from server-side scripts. In Calc.aspx, the server-side script is the code that appears between the <script> and </script> tags. The statements

int a = Convert.ToInt32 (op1.Text);
int b = Convert.ToInt32 (op2.Text);

extract user input from the TextBox controls by reading their Text properties, while the statement

Sum.Text = (a + b).ToString ();

displays the sum of the inputs by writing to the Label control’s Text property. The names op1, op2, and Sum are the controls’ programmatic IDs. Control IDs are defined by including ID attributes in control tags. In Calc.aspx, the Label control serves as a placeholder for the Web form’s output. Because the default value of a Label control’s Text property is an empty string, nothing appears in the form where the Label control is positioned until the server-side script assigns a string to the control’s Text property.

Control Events

The ability to encapsulate complex rendering and behavioral logic in reusable control classes is one of the fundamental tenets of the Web Forms programming model. Another is the use of events and event handling. Most server controls fire events in response to user input. Button controls, for example, fire Click events when they’re clicked. Wiring an event to an event handler is accomplished by prefixing the event name with “On” and using the resulting text as an attribute in the tag that declares the control. In Calc.aspx, the statement

<asp:Button Text="  =  " OnClick="OnAdd" RunAt="server" />

serves the dual purpose of declaring a Button control and designating OnAdd as the handler for the Button control’s Click events. That’s why the code in OnAdd executed when you clicked the = button. Knowing this, it’s a simple matter to consult the documentation for the list of events a control is capable of firing and connecting handlers to the events that interest you.

What happens under the hood to support the Web Forms event model is a little more complex. Look again at the HTML returned by Calc.aspx. Notice that it contains an HTML form and a submit button. Clicking the button posts the form back to the server using an HTTP POST. Recognizing that the POST command represents a postback that occurred because the user clicked the = button, ASP.NET notifies the Button object and the Button responds by firing a Click event on the server. ASP.NET subsequently calls OnAdd and then renders the page again into HTML. Because the Label control’s Text property now has a non-null string assigned to it, this time the HTML output by the Label control includes a text string between the <span> and </span> tags.

Implementation Notes

Calc.aspx contains no code to prevent the numbers typed into the TextBox controls from disappearing following a postback. The <asp:TextBox> tags in Example 5-6 lack Value attributes such as the ones in Example 5-5’s <input type= “text"> tags. Yet the inputs don’t disappear when you click the = button. Why? Because TextBox controls automatically persist their contents across postbacks. Check the HTML returned to the browser following the postback and you’ll find that <input type="text"> tags rendered by the TextBox controls have Value attributes that equal the text typed by the user.

To make Calc.aspx as simple as possible, I purposely omitted error checking code. To see what I mean, type something other than a simple integer value (say, “hello”) into one of the text boxes and click the = button. The page you see is ASP.NET’s way of responding to unhandled exceptions. To prevent this error, rewrite Calc.aspx’s OnAdd method as follows:

void OnAdd (Object sender, EventArgs e)
{
    try {
        int a = Convert.ToInt32 (op1.Text);
        int b = Convert.ToInt32 (op2.Text);
        Sum.Text = (a + b).ToString ();
    }
    catch (FormatException) {
        Sum.Text = "Error";
    }
}

This version of OnAdd catches the exception thrown when Convert.ToInt32 is unable to convert the input to an integer and responds by displaying the word “Error” to the right of the push button.

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. The following table lists the Web controls provided in version 1 of the .NET Framework class library:

Table 5-1. 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 isnapostrophet 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.

Table 5-2. 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:

  • 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 text boxes 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 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.NETFrameworkvn.n.nnnnTemporary 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 oot, 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 Inetpubwwwroot). 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.

DLL generated from Calc.aspx.
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.

Table 5-3. 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 (Example 13-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 Example 5-10 and Example 5-11, to Inetpubwwwroot 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 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.

Web form currency converter.
Figure 5-9. Web form currency converter.
Example 5-10. Currency converter source code.

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>
Example 5-11. XML file used by Converter.aspx.

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>

Code-Behind Programming

While it’s perfectly legal to put HTML and code in the same ASPX file, in the real world you should segregate the two by placing them in separate files. Proper separation of code and data is achieved in ASP.NET by using a technique called code-behind programming. A Web form that uses code-behind is divided into two parts: an ASPX file containing HTML, and a source code file containing code. Here are two reasons why all commercial Web forms should employ code-behind:

  • Robustness. If a programming error prevents the code in an ASPX file from compiling, the error won’t come to light until the first time the page is accessed. Careful testing will take care of this, but how often do unit tests achieve 100 percent code coverage?

  • Maintainability. ASP files containing thousands of lines of spaghetti-like mixtures of HTML and script are not uncommon. Clean separation of code and data makes applications easier to write and to maintain.

Code-behind is exceptionally easy to use. Here’s a recipe for using code-behind in Web forms coded in C#:

  1. Create a CS file containing event handlers, helper methods, and other code—everything that would normally appear between <script> and </script> tags in an ASPX file. Make each of these source code elements members of a class derived from System.Web.UI.Page.

  2. In your Page-derived class, declare protected fields whose names mirror the IDs of the controls declared in the ASPX file. For example, if the Web form includes a pair of TextBox controls whose IDs are UserName and Password, include the following statements in your class declaration:

    protected TextBox UserName;
    protected TextBox Password;

    Without these fields, the CS file won’t compile because references to UserName and Password are unresolvable. At run time, ASP.NET maps these fields to the controls of the same name so that reading UserName.Text, for example, reads the text typed into the TextBox control named UserName.

  3. Compile the CS file into a DLL and place the DLL in a subdirectory named bin in the virtual directory that holds the ASPX file.

  4. Place the HTML portion of the Web form—everything between the <html> and </html> tags—in an ASPX file. Include in the ASPX file an @ Page directive containing an Inherits attribute that identifies the Page-derived class in the DLL.

That’s it; that’s all there is to it. You get all the benefits of embedding code in an ASPX file but none of the drawbacks. The application in the next section demonstrates code-behind at work.

The Lander Application

In 1969, Neil Armstrong landed the Apollo 11 Lunar Excursion Module (LEM) on the moon with just 12 seconds of fuel to spare. You can duplicate Armstrong’s feat with a Web-based lunar lander simulation patterned after the lunar lander game popularized on mainframe computers in the 1970s. This version of the game is built from an ASP.NET Web form, and it uses code-behind to separate code and HTML. It’s called Lander, and it’s shown in Internet Explorer in Figure 5-12. Its source code appears in Example 5-13.

To run the program, copy Lander.aspx to your PC’s Inetpubwwwroot directory. If Inetpubwwwroot lacks a subdirectory named bin, create one. Then compile Lander.cs into a DLL and place the DLL in the bin subdirectory. Here’s the command to compile the DLL:

csc /t:library Lander.cs

Next, start your browser and type

http://localhost/lander.aspx

into the address bar. Begin your descent by entering a throttle value (any percentage from 0 to 100, where 0 means no thrust and 100 means full thrust) and a burn time in seconds. Click the Calculate button to input the values and update the altitude, velocity, acceleration, fuel, and elapsed time read-outs. Repeat until you reach an altitude of 0, meaning you’ve arrived at the moon’s surface. A successful landing is one that occurs with a downward velocity of 4 meters per second or less. Anything greater and you dig your very own crater in the moon.

The Lander application.
Figure 5-12. The Lander application.

Lander.aspx is a Web form built from a combination of ordinary HTML and ASP.NET server controls. Clicking the Calculate button submits the form to the server and activates the DLL’s OnCalculate method, which extracts the throttle and burn time values from the input fields and updates the onscreen flight parameters using equations that model the actual flight physics of the Apollo 11 LEM. Like many Web pages, Lander.aspx uses an HTML table with invisible borders to visually align the page’s controls.

Lander.aspx differs from the ASPX files presented thus far in this chapter in that it contains no source code. Lander.cs contains the form’s C# source code. Inside is a Page-derived class named LanderPage containing the OnCalculate method that handles Click events fired by the Calculate button. Protected fields named Altitude, Velocity, Acceleration, Fuel, ElapsedTime, Output, Throttle, and Seconds serve as proxies for the controls of the same names in Lander.aspx. LanderPage and OnCalculate are declared public, which is essential if ASP.NET is to use them to serve the Web form defined in Lander.aspx.

Example 5-13. The Lander source code.

Lander.aspx

<%@ Page Inherits="LanderPage" %>

<html>
  <body>
    <h1>Lunar Lander</h1>
    <form runat="server">
      <hr>
      <table cellpadding="8">
        <tr>
          <td>Altitude (m):</td>
          <td><asp:Label ID="Altitude" Text="15200.0"
            RunAt="Server" /></td>
        </tr>
        <tr>
          <td>Velocity (m/sec):</td>
          <td><asp:Label ID="Velocity" Text="0.0"
            RunAt="Server" /></td>
        </tr>
        <tr>
          <td>Acceleration (m/sec2):</td>
          <td><asp:Label ID="Acceleration" Text="-1.6"
            RunAt="Server" /></td>
        </tr>
        <tr>
          <td>Fuel (kg):</td>
          <td><asp:Label ID="Fuel" Text="8165.0" RunAt="Server" /></td>
        </tr>
        <tr>
          <td>Elapsed Time (sec):</td>
          <td><asp:Label ID="ElapsedTime" Text="0.0"
            RunAt="Server" /></td>
        </tr>
        <tr>			
          <td>Throttle (%):</td>
          <td><asp:TextBox ID="Throttle" RunAt="Server" /></td>
        </tr>
        <tr>			
          <td>Burn Time (sec):</td>
          <td><asp:TextBox ID="Seconds" RunAt="Server" /></td>
        </tr>
      </table>
      <br>
      <asp:Button Text="Calculate" OnClick="OnCalculate"
        RunAt="Server" />
      <br><br>
      <hr>
      <h3><asp:Label ID="Output" RunAt="Server" /></h3>
    </form>
  </body>
</html>

Lander.cs

using System;
using System.Web.UI;
using System.Web.UI.WebControls;

public class LanderPage : Page
{
    const double gravity = 1.625;      // Lunar gravity
    const double landermass = 17198.0; // Lander mass

    protected Label Altitude;
    protected Label Velocity;
    protected Label Acceleration;
    protected Label Fuel;
    protected Label ElapsedTime;
    protected Label Output;
    protected TextBox Throttle;
    protected TextBox Seconds;

    public void OnCalculate (Object sender, EventArgs e)
    {
        double alt1 = Convert.ToDouble (Altitude.Text);

        if (alt1 > 0.0)  {
            // Check for blank input fields
            if (Throttle.Text.Length == 0) {
                Output.Text = "Error: Required field missing";
                return;
            }

            if (Seconds.Text.Length == 0) {
                Output.Text = "Error: Required field missing";
                return;
            }

            // Extract and validate user input
            double throttle;
            double sec;
            try {
                throttle = Convert.ToDouble (Throttle.Text);
                sec = Convert.ToDouble (Seconds.Text);
            }
            catch (FormatException) {
                Output.Text = "Error: Invalid input";
                return;
            }

            if (throttle < 0.0 || throttle > 100.0) {
                Output.Text = "Error: Invalid throttle value";
                return;
            }

            if (sec <= 0.0) {
                Output.Text = "Error: Invalid burn time";
                return;
            }

            // Extract flight parameters from the Label controls
            double vel1 = Convert.ToDouble (Velocity.Text);
            double fuel1 = Convert.ToDouble (Fuel.Text);
            double time1 = Convert.ToDouble (ElapsedTime.Text);

            // Compute thrust and remaining fuel
            double thrust = throttle * 1200.0;
            double fuel = (thrust * sec) / 2600.0;
            double fuel2 = fuel1 - fuel;

            // Make sure there’s enough fuel
            if (fuel2 < 0.0) {
                Output.Text = "Error: Insufficient fuel";
                return;
            }
				
            // Compute new flight parameters
            Output.Text = "";
            double avgmass = landermass + ((fuel1 + fuel2) / 2.0);
            double force = thrust - (avgmass * gravity);
            double acc = force / avgmass;

            double vel2 = vel1 + (acc * sec);
            double avgvel = (vel1 + vel2) / 2.0;
            double alt2 = alt1 + (avgvel * sec);
            double time2 = time1 + sec;

            // If altitude <= 0, then we’ve landed
            if (alt2 <= 0.0) {
                double mul = alt1 / (alt1 - alt2);
                vel2 = vel1 - ((vel1 - vel2) * mul);
                alt2 = 0.0;
                fuel2 = fuel1 - ((fuel1 - fuel2) * mul);
                time2 = time1 - ((time1 - time2) * mul);

                if (vel2 >= -4.0)
                    Output.Text = "The Eagle has landed";
                else
                    Output.Text = "Kaboom!";
            }				

            // Update the Labels to show latest flight parameters
            Altitude.Text = alt2.ToString ("f1");
            Velocity.Text = vel2.ToString ("f1");
            Acceleration.Text = acc.ToString ("f1");
            Fuel.Text = fuel2.ToString ("f1");
            ElapsedTime.Text = time2.ToString ("f1");
        }
    }
}

How Code-Behind Works

A logical question to ask at this point is, “How does code-behind work?” The answer is deceptively simple. When you put Web forms code in an ASPX file and a request arrives for that page, ASP.NET derives a class from System.Web.UI.Page to process the request. The derived class contains the code that ASP.NET extracts from the ASPX file. When you use code-behind, ASP.NET derives a class from your class—the one identified with the Inherits attribute—and uses it to process the request. In effect, code-behind lets you specify the base class that ASP.NET derives from. And since you write the base class, you control what goes in it.

Figure 5-14 shows (in ILDASM) the class that ASP.NET derived from LanderPage the first time Lander.aspx was requested. You can clearly see the name of the ASP.NET-generated class—Lander_aspx—as well as the name of its base class: LanderPage.

DLL generated from a page that uses code-behind.
Figure 5-14. DLL generated from a page that uses code-behind.

Using Code-Behind Without Precompiling: The Src Attribute

If you like the idea of separating code and data into different files but for some reason would prefer not to compile the source code files yourself, you can use code-behind and still allow ASP.NET to compile the code for you. The secret? Place the CS file in the same directory as the ASPX file and add a Src attribute to the ASPX file’s @ Page directive. Here’s how Lander.aspx’s Page directive would look if it were modified to let ASP.NET compile Lander.cs:

<%@ Page Inherits="LanderPage" Src="Lander.cs" %>

Why anyone would want to exercise code-behind this way is a question looking for an answer. But it works, and the very fact that the Src attribute exists means someone will probably find a legitimate use for it.

Using Non-ASP.NET Languages in ASP.NET Web Forms

Code embedded in ASPX files has to be written in one of three languages: C#, Visual Basic .NET, or JScript. Why? Because even though compilers are available for numerous other languages, ASP.NET uses parsers to strip code from ASPX files and generate real source code files that it can pass to language compilers. The parsers are language-aware, and ASP.NET includes parsers only for the aforementioned three languages. To write a Web form in C++, you have to either write a C++ parser for ASP.NET or figure out how to bypass the parsers altogether. Code-behind is a convenient mechanism for doing the latter.

Code-behind makes it possible to code Web forms in C++, COBOL, and any other language that’s supported by a .NET compiler. Example 5-15 contains the C++ version of Lander.cs. Lander.cpp is an example of C++ with Managed Extensions, better known as managed C++. That’s Microsoft’s term for C++ code that targets the .NET Framework. When you see language extensions such as __gc, which declares a managed type, being used, you know you’re looking at managed C++.

The following command compiles Lander.cpp into a managed DLL and places it in the current directory’s bin subdirectory:

cl /clr lander.cpp /link /dll /out:binLander.dll

You can replace the DLL created from the CS file with the DLL created from the CPP file and Lander.aspx is none the wiser; it still works the same as it did before. All it sees is a managed DLL containing the LanderPage type identified by the Inherits attribute in the ASPX file. It neither knows nor cares how the DLL was created or what language it was written in.

Example 5-15. Managed C++ version of Lander.cs.

Lander.cpp

#using <system.dll>
#using <mscorlib.dll>
#using <system.web.dll>

using namespace System;
using namespace System::Web::UI;
using namespace System::Web::UI::WebControls;

public __gc class LanderPage : public Page
{
protected:
    static const double gravity = 1.625;      // Lunar gravity
    static const double landermass = 17198.0; // Lander mass

    Label* Altitude;
    Label* Velocity;
    Label* Acceleration;
    Label* Fuel;
    Label* ElapsedTime;
    Label* Output;
    TextBox* Throttle;
    TextBox* Seconds;

public:
    void OnCalculate (Object* sender, EventArgs* e)
    {
        double alt1 = Convert::ToDouble (Altitude->Text);

        if (alt1 > 0.0)  {
            // Check for blank input fields
            if (Throttle->Text->Length == 0) {
                Output->Text = "Error: Required field missing";
                return;
            }

            if (Seconds->Text->Length == 0) {
                Output->Text = "Error: Required field missing";
                return;
            }

            // Extract and validate user input
            double throttle;
            double sec;

            try {
                throttle = Convert::ToDouble (Throttle->Text);
                sec = Convert::ToDouble (Seconds->Text);
            }
            catch (FormatException*) {
                Output->Text = "Error: Invalid input";
                return;
            }

            if (throttle < 0.0 || throttle > 100.0) {
                Output->Text = "Error: Invalid throttle value";
                return;
            }

            if (sec <= 0.0) {
                Output->Text = "Error: Invalid burn time";
                return;
            }

            // Extract flight parameters from the Label controls
            double vel1 = Convert::ToDouble (Velocity->Text);
            double fuel1 = Convert::ToDouble (Fuel->Text);
            double time1 = Convert::ToDouble (ElapsedTime->Text);

            // Compute thrust and remaining fuel
            double thrust = throttle * 1200.0;
            double fuel = (thrust * sec) / 2600.0;
            double fuel2 = fuel1 - fuel;

            // Make sure there’s enough fuel
            if (fuel2 < 0.0) {
                Output->Text = "Error: Insufficient fuel";
                return;
            }
				
            // Compute new flight parameters
            Output->Text = "";
            double avgmass = landermass + ((fuel1 + fuel2) / 2.0);
            double force = thrust - (avgmass * gravity);
            double acc = force / avgmass;

            double vel2 = vel1 + (acc * sec);
            double avgvel = (vel1 + vel2) / 2.0;
            double alt2 = alt1 + (avgvel * sec);
            double time2 = time1 + sec;

            // If altitude <= 0, then we’ve landed
            if (alt2 <= 0.0) {
                double mul = alt1 / (alt1 - alt2);
                vel2 = vel1 - ((vel1 - vel2) * mul);
                alt2 = 0.0;
                fuel2 = fuel1 - ((fuel1 - fuel2) * mul);
                time2 = time1 - ((time1 - time2) * mul);

                if (vel2 >= -4.0)
                    Output->Text = "The Eagle has landed";
                else
                    Output->Text = "Kaboom!";
            }				

            // Update the Labels to show latest flight parameters
            Altitude->Text = (new Double (alt2))->ToString ("f1");
            Velocity->Text = (new Double (vel2))->ToString ("f1");
            Acceleration->Text = (new Double (acc))->ToString ("f1");
            Fuel->Text = (new Double (fuel2))->ToString ("f1");
            ElapsedTime->Text = (new Double (time2))->ToString ("f1");
        }
    }
};

Web Forms and Visual Studio .NET

Now that you know what makes Web forms tick, it’s time to learn to build Web forms the Visual Studio .NET way. Visual Studio .NET brings rapid application development to the Web. You design forms by choosing controls from a palette and dropping them onto forms. You write event handlers by double-clicking controls and filling in empty method bodies. And you compile and run your application by executing simple menu commands. It’s no accident that building Web forms with Visual Studio .NET feels a lot like building Windows applications with Visual Basic. That’s exactly the feel Microsoft intended to convey.

This chapter closes with a step-by-step tutorial describing how to build a Web-based mortgage payment calculator with Visual Studio .NET. Figure 5-16 shows the finished product. Enter a loan amount, interest rate, and term (length of the loan in months), and click the Compute Payment button. The corresponding monthly payment appears at the bottom of the page.

Web-based mortgage payment calculator.
Figure 5-16. Web-based mortgage payment calculator.

Step 1: Create a Virtual Directory

When you create a Web application project with Visual Studio .NET, you don’t tell Visual Studio .NET where to the store the files by entering a path name; you enter a URL. Assuming you want to store the files on your PC but don’t want to clutter Inetpubwwwroot with project subdirectories, your first step is to create a project directory and turn it into a virtual directory so that it’s URL-addressable. Here are the steps:

  1. Create a folder named Projects somewhere on your hard disk to hold your Web application projects. Then create a Projects subdirectory named LoanCalc.

  2. Start the Internet Information Services applet in Windows. You’ll find it under Administrative Tools.

  3. In the left pane of the Internet Information Services window, expand the Local ComputerWeb Sites folder, and select Default Web Site.

  4. Select the New/Virtual Directory command from the Action menu to start the Virtual Directory Creation Wizard.

  5. When the wizard asks for an alias, type “LoanCalc.” When it asks for a path name, enter the path to the LoanCalc directory you created in step 1. Click the Next and Finish buttons until the wizard closes.

You just created a physical directory named LoanCalc and converted it into a virtual directory. Its URL is http://localhost/loancalc. Before proceeding, verify that LoanCalc appears with the other virtual directories listed under Default Web Site in the Internet Information Services window, as shown in Figure 5-17.

Internet Information Services window.
Figure 5-17. Internet Information Services window.

Step 2: Create a Web Application Project

Start Visual Studio .NET, and then select the File/New/Project command. Fill in the New Project dialog exactly as shown in Figure 5-18. Verify that the statement “Project will be created at http://localhost/LoanCalc” appears near the bottom of the dialog, that Visual C# Projects is selected in the Project Types box, and that ASP.NET Web Application is selected in the Templates box. Then click OK to create a new project named LoanCalc in the LoanCalc directory you created a moment ago.

Creating the LoanCalc project.
Figure 5-18. Creating the LoanCalc project.

Step 3: Change to Flow Layout Mode

The next screen you see is the Visual Studio .NET Web forms designer. Here you design forms by dragging and dropping controls. Before you begin, however, you have a decision to make.

The forms designer supports two layout modes: grid layout and flow layout. Grid layout mode lets you place controls anywhere in a form. It relies on CSS-P (Cascading Style Sheets-Position) to achieve precise positioning of controls and other HTML elements. Flow layout mode eschews CSS-P and relies on the normal rules of HTML layout. Flow layout mode is more restrictive, but it’s compatible with all contemporary browsers.

So that LoanCalc will be compatible with as wide a range of browsers as possible, go to Visual Studio .NET’s Properties window and change to flow layout mode by changing the document’s pageLayout property from GridLayout, which is the default, to FlowLayout. Note that “DOCUMENT” must be selected in the combo box at the top of the Properties window for the pageLayout property to appear. If DOCUMENT doesn’t appear in the drop-down list, click the empty form in the forms designer.

Before proceeding, click the form and select the Snap To Grid command from Visual Studio .NET’s Format menu. This setting will make it easier to size and position the form’s controls consistently with respect to one another.

Step 4: Add a Table

Since you’re working in flow layout mode, tables are your best friend when it comes to positioning and aligning controls on a page. Click the Web form design window to set the focus to the designer. Then use Visual Studio .NET’s Table/Insert/Table command to add an HTML table to the Web form. When the Insert Table dialog appears, fill it in as shown in Figure 5-19. In particular, set Rows to 4, Columns to 2, Width to 100 percent, Border Size to 0, and Cell Padding to 8. When you click OK, the table appears in the forms designer window.

Adding a table to a Web form.
Figure 5-19. Adding a table to a Web form.

Step 5: Insert Text

Click the cell in the table’s upper left corner. A caret appears signaling that any text you type will appear inside the table cell. Type “Principal”. Then go to the Properties window and change the cell’s align property to “right” to right align the text. Repeat the process to add “Rate (percent)” to the cell in the next row, and “Term (months)” to the cell below that. Finish up by dragging the vertical divider between table cells until the table’s leftmost column is just wide enough to fit the text. Figure 5-20 shows how the table should look when you’ve finished.

The LoanCalc form after adding text.
Figure 5-20. The LoanCalc form after adding text.

Step 6: Add TextBox Controls

If the Toolbox window isn’t displayed somewhere in the Visual Studio .NET window (it appears at the far left by default), choose the Toolbox command from the View menu to display it. Click the Toolbox’s Web Forms button to display a list of Web controls, and then use drag-and-drop to add TextBox controls to the right-hand cells in the table’s first three rows. (See Figure 5-21.) Finish up by using the Properties window to change the TextBox controls’ IDs to “Principal”, “Rate”, and “Term”, respectively.

The LoanCalc form after adding TextBox controls.
Figure 5-21. The LoanCalc form after adding TextBox controls.

Step 7: Add a Button Control

Add a Button control to the rightmost cell in the table’s bottom row, as shown in Figure 5-22. Size the button so that its width equals that of the text box above it. Change the button text to “Compute Payment” and the button ID to “PaymentButton”.

The LoanCalc form after adding a Button control.
Figure 5-22. The LoanCalc form after adding a Button control.

Step 8: Add a Label Control

Select a Label control from the Toolbox, and add it to the form just below the table, as shown in Figure 5-23. Change the Label control’s text to an empty string and its ID to “Output”.

The LoanCalc form after adding a Label control.
Figure 5-23. The LoanCalc form after adding a Label control.

Step 9: Edit the HTML

The next step is to dress up the form by adding a few HTML elements. Start by clicking the HTML button at the bottom of the designer window to view the HTML generated for this Web form. Manually add the following statements between the <body> tag and the <form> tag:

<h1>Mortgage Payment Calculator</h1>
<hr>

Next scroll to the bottom of the file and add these statements between the </table> tag and the <asp:Label> tag:

<br>
<hr>
<br>
<h3>

As a last step, move the </h3> tag that Visual Studio .NET inserted so that it comes after the <asp:Label> tag. Now click the Design button at the bottom of the forms designer to switch out of HTML view and back to design view. Figure 5-24 shows how the modified form should look.

The LoanCalc form after adding HTML tags.
Figure 5-24. The LoanCalc form after adding HTML tags.

Step 10: Add a Click Handler

Double-click the form’s Compute Payment button. Visual Studio .NET responds by adding a method named PaymentButton_Click to WebForm1.aspx.cs and showing the method in the program editor. Add the following code to the empty method body:

try {
    double principal = Convert.ToDouble (Principal.Text);
    double rate = Convert.ToDouble (Rate.Text) / 100;
    double term = Convert.ToDouble (Term.Text);
    double tmp = System.Math.Pow (1 + (rate / 12), term);
    double payment = principal * (((rate / 12) * tmp) / (tmp - 1));
    Output.Text = "Monthly Payment = " + payment.ToString ("c");
}
catch (Exception) {
    Output.Text = "Error";
}

PaymentButton_Click isn’t an ordinary method; it’s an event handler. Check out the InitializeComponent method that Visual Studio .NET wrote into WebForm1.aspx.cs and you’ll find a statement that registers PaymentButton_Click to be called in response to the Compute Payment button’s Click events. InitializeComponent is called by OnInit, which is called when the page fires an Init event. The handler that you just implemented responds to Click events by extracting user input from the form’s TextBox controls, computing the corresponding monthly payment, and displaying the result in the Label control.

Step 11: Build and Test

You’re now ready to try out your handiwork. Select BuildLoanCalc from the Build menu to compile your code. If it builds without errors, choose Start (or Start Without Debugging) from the Debug menu to run it. When the Web form pops up in Internet Explorer, verify that it works properly by entering the following three inputs:

  • Principal: 100000

  • Rate: 10

  • Term: 240

Now click the Compute Payment button. If “Monthly Payment = $965.02” appears at the bottom of the page, give yourself a pat on the back. You just built your first Web form with Visual Studio .NET.

The LoanCalc Source Code

Of the many files in the LoanCalc directory, WebForm1.aspx and WebForm1.aspx.cs are the two that interest us the most. They contain LoanCalc’s source code. (If you’re curious to know what all those other files are for, be patient. You’ll learn about many of them—particularly Global.asax and Web.config—in Chapter 9. Most of the extra files are superfluous in this example, but Visual Studio .NET insists on creating them anyway.) WebForm1.aspx contains no code; only HTML. Visual Studio .NET always uses code-behind in its Web forms, so all the C# code is located in WebForm1.aspx.cs. Example 5-25 shows the finished versions of both files. Most of the content that you see was generated by Visual Studio .NET. The statements that you added are shown in boldface type.

Given what you already know about Web forms, there isn’t much in LoanCalc’s source code to write home about. The ASPX file defines the user interface using a mixture of HTML and Web controls, and the CS file contains the Compute Payment button’s Click handler as well as the code that connects the button to the handler. Neither file contains anything you couldn’t have written by hand, but it should be apparent that building Web forms visually is faster and less error prone than building them manually.

Example 5-25. The LoanCalc source code.

WebForm1.aspx

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" 
  Inherits="LoanCalc.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
  <HEAD>
    <title>WebForm1</title>
    <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
    <meta name="CODE_LANGUAGE" Content="C#">
    <meta name="vs_defaultClientScript" content="JavaScript">
    <meta name="vs_targetSchema"
      content="http://schemas.microsoft.com/intellisense/ie5">
  </HEAD>
  <body>
    <h1>Mortgage Payment Calculator</h1>
    <hr>
    <form id="form1"_method="post" runat="server">
      <TABLE id="Table1" cellSpacing="1" cellPadding="8" width="100%"
        bgColor="thistle" border="0">
        <TR>
          <TD align="right" style="WIDTH: 99px">Principal</TD>
          <TD><asp:TextBox id="Principal" runat="server"></asp:TextBox></TD>
        </TR>
        <TR>
          <TD align="right" style="WIDTH: 99px">Rate (percent)</TD>
          <TD><asp:TextBox id="Rate" runat="server"></asp:TextBox></TD>
        </TR>
        <TR>
          <TD align="right" style="WIDTH: 99px">Term (months)</TD>
          <TD><asp:TextBox id="Term" runat="server"></asp:TextBox></TD>
        </TR>
        <TR>
          <TD style="WIDTH: 99px"></TD>
          <TD><asp:Button id="PaymentButton" runat="server"
            Text="Compute Payment" Width="156px"></asp:Button></TD>
        </TR>
      </TABLE>
      <br>
      <hr>
      <br>
      <h3>
        <asp:Label id="output"_runat="server"></asp:Label>
      </h3>
    </form>
  </body>
</HTML>

WebForm1.aspx.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace LoanCalc
{
    /// <summary>
    /// Summary description for WebForm1.
    /// </summary>
    public class WebForm1 : System.Web.UI.Page
    {
        protected System.Web.UI.WebControls.TextBox Rate;
        protected System.Web.UI.WebControls.TextBox Term;
        protected System.Web.UI.WebControls.Button PaymentButton;
        protected System.Web.UI.WebControls.TextBox Principal;
        protected System.Web.UI.WebControls.Label Output;
	
        private void Page_Load(object sender, System.EventArgs e)
        {
            // Put user code to initialize the page here
        }

        #region Web Form Designer generated code
        override protected void OnInit(EventArgs e)
        {
            //
            // CODEGEN: This call is required by the ASP.NET
            // Web Form Designer.
            //
            InitializeComponent();
            base.OnInit(e);
        }
		
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {    
            this.PaymentButton.Click +=
                new System.EventHandler(this.PaymentButton_Click);
            this.Load += new System.EventHandler(this.Page_Load);
        }
        #endregion

        private void PaymentButton_Click(object sender, System.EventArgs e)
        {
            try {
                double principal = Convert.ToDouble (Principal.Text);
                double rate = Convert.ToDouble (Rate.Text) / 100;
                double term = Convert.ToDouble (Term.Text);
                double tmp = System.Math.Pow (1 + (rate / 12), term);
                double payment =
                    principal * (((rate / 12) * tmp) / (tmp - 1));
                Output.Text = "Monthly Payment = " + payment.ToString ("c");
            }
            catch (Exception) {
                Output.Text = "Error";
            }
        }
    }
}

A Glimpse into the Future

A lot of developers—not all of them Microsoft employees—believe that Web forms are a glimpse into the future of Web programming. The idea of encapsulating complex rendering and behavioral logic in reusable control classes and having those controls fire events that can be processed on Web servers is one whose time has come. Server controls provide the building blocks for sophisticated Web forms while shielding the developer from the nuances of HTML and client-side scripting. That’s a win no matter how you look at it (or which computer company—Sun or Microsoft—you swear allegiance to).

You now know more about Web forms than 99.999 percent of the people on the planet. But there’s still much to learn. In the next chapter, you’ll continue the journey to Web Forms enlightenment by learning about all the different controls in System.Web.UI.WebControls. After that, you’ll learn how to build Web controls of your own, structure applications around Web forms, create secure Web forms, and much more. Most important, you’ll discover that putting your software on the Web isn’t such a scary proposition after all.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.198.94