Chapter 16. ASP.NET Web Forms and Controls

Topics in This Chapter

  • Web Client-Server Communications: Web page requests and responses rely on HTML to represent content, and HTTP to define the protocol that governs client-server interaction. JavaScript, ASP, and ASP.NET are compared as ways to develop an application.

  • Structure of an .aspx Page: An .aspx Web page is compiled as a class. It contains a variety of directives that control the page's behavior and can link it to external code. This section looks at the basic elements that comprise an .aspx file, including how Viewstate is used to maintain state information for the form.

  • Inline and Code-Behind Pages: ASP.NET offers three models for designing a Web application: inline code, code-behind, and code-behind with partial classes. The latter two permit the page interface to be separated from the program logic. The implementation code is placed in a code-behind file that is referenced by an .aspx page.

  • HTML Server-Side Controls and Web Controls: ASP.NET provides controls that correspond to HTML tags and are accessed as classes in the server-side code. It also provides a number of native “Web” controls that include features not found on standard HTML controls. These include validation controls, data source controls, and sophisticated data display controls such as the DataList, GridView, and TreeView.

  • Master and Content Pages: ASP.NET supports the creation of a master Web page that serves as a template for other “content” pages that visually inherit from it. This chapter explains how to create master pages containing special “place holder” controls that define where derived content pages may insert their custom content.

Developing applications to run on the Internet is a broad topic, and this book devotes its last three chapters to the subject. This chapter introduces key features of ASP.NET and focuses on using controls to create Web pages; Chapter 17, “The ASP.NET Application Environment,” looks at application development issues such as managing sessions and configuring ASP.NET control files; and the book's final chapter discusses Web Services.

ASP.NET is technically regarded as the next generation of ASP. There are syntactic similarities and compatibilities, but the differences are even greater. Thus, this chapter makes no attempt to explain ASP.NET in terms of ASP. There are some comparisons, but no prior knowledge of ASP is assumed. You will also find traces of JavaScript sprinkled in a couple of applications, but the code is easily understood within the context of the examples.

The first section provides a tour of client-server Web interaction. It begins with a simple JavaScript application that demonstrates the fundamental techniques used to transfer information between a client and Web server. It then shows how the ASP.NET model encapsulates these principles and adds an object-oriented approach to Web page design and implementation. Subsequent sections survey the array of Web presentation and validation controls. Special attention is given to the DataList and GridView controls.

One note: IIS (Microsoft Internet Information Server) is conspicuous by its absence in our discussion. Although all of the applications were tested in an IIS environment, and the preponderance of ASP.NET applications will run on this Web server, they are not bound to it. Microsoft has created an open-source HTTP server named Cassini that is written in C#. It's fully HTTP/1.1 compliant, supports directory browsing, as well as many of the standard MIME types, and most importantly, supports ASP.NET. It has been tested on Apache servers and is clearly geared toward making ASP.NET the Web development tool of choice for multiple Web platforms.

Client-Server Interaction over the Internet

At its core, the Internet consists of resources and users who want to access Web-based resources. Three mechanisms are required to enable this access: a naming scheme (URI) to locate the resources, a protocol (HTTP) defining how the request/response process works, and a language (HTML) for publishing information and providing a means to navigate to the resources. This environment is quite different than that facing the Windows Forms programmer and presents the Web application developer with three major challenges:

  • HTML. The developer must be familiar with the Hypertext Markup Language in order to create Web Forms. Certainly, HTML generators are a useful accessory, but at some point, the developer has to understand the raw HTML.

  • HTTP. The primary task of a Web application is to respond to HTTP requests. These requests may be sent by either an HTTP GET or POST method and may contain headers in addition to the main message body. The language used by the Web developer must provide access to the full HTTP message. For example, to process a received form, a Web application must be able to extract data from fields on the form. Header information can be equally important. It can be used to control caching, identify the source of the request, and pass cookies between the client and server.

  • State. There is no intrinsic feature in HTML or HTTP that maintains the state of variables during the request/response operation. Figure 16-1 illustrates this. The client receives an empty survey form, fills it out, and posts it back to the server. The server detects an error and returns the same Web page—which is blank again. Preserving data (state) over the round trip(s) between client and server is, perhaps, the biggest challenge facing the Web programmer.

    Request/response process

    Figure 16-1. Request/response process

One of the best ways to understand and appreciate a technology is to compare it with other technologies that perform a similar task. With that in mind, this section takes a simple Web application and implements it in JavaScript, ASP, and ASP.NET. The objectives are twofold: to understand the problems associated with implementing a Web Forms application, and to illustrate the evolutionary approach that ASP.NET uses to meet these challenges. You do not need experience with ASP or JavaScript to understand the examples, but it helps. Their function is to demonstrate how the HTTP GET and POST methods send data to a server and maintain state information. The examples could just as easily use Perl or PHP scripting.

Web Application Example: Implementing a BMI Calculator

This application calculates the Body Mass Index (BMI) for a given height and weight. The user enters this information (see Figure 16-2) into a form and then clicks Submit Form to send the request to a server. The server calculates the BMI and returns a Web page that includes the BMI and original data entered.

BMI Web calculator

Figure 16-2. BMI Web calculator

Realistically, the calculation could be done using JavaScript on the client's page without requiring a trip to the server. But let's suppose that we also want to record each time the calculator is used. For that, a trip to the server is necessary.

A JavaScript BMI Calculator

JavaScript remains the primary language used for client-side coding on a Web application. It is platform and browser independent (although there are a few exceptions). As we will see later, ASP.NET automatically generates JavaScript for its client-side processing.

The first example uses JavaScript on both the client and server side. Listing 16-1 contains the HTML and JavaScript that comprise the client-side page. For brevity, some of the less important code is excluded.

The HTML in the <body> defines the fields and buttons on a form bmi_input. This form defines GET as the HTML method for delivering the contents of the form to the server. When the button is clicked, control passes to the JavaScript function post that verifies the content of the form and sends it to the server. We'll get to the other JavaScript code after we look at the server side.

Example 16-1. JavaScript/HTML to Display a BMI Calculator— bmi.htm

<HTML>
<HEAD><TITLE>BMI Calculator</TITLE>
<SCRIPT LANGUAGE="Javascript" TYPE="text/javascript">
<!—
// hrefstr is set to querystring values—if any
var hrefstr= location.search.substring(1,
      location.search.length);
function showbmi(){
   if (hrefstr )   // display values in form fields
   {
      var parms   = hrefstr.split('&'),
      var f = self.document.forms[0];
      f.bmi.value = eval(parms[0]);
      f.hti.value = eval(parms[2]);
      f.wt.value  = eval(parms[3]);
      f.htf.value = eval(parms[1]);
   }
}
// -->Code for Verify goes here.
// Post Form to Web Host
function post() {
   if (verify()) //Call function to verify values in form fields
   {
      var f = self.document.forms[0];
      f.bmi.value=0;
      f.submit();  // Use HTTP GET to send form to server
   }
}
//-->
</script>
</HEAD>
<BODY bgcolor=#ffffff>
<FORM NAME="bmi_input" method=GET action=bmicalculator.htm>
   <table border=0 cellpadding=2 cellspacing=0 width=180
      bgcolor=#cccccc>
   <tr><td colspan=3 align=center><font size=2 color=#33333>
      <b>BMI Calculator</b> </td></tr>
   <tr><td><font size=2 ><b>BMI:</b></td>
      <td colspan=2 ><input type=text size=5 name=bmi>
   </td></tr>
   <tr><td colspan=3><hr size=1></td></tr>
   <tr><td><font size=2 >Height:</td>
      <td><input type=text size=3 name=htf maxlength=1></td>
      <td><input type=text size=3 name=hti maxlength=2></td>
   </tr>
   <tr><td>&nbsp;</td><td valign=top><font size=2>feet</td>
       <td valign=top><font size=2>inches</td>
   </tr>
   <tr><td><font size=2 >Weight:</td>
      <td colspan=2><input type=text size=3 name=wt
            maxlength=3></td>
   </tr>
   <tr><td colspan=3 align=center>
   <INPUT TYPE="button" VALUE="Submit Form" ONCLICK=
            "self.post()";>
   </td></tr>
   <tr><td colspan=3>&nbsp;</td></tr>
   </table>
</FORM>
<SCRIPT LANGUAGE="Javascript" TYPE="text/javascript">
<!--
   showbmi();   // Fills form with values if they exist
//-->
</script>
</body>
</html>

Using the GET method causes the form's data to be sent to the server as part of the URL. This string of data, referred to as a query string, contains name-value pairs that correspond to the name of the fields on the form, followed by their value. Figure 16-3 shows how form data is passed to the Web page bmicalculator.htm that calculates the BMI.

Passing data in a query string

Figure 16-3. Passing data in a query string

Data is returned to the client in the same manner—by appending it to the URL of the page containing the original form. Note that bmi is now set to the calculated value. Here is the server-side code that creates this response:

<html><head></head>
<body >
<script language="javascript">
<!—
   // Use location.search to access the query string
   var hrefstr = location.search.substring(1,
         location.search.length);
   var parms   = hrefstr.split('&'),
      feet    =  parms[1];
      inch    =  parms[2];
      pounds  =  parms[3];
      totinches = eval(feet)*12 + eval(inch);
      // ..... Calculate BMI
      var h2 = totinches * totinches;
      bmi = Math.round(eval(pounds) * 703 * 10/ h2)/10;
      // --> Place code here to maintain count of visits.
      //... Return value and original parameters as a query string
      ndx = hrefstr.indexOf('htf'),
      self.location = 'bmi.htm?bmi=
            '+bmi+"&"+hrefstr.substring(ndx);
   //-->
</script>
</body></html>

This code grabs the values from the query string using location.search, parses them, calculates the BMI, and then creates a URL for the page to be returned. The final step is for the client browser to display the calculated BMI value.

At the bottom of the code in Listing 16-1 is a call to the JavaScript function showbmi(). This function operates much like the preceding server code. It extracts the data from the query string and displays it in the appropriate fields on the form. Note that the function first confirms that the query string is not empty, as will be the case the first time the form is loaded.

The use of a query string is popular for applications that transfer small amounts of data. However, it becomes a problem with large forms because the query string is limited by the 2K maximum string length imposed by some browsers (IE). In addition, it raises obvious security concerns by exposing data in a URL line that can be altered by the user. Query string encryption can mitigate this problem and should be considered where it makes sense. A more robust solution is to use the HTTP POST method in place of GET.

An ASP (Active Server Pages) BMI Calculator

JavaScript is intended primarily for client-side scripting and cannot process data sent by a POST method. This example replaces the JavaScript server code with an ASP file that handles the POST request. We change one statement in the client code to indicate that POST is being used and to provide the name of the ASP server page:

<FORM NAME="bmi_input" method=POST action=bmicalculator.asp>

The code for the ASP server file is quite simple. It accesses the data passed from the client by referencing a request object. The data is passed to a VBScript function that calculates the BMI. A URL is then constructed that contains the query string with the BMI value and other parameters. The response.redirect method sends the form to the client's browser.

<script language="VBScript" runat="server">
function getBMI(inch,feet,pounds)
   totinches = feet*12 + inch
   h2 = totinches * totinches
   getBMI= (pounds * 703 * 10/ h2)/10
end function
</script>
<%
   ' ... POST data is available in request object
   inch    = request("hti")
   feet    = request("htf")
   pounds  = request("wt")
   bmi= left(cstr(getBMI(inch,feet,pounds)),4)
   ' ... return value and original parameters as a query string
   hrefstr = "&htf=" +cstr(feet) + "&hti=" + cstr(inch) +
             "&wt="&cstr(pounds)
   response.redirect ("bmi3.htm?bmi="+bmi+hrefstr)
%>

This solution illustrates the fundamental ASP approach of using VBScript to interact with HTTP response and request objects. In addition, HTML and JavaScript can be intermixed with the ASP code to create Web Forms. Although this offers a degree of flexibility, it often results in a babel of code and inconsistent coding techniques.

Using ASP.NET to Implement a BMI Calculator

Before building an ASP.NET solution for the calculator, let's first take a general look at the features of ASP.NET and how they affect Web Forms programming. One way to understand the Web Forms model is to look at the requirements faced by the ASP.NET designers—and how they met them.

  • Integration with the .NET environment. Web Forms are built on the Common Language Runtime (CLR) and have access to all types in the Framework Class Library, as well as special namespaces, such as System.Web and System.Web.UI that support Web applications. An important aspect of .NET integration is that it is required only on the server side; users are not required to install the .NET Framework on their computers. The emphasis is on rendering code that runs unaided on a browser. In fact, the emitted code is tailored to recognize the browser it is running on, so it can take full advantage of its features.

  • Linking client-side HTML controls with server-side controls. All controls in ASP.NET are classes. Controls that appear on a browser window are renderings based on HTML tags. To reconcile this differing technology, ASP.NET includes server-side controls that map directly to HTML tags. For example, the <input type = text> tags we saw in the earlier examples are represented by the HtmlInputText class found in the System.Web.UI.HtmlControls namespace. When information on a Web page is sent to a server, all HTML tags designated to run on the server are converted into their corresponding .NET class and compiled as members of the Web page. We'll see how HTML tags are mapped to server controls in the next example.

  • Compiled rather than interpreted Web pages. Traditionally, Web pages have been rendered by JavaScript, VBScript, Perl, and other scripting interpreters. In contrast, an ASP.NET Web page consists of a user interface defined by HTML, and interface logic written in a .NET language such as C# or VB.NET. The first request for an ASP page (.aspx file) results in the page being compiled into a .NET class. Further requests are then handled by the assembly created by compilation. For the user, it means faster Web access; for the developer, it means applications are developed using the same .NET tools available for desktop and component applications.

ASP.NET offers three models for implementing a Web application:

  • Inline code. The HTML markup code and application code (C#) coexist in a single .aspx file.

  • Code-behind. The markup code and application code are placed in separate files. The markup is in an .aspx file and the logic code resides in a .cs or dll file.

  • Partial classes. This is a variation of the code-behind model that places the markup and code in separate files. The difference is that the code-behind file is implemented using partial classes. It is stored as a .cs file and is compiled along with the markup file. This model is available only with ASP.NET versions 2.0 and later.

None of the models offers a performance advantage over the others. This leaves the choice of model up to one's preference—or need—for code separation. Let's now examine the models by using each to implement the BMI application.

Inline Code Model

The code for the BMI Web Form application is shown in Listing 16-2. Although it resembles an HTML page, an .aspx page is actually an XML-formatted page. It is processed on the server and used to generate a mixture of HTML and JavaScript that is sent to the browser.

Notice that this example contains actual C# code—between the <script> </script> tags—that calculates the BMI value. This method is executed on the server and is not visible as source code to the client.

Example 16-2. ASP.NET Inline Code Implementation of a BMI Calculator

<%@ Page Language="C#"  %>
<HTML>
<HEAD><TITLE>BMI Calculator</TITLE>
<script  runat="Server">
   // Calculate BMI from values on form
   private void getBMI(object sender, System.EventArgs e)
   {
      try
      {
         decimal f =  Convert.ToDecimal(htf.Value);
         decimal inch = Convert.ToDecimal(hti.Value);
         decimal w = Convert.ToDecimal(wt.Value);
         decimal totinches = f * 12 + inch;
         decimal h2 = totinches * totinches;
         decimal massIndex = (w * 703 * 10/ h2)/10;
         bmi.Value = massIndex.ToString("##.##");
      }catch (Exception ex)
         { bmi.Value=" "; }
    }
</script>
</HEAD>
<BODY bgcolor=#ffffff>
<FORM NAME="bmi_input" runat=server>
   <table border=0 cellpadding=2 cellspacing=0 width= 180
         bgcolor=#cccccc>
   <tr><td colspan=3 align=center><font size=2>
         <b>BMI Calculator</b>
         </td></tr>
   <tr><td><font size=2 ><b>BMI:</b></td>
         <td colspan=2 ><input type=text size=5 id=bmi
                  runat=server></td></tr>
   <tr><td colspan=3><hr size=1></td>
   </tr>
   <tr><td><font size=2 >Height:</td>
         <td><input type=text size=3 id=htf maxlength=1
                  runat=server></td>
         <td><input type=text size=3 id=hti maxlength=2
                  runat=server></td>
   </tr>
   <tr><td>&nbsp;</td><td valign=top><font size=2>feet</td>
         <td valign=top><font size=2>inches</td>
   </tr>
   <tr><td><font size=2 >Weight:</td>
         <td colspan=2><input type=text size=3 id=wt maxlength=3
                  runat=server></td>
   </tr>
   <tr><td colspan=3 align=center>
   <INPUT TYPE="button" VALUE="Submit Form"
            OnServerClick="getBMI"
            id=bmiButton runat=server>
         </td>
   </tr>
   <tr><td colspan=3>&nbsp;</td>
   </tr>
   </table>
</FORM>
</body>
</html>

To understand the differences between standard HTML and ASP.NET code, let's compare how controls are specified in Listing 16-1 versus Listing 16-2.

  • The most obvious difference is the addition of the runat=server attribute in the <Form> and <Input> tags. This designation converts any HTML elements to HTML server controls that can be processed by the server-side code prior to sending them to the browser. The following code illustrates how an <input> tag is transformed before it emitted to the client's browser:

    ASP.NET statement:
       <input type=text size=3 id=wt maxlength=3 runat=server>
    HTML statement emitted:
       <input name="wt" id="wt" type="text" size="3"
          maxlength="3" value="168" />
    
  • The <Form> tag does not include the method or action attribute. By default, the POST method is used. As the following code shows, the page is self-referencing, which means the page sends the form to itself.

    ASP.NET statement:
       <FORM NAME="bmi_input" runat=server>
    HTML statement emitted:
       <form name="_ctl0" method="post" action="bmi.aspx"
          id="_ctl0">
    
  • The <button> tag contains an OnServerClick event delegate. This indicates that a method on the server will handle the event. The resulting HTML references a JavaScript function that has been added to post the form when the button is clicked.

    HTML statement emitted:
       <input language="javascript" onclick =
          "__doPostBack('_ctl1','')" name="_ctl1"
          type="button" value="Submit Form" />
    

ASP.NET also adds three hidden fields to the HTML page it returns to the browser:

<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE"
     value="dDwxMzc5NjU4NTAwOzs+iIczTTLHA74jT/02tIwU9FRx5uc=" />

The first field, __EVENTTARGET, specifies the control that invoked the request (known as a postback) to the server; the second, __EVENTARGUMENT, contains any parameters required by the event. The __VIEWSTATE field is by far the most interesting.

View State

View state is the feature in ASP.NET that automatically maintains the state of server-side controls (controls declared with runat=server) as a form makes the round trip between the client and the server. In other words, it allows a page to place data in a control such as a ListBox or GridView one time—usually when the page is first loaded—and ensures the data is retained as subsequent postbacks occur. Here is how it works.

When a page is posted back to the server, the data in the hidden __VIEWSTATE field is deserialized and used to set the state of controls and the overall Web page. Data received in the HTTP request as part of a POST operation is used to set the values of those related controls (note that for controls whose contents are posted—TextBox, CheckBox, RadioButtons—the posted data overwrites the view state data). The __VIEWSTATE field is then updated before it is passed back to the client. The returned view state value plays no role on the client other than to represent a snapshot of control values at the time the page is received by the browser.

Because the view state string can be viewed in a source listing, questions about security become a legitimate issue. However, unlike the query string, the value is not represented as clear text.

value="dDwxMzc5NjU4NTAwOzs+iIczTTLHA74jT/02tIwU9FRx5uc="

By default, a machine-specific authentication code is calculated on the data and appended to the view state string. The full string is then Base64 encoded. It is possible to decode the string, but the difficulty in doing so will thwart most casual efforts. Tampering with the string can also be detected by the server and results in a security exception being thrown. As always, use Secure Sockets Layer (SSL) to ensure absolute security for Internet communications.

Core Note

Core Note

Maintaining view state data within a Web page makes the page independent of the server. This means that a Web page request can be sent to any server in a Web farm—rather than restricting it to a single server.

Performance is another issue that must be considered when working with the view state value. By default, it maintains data for all server-side controls on the form. The control information is not limited to only the data value associated with the control. For example, when a DataGrid is used, the view state includes not only the data in each cell, but also column and row headers, and related style attributes. The view state data can easily add several thousand bytes to a Web page and slow performance.

To improve performance, you may want to disable view state for the Web page and apply it only to selected controls. Set the EnableViewState attribute in the @Page directive to disable it at the page level:

<%@ Page Language="C#" EnableViewState="False" %>

Then, to enable view state for an individual control, apply the attribute as shown in the following code:

<input type=text size=3 id=wt maxlength=3 EnableViewState=true
      runat=server >

Of course, you can also take the opposite tact and leave view state on for the page and turn it off for selective controls.

The decision to enable or disable view state is one of the key decisions in designing a Web page that displays large amounts of data in a server-side control. The easiest approach is to allow ViewState to take care of the details. However, this can result in a large HTML payload being transferred repeatedly between browser and server. An alternative is to design the code to reload the data into the controls on each postback. The data may be fetched from the original source—usually a database—or it may be stored on the server in session state or a cache. These last two options are described in Chapter 17, “The ASP.NET Application Environment.”

Core Note

Core Note

ViewState maintains not only a control's data but also its state. For example, it keeps track of the last item(s) selected in a ListBox and permits the ListBox to be redisplayed in its most recent state. The drawback of manually repopulating a control, rather than using ViewState, is that this state information is lost.

The @Page Directive

The last element to discuss in Listing 16-2 is the @Page directive. An .aspx file can contain only one @Page directive, and it is typically—although not required—the first statement in the file. The purpose of this directive is to assign attribute values that control the overall behavior of the Web page, as well as specify how the page is assembled and compiled on the server. Table 16-1 lists selected attributes for the @Page directive.

Table 16-1. Attributes for the @Page Directive

Attribute/Value

Description

EnableSessionState = value

Specifies the type of access the page has to the session state information. Sessions are discussed in Chapter 17.

EnableViewState = bool

Enables or disables view state for the page. Individual controls can override this value.

EnableViewStateMac = bool

Is used to make the view state more secure by adding a validator hash string to the view state string that enables the page to detect any possible attempt at corrupting original data.

SmartNavigation = bool

Setting this to true can improve the rendering of pages for users of Internet Explorer 5.0 and later. Its improvements include

  • Eliminating flickering when a page loads.

  • Retaining the input focus on the field last having it.

  • Preserving the scroll position on pages longer than one screen.

ErrorPage = url

Specifies the URL of a Web page that is called when an unhandled exception occurs.

Culture = string

A culture setting for the page based on the CultureInfo class. This attribute affects how culture-dependent functions, such as numbers and dates, are displayed. The following setting causes a DateTime object to be displayed in a European format using German months and days: Culture="de-DE". These settings can also be set in the Web.config file as described in the next chapter.

UICulture = id

Specifies the user interface culture for this page.

Example: UICulture="de"

Trace = bool

Turns tracing on or off for the page. Default is false. When tracing is on, diagnostic information about a single request for an .aspx page is collected. The results of the trace are available programmatically and are appended as a series of tables at the bottom of the browser output. Tracing is discussed in Chapter 17.

Inherits = class name

Specifies the base class used to generate a class from the .aspx file. The default is System.Web.UI.Page. If code-behind is used, the class name from this code is used.

MasterPageFile = master page

Specifies the “master page” from which the current page visually inherits its layout. Introduced with 2.0.

theme = theme name

Specifies the subdirectory containing the .skin file (specifies the appearance of controls) and any other images and style sheets that define the look and style (theme) of a page. The theme file is stored in the /app_themes subdirectory. Introduced with 2.0.

Language = language

Specifies the language for inline code.

Codebehind = *.dll

Specifies the name of a compiled code-behind file. This file must be in the in subdirectory of the application.

Codefile = *.cs

Specifies a code-behind file containing a partial class. Introduced with 2.0.

Src = path

Specifies a code-behind file containing source code.

The Codebehind, Codefile, and Src attributes specify the assembly or source file containing the business logic code for the page. Instead of placing code between <script></script> tags as we did in Listing 16-2, the code is placed in a separate code-behind file that is referenced by these attributes. Before discussing code-behind, let's look at some additional directives that are frequently used in .aspx pages.

Other Directives

@Import Directive

This directive is used to import a namespace in an .aspx page. It serves the same purpose as the C# using statement.

<%@ Import namespace="System.Net" %>

Several namespaces are automatically imported in a page, making this directive unnecessary in most cases. These namespaces include

  • System, System.IO

  • System.Web.UI, System.Web.UI.HtmlControls, System.Web.UI.WebControls

  • System.Web, System.Web.SessionState, System.Web.Caching

  • System.Text, System.Text.RegularExpressions

@Assembly Directive

This directive links an assembly to the current page while the page is being compiled. This provides the page with access to all types in the assembly. It takes two forms:

<%@ Assembly Name="webfunctions" %>
<%@ Assembly Src="webfunctions.cs" %>

The first version references an assembly that may be private or deployed in the Global Assembly Cache; the second statement causes the source to be dynamically compiled into an assembly that is linked to the Web page. Note that assemblies in the application's in subdirectory are automatically linked to a page and do not need to be referenced.

@Register Directive

This directive associates alias names with namespaces and classes. Its purpose is to provide a convenient syntax for adding custom controls to a Web page. The directive takes two forms:

Syntax:

<%@ Register Tagprefix="tagprefix" Namespace="namespace"
      Assembly="assembly" %>
<%@ Register Tagprefix="tagprefix" Tagname="tagname"
      Src="pathname" %>

Attributes:

Tagprefix

Alias for a namespace.

Namespace

The namespace to associate with Tagprefix.

Assembly

Assembly in which namespace resides.

Tagname

Alias to associate with a class.

Src

The file containing the user control

The first form of the directive is used to add an ASP.NET server control to a page; the second form is used with a custom control contained in a source file. In the latter case, the TagPrefix and TagName are always used together as a colon-separated pair. Here is a code segment that places a user control defined in the file hdr.ascx on a Web page. The @Register directive defines the alias pair that is used to declare the control on the Web page.

<%@ Register TagPrefix="uc1" TagName="hdr" Src="hdr.ascx" %>

<form id="Form1" method="post" runat="server">
      <uc1:hdr id="Hdr1" runat="server"></uc1:hdr>
</form>

We'll make use of this directive in Section 16.4, which provides examples of how to create and use custom controls. Note that @Register directive information also can be stored in the Web.config file (see Chapter 17), eliminating the need to place it in a Web page.

The Code-Behind Model

The example in Listing 16-2 contains both C# to implement program logic and HTML to render the user interface. A Web page can also be configured as an .aspx file, containing only the interface code and a separate code-behind file that contains the program logic and serves as the base class for compiling the .aspx file (see Figure 16-4). This code-behind file takes the .cs extension.

How ASP.NET responds to a Web page request

Figure 16-4. How ASP.NET responds to a Web page request

The code-behind page is linked to the .aspx file as an assembly or source file using the Codebehind or Src attributes of the @Page directive. If the Codebehind attribute is used, the assembly must be stored in the in directory of the application.

Let's now look at how the code in Listing 16-2 can be changed to use a code-behind file. We create a code-behind file named bmicb.cs (see Listing 16-3) to replace the code currently between the <script/> tags. The @Page directive links this file to the .aspx file:

<%@ Page Language="C#" Src="bmicb.cs" Inherits="BMI" %>

The code-behind page is always structured as a class whose name must be specified by an Inherits attribute. This class is shown in Listing 16-3. Let's take a close look at it, because knowledge of how the code-behind file and the .aspx file interact on the server is essential to understanding the ASP.NET Web page model.

Example 16-3. Code-Behind File for BMI Calculator—bmicb.cs

using System;
using System.Web.UI.HtmlControls;
public class BMI : System.Web.UI.Page
{
   //     <input type=text id=htf runat=server>
   protected HtmlInputText htf;
   //     <input type=text id=hti runat=server>
   protected HtmlInputText hti;
   //     <input type=text id=wt runat=server>
   protected HtmlInputText wt;
   //     <input type=text id=bmi runat=server>
   protected HtmlInputText bmi;
   //     <input type="button" VALUE="Submit Form" id=bmiButton
   //      runat=server>
   protected HtmlInputButton bmiButton;

   override protected void OnInit(EventArgs e)
   {
      // Delegate to handle button click on client
      bmiButton.ServerClick += new EventHandler(getBMI);
   }
   protected void getBMI(object sender, System.EventArgs e)
   {
      decimal f =  Convert.ToDecimal(htf.Value);
      decimal inch = Convert.ToDecimal(hti.Value);
      decimal w = Convert.ToDecimal(wt.Value);
      decimal totinches = f * 12 + inch;
      decimal h2 = totinches * totinches;
      decimal massIndex = (w * 703 * 10/ h2)/10;
      bmi.Value = massIndex.ToString("##.##");
   }
}

The first thing to observe from this listing is that it consists of one class—BMI—that derives from the System.Web.UI.Page class. The Page class is to Web Forms what the Form class is to Windows Forms. Like the Form, it has a sequence of events that are fired when it is initialized and loaded. It also has several properties that control its behavior—many of which correspond to the @Page directive attributes already discussed. We'll look at the Page class later in this section.

One of the trickiest aspects of learning ASP.NET is grasping how server-side controls work—specifically, how the content and action of controls displayed on a browser are managed on the server. Figure 16-5 illustrates the relationship. Each server control is declared as a field in the BMI class. When the values of the controls are posted to the server, they are assigned as field values. In this example, all of the controls are HTML controls—that is, standard HTML controls with the runat=server attribute.

Binding between code-behind page and .aspx page

Figure 16-5. Binding between code-behind page and .aspx page

The id value in the tag must match the field name identically. The field types are defined in the System.Web.UI.HtmlControls namespace. Each HTML control has a one-to-one mapping with an HTML tag, as shown in Table 16-2.

Table 16-2. HTML Controls and Their Tags

Control

HTML Tag

HtmlAnchor

<a>

HtmlSelect

<select>

HtmlTextArea

<textarea>

HtmlButton

<input type=button> <input type=submit>

HtmlCheckBox

<input type=checkbox>

HtmlRadio

<input type=radio>

HtmlHidden

<input type=hidden>

HtmlInputText

<input type=text>

HtmlInputFile

<input type=file>

HtmlForm

<form>

HtmlImage

<img>

HtmlTable

<table>

HtmlTableRow
HtmlTableCell
<tr>
<td>

HtmlGenericControl

All other unmapped tags such as <div> and <p>

Handling Events on the Server

In our example, clicking the Submit button sends a request to the server to calculate and return a BMI value based on the form's content. The .aspx code for the button looks like this:

<INPUT TYPE="button" VALUE="Submit Form"
      id=bmiButton runat=server>

Compare this with the tag defining the button in Listing 16-2:

<INPUT TYPE="button" VALUE="Submit Form" OnServerClick="getBMI"
      id=bmiButton runat=server>

This earlier code defines a method (getBMI) to be called when the click event occurs. Because our current example has the method placed in a code-behind file, there is no reference to it in the .aspx file. Instead, the server-side code handles the event using a standard delegate-based event handling approach. An event handler method (getBMI) is defined that matches the signature of the EventHandler delegate. Then, using the button's id value, we create a delegate instance that registers the method for the ServerClick event of the bmiButton control:

bmiButton.ServerClick += new EventHandler(getBMI);

When the button is clicked on the browser, the contents of the form are posted to the server, which recognizes the button click event and calls the appropriate event handler. This raises one obvious question: Because a form can contain any number of buttons, how does the server determine the event that triggered the post-back? The answer is that the name of the control causing the event is passed to the server in the __EVENTTARGET hidden field that was discussed earlier. This is handled automatically by ASP.NET; the developer's responsibility is to create the delegate and server-side control event handler.

Code-Behind with Partial Classes

The problem with the preceding code-behind model is that each control in the markup page must be explicitly mapped to a protected member in the code page. Changes to the markup page require that the code page members be kept in sync. The use of a partial class in the code-behind page eliminates the need for the protected class member. The partial class is compiled with the markup page, which permits the markup and code sections to directly access each other's members. The effect is the same as using the inline model. To demonstrate this, extract the getBMI method from the inline code (refer to Listing 16-2) and place it in its own file inside a partial class. The result is a code-behind partial class as shown in Listing 16-4.

Example 16-4. Code-Behind Partial Class File for BMI Calculator

//file: bmi.aspx.cs
using System;
partial class BMICalc: System.Web.UI.Page{}
{
   void getBMI (Object sender, EventArgs e)
   {
      try
      {
         decimal f =  Convert.ToDecimal(htf.Value);
         decimal inch = Convert.ToDecimal(hti.Value);
         decimal w = Convert.ToDecimal(wt.Value);
         decimal totinches = f * 12 + inch;
         decimal h2 = totinches * totinches;
         decimal massIndex = (w * 703 * 10/ h2)/10;
         bmi.Value = massIndex.ToString("##.##");
      } catch (Exception ex)
         { bmi.Value=" "; }
   }
}

The client markup page links to this code by specifying the file name and the partial class name in its @Page declaration:

<%@ Page codefile="bmi.aspx.cs" inherits="BMICalc" %>

ASP.NET 2.0 continues to support the original code-behind model, but it should be used only for preexisting code. New development should employ the inline or partial class model.

Page Class

The first time an ASP.NET page is accessed, it is parsed and compiled into an assembly. This is a relatively slow process that results in the delay one notices the first time the .aspx page is called. Subsequent requests receive much faster responses because the assembly handles them. This assembly consists of a single class that contains all of the server-side code, as well as static HTML code.

The compiled class derives either directly from the System.Web.UI.Page class or indirectly via an intermediate code-behind class. It is important to understand the members of the Page class. Its methods and properties define much of the functionality the .aspx code relies on for handling requests, and its events define junctures at which the code must perform initialization and housekeeping tasks.

Table 16-3 summarizes some of the important properties of the Page class.

Table 16-3. Selected Properties of the Page Class

Control

HTML Tag

Application

Returns the HttpApplicationState object that contains information about the executing application.

EnableViewState

Boolean value that indicates whether controls retain their values between requests. Default is true.

IsPostBack

Boolean value indicating whether the page is being loaded and accessed for the first time or in response to a postback.

PreviousPage

Provides a reference to the page originating the call. Information on the calling page is available when the page is reached by cross-page posting or is invoked by HttpUtilityServer.Transfer().

Request

Gets the HttpRequest object that provides access to data contained in the current request.

Response

Gets the HttpResponse object that is used to programmatically send HTTP responses to the client.

Server

Gets the HttpServerUtility object provided by the HTTP runtime.

Session

Gets the HttpSessionState object, which provides information about the state of the current session.

The Application and Session properties provide state information for a Web application and are discussed in the next chapter.

HttpRequest and HttpResponse Objects

The Request and Response properties expose underlying objects (HttpRequest and HttpResponse) that are used to interact with the incoming HTTP request and the outgoing HTTP response. These classes include properties and methods to read cookies from the client's machine, receive uploaded files, identify the browser, get the IP address of the client, and insert custom content into the outgoing HTTP body. Chapter 17 discusses these in detail, but as an introduction, let's look a simple code example that illustrates the fundamentals of accessing these objects within an .aspx page.

The next code segment uses the Response.Output.Write method to write information into the HTTP content returned to a browser. The information comes from Request.Browser properties that indicate the type of client. Note that by using <% %> tags in an .aspx file, we can intersperse these statements with the HTML code. Do this judiciously. An .aspx file is much easier to read when the C# code is segregated between <script> tags, as shown in Listing 16-2, or placed in a code-behind file.

<table border=0 cellpadding=0 cellspacing=0>
   <%
      Response.Output.Write(@"<tr><td>Browser Version: {0}
           </td></tr>", Request.Browser.Type); //  IE6
   %>
</table>

Using IsPostBack

This useful property enables a Web application to distinguish between a postback and the first time a Web page is invoked. It lets the program know when to initialize values that only need to be set once. A typical example is a ListBox that contains unchanging values, such as all of the states in the United States. When assigned, these values are subsequently maintained in the __VIEWSTATE hidden field and do not need to be re-initialized.

To demonstrate, let's extend our BMI inline code example to display the date and time when the user first requests the Web page. When displayed, this date and time remain unchanged no matter how many times the BMI calculation is subsequently requested.

The date and time are displayed using a <span/> tag, which is added beneath the opening <FORM> tag (refer to Figure 16-2) in the bmi.aspx file. (This is equivalent to a Web control Label.)

<FORM NAME="bmi_input" runat=server>
<span id=sessionstart runat=server/><br>

In the code section, we must assign the date and time to the inner contents of the <span> tag the first time that the page is called. Here is the code to do this:

void Page_Load(object sender, EventArgs e)
{
   if(!this.IsPostBack) sessionstart.InnerText =
         DateTime.Now.ToString();
}

Recall that the code on the server is compiled into a class that inherits from the Page class. This class includes several events—discussed in the following section—that are triggered when a request is sent to the Web page. The most useful is the Page_Load event that is raised each time the page is loaded. Applications typically include an event hander for it that checks IsPostBack and performs initialization if the call is not a postback. In this example, the InnerText field of the <span> tags is set to the date and time the first time the page is loaded.

Page Events

The preceding example demonstrates how the Page_Load event handler provides a convenient place to initialize variables. The Load event is only one of four events defined by the System.Web.UI.Page class. The others are Init, PreRender, and UnLoad. These occur in a fixed sequence as shown in Figure 16-6.

System.Web.UI.Page events

Figure 16-6. System.Web.UI.Page events

To best understand the role of each, let's look at what happens when a form is posted to a server:

  1. The temporary class for the page is generated, compiled, and loaded. The ASP.NET runtime begins processing the page and fires the Page.Init event.

  2. The state of the page object and its controls are set from data in the POST variables and the __VIEWSTATE field. The Page.Load event is fired. Typically, the OnLoad event handler is overridden to initialize control values and establish database connections.

  3. Events related to all controls are fired. The last event that occurs is the one that caused the postback. After all server-side control events have been processed, the Page.PreRender event occurs.

  4. The page enters its rendering phase. The class that represents the page calls the Render method on all the individual controls included in the page.

  5. The HTTPResponse is issued, sending the rendered HTML to the client. The Page.Unload event completes the process. This event should be used to release resources by closing files and database connections.

Although Web Forms provide an event-based model that is similar to the Windows Forms model, the key difference is that the events do not actually fire until control returns to the server. There they must occur in the fixed order described in this section. Your code interacts with these events by overriding or replacing the event handlers for the events described in Figure 16-6.

Cross-Page Posting

A Web page in .NET 1.x could only post to itself. In 2.0, you can designate a target page, other than the current page, by setting the PostBackUrl property of a Button, ImageButton, or LinkButton to the address of the target page. Posting occurs when the button is clicked. To demonstrate, the button defined in this code posts the contents of the current form to nextstep.aspx when the button is clicked.

<asp:button id="postBtn"
   text="redirect"
   postbackurl="nextstep.aspx"
   runat="Server">
</asp:button>

The page to which a form is posted can view the contents of the calling form using the PreviousPage property. In this example, nextstep.aspx uses PreviousPage to get the text value of the button that initiated the post. Of course, this technique would be used more often to gather data from text boxes on the posted form.

if(!this.IsPostBack)
{
   // Sets text to the value of the Text property of the button
   // from the calling form having the id postBtn: "redirect"
   string text =
         ((Button)PreviousPage.FindControl("postBtn")).Text;
}

Web Forms Controls

The designer of an ASP.NET Web page can choose from three types of controls for the GUI design: client-side HTML controls, HTML server controls, and Web Forms controls. Client-side controls are the traditional controls defined by HTML tags (<table>, <button>, <input>, and others). These controls are still useful for client-side form validation and creating hyperlinks.

HTML server controls are server-side versions of the standard HTML controls (see Listing 16-2). As we have seen, they're created by adding a runat = server attribute to the HTML tag. The main reason they are included in .NET is to ease the transition from legacy HTML files to ASP.NET. However, except for incurring less overhead, there is no real advantage in using HTML server controls. Web controls—a much more powerful alternative—are nearly as easy to implement and unlock the full capabilities of ASP.NET.

Web controls are native ASP.NET controls that extend the features of the HTML server controls and add non-HTML controls, such as a calendar and data grid. They are actual classes in the .NET Framework and expose properties that enable the developer to exert much more control over their appearance and behavior than is possible with server controls. With the exception of the Table—the HTML table is easier to work with for general use—Web controls should be the control of choice for ASP.NET pages.

Web Controls Overview

Web controls behave much like the HTML server controls: Each has a corresponding class; those with a user interface render themselves in HTML understandable by a browser; and most expose events that are handled on the server. A significant difference from the HTML controls is that Web controls provide a richer object model. From the WebControl class, they inherit properties that affect their appearance: ForeColor, BackColor, BorderColor, BorderStyle, Height, and Width. In addition, many can be bound to a data source that provides their content.

A Web control is declared using the asp: prefix. For example, a TextBox and Button are declared as

<asp:TextBox id="First Name" type="text" runat="server" />
<asp:Button  id="Save" Text="Save Data" runat="server"
      OnClick="SaveBtn_Click" />

ASP.NET Web controls are defined in the System.Web.UI.WebControls namespace. There are a lot of them—more than 70 with the release of ASP.NET 2.0. They naturally fall into functional categories that provide a convenient way to present them (see Figure 16-7):

  • Simple controls. Enhanced alternatives to the HTML Server controls and standard HTML tags.

  • List controls. These inherit directly from the System.Web.UI.WebControls.ListControl class and are populated by ListItem objects.

  • Data Display controls. These complex controls are designed to display multiple rows of data (GridView, DataList, Repeater) or multiple fields for a single row of data (FormView, DetailsView).

  • Data Source controls. Introduced with ADO.NET 2.0, these controls serve as source of data for other controls—primarily the data display controls—that bind to them. As their names imply, these controls provide data from a variety of sources.

  • Validation controls. These helper controls perform predefined and custom data validation on other controls.

  • Login controls. A set of controls designed to control the login process by managing and authenticating users.

ASP.NET Web controls

Figure 16-7. ASP.NET Web controls

In addition to these, there are a few of highly specialized controls: Calendar, AdRotator, and FileUpload. This section provides examples of commonly used visual controls, as well Validation controls and the new (.NET 2.0) Data Source controls. Before examining specific controls, let's look at the properties shared by all Web controls that govern their appearance.

Specifying the Appearance of a Web Control

All Web controls inherit numerous properties from the base WebControl class that can be used to set their appearance and behavior. The properties are most effective when used to decorate simple controls where their effect is more pronounced than with complex controls. Table 16-4 demonstrates how these properties can be used to alter the appearance of a simple Button control.

Table 16-4. Properties That Affect the Appearance of a Web Control

Property

Button1

Button2

Button3

Width

80

100

100

Height

20

20

24

BackColor

#ffffff

#efefe4

#cccccc

BorderStyle

Double

Dotted

None

BorderWidth

1

1

1

BorderColor

Red

Black

Black

Font-Names

Sans-Serif

Sans-Serif

Courier

Font-Size

8pt

10pt

11pt

Font-Bold

true

true

true

Button
Displayed
Properties That Affect the Appearance of a Web ControlProperties That Affect the Appearance of a Web ControlProperties That Affect the Appearance of a Web Control

Other useful properties include Enabled, Visible, and TabIndex. The latter indicates the tab order of a control. Note that browsers may render these properties differently or ignore some of them altogether. For example, Firefox and Netscape tend to ignore the Width property.

Simple Controls

The simple controls are typically used in combination to create an interface for users to enter data or make selections. The easiest way to present and manage controls that are related by function and style is to place them in a common container. In traditional HTML, the <DIV> element is used for this purpose; in ASP.NET, the panel control serves as a generic container (and is often rendered in browsers as a <DIV> element). There are many advantages to using a panel to layout controls:

  • It eliminates the need for multiple Web pages. Because ASP.NET is designed around posting back to the calling Web page, it's an easy way for a single Web page to present multiple interfaces by simply toggling panels' visibility on or off.

  • Controls on a panel maintain data that can be referenced even if the panel is not visible. This eliminates the use of multiple hidden fields.

  • It serves to unify the appearance of grouped controls by providing a common background or border. A screen is easily sectioned into multiple panels.

The screens in Figure 16-8 are created using a combination of button, label, text box, and panel controls. The page consists of a form that accepts name and address information. Controls to accept name fields are contained on one panel, whereas address fields are on another. Clicking the Name and Address buttons toggles the visibility property of the two panels. The effect is to reuse the same display space for both types of data—obviating the need for a long scrolling form or multiple Web pages.

Using panels to manage simple controls
Using panels to manage simple controls

Figure 16-8. Using panels to manage simple controls

A look at the underlying code reveals the syntax and mechanics of working with Web controls. We'll look at the three most interesting areas of the code: the Page_Load event handler, the button declarations and event handlers, and the layout of the panels.

Using the Page_Load Event to Initialize the Screen

When the page is loaded for the first time, the panel that accepts name fields, pnlName, is made visible, whereas pnlAddress has its visibility turned off.

<script  runat="Server">
   void Page_Load(object sender, EventArgs e)
{
   if(!this.IsPostBack) {
      pnlName.Visible = true;
      pnlAddress.Visible = false;
   }
}

Buttons

The two menu buttons across the top of the form are declared as

<asp:Button ID="btnName" CommandName="name"
      OnCommand="Button_Command" text="Name" Runat="server" />
<asp:Button ID="btnAddress" CommandName="address"
      OnCommand="Button_Command" text="Address" Runat="server" />

The buttons specify Button_Command as an event handler to be called when each button is clicked. As shown in the event handler code, the buttons' CommandName property identifies the button raising the event. This enables a single method to handle multiple button clicks, which is a useful way to group code performing similar tasks. It's most commonly used with buttons that control column sorting in a grid.

void  Button_Command(Object sender, CommandEventArgs e) {
   // CommandName identifies control invoking event
   switch(e.CommandName)
   {
      case "address":
         pnlName.Visible = false;
         pnlAddress.Visible = true;
         break;
      case "name":
          pnlAddress.Visible= false;
          pnlName.Visible = true;
          break;
      default:
          break;
   }
}

The Clear and Submit buttons use the OnClick property—rather than OnCommand—to specify their Click event handlers as separate methods:

<asp:Panel id="pnlBottom"
   style="Z-INDEX:103; LEFT:20px;POSITION:absolute; TOP: 240px"
      runat="server"
      BackColor=#cccccc
      Height="26px"
      Width="278px">
   &nbsp;
   <asp:Button id="btnClear" Text="Clear"  OnClick="clear_Form"
      runat="server"  />
   &nbsp;&nbsp;
   <asp:Button ID="btnSubmit" Text="Submit" Font-Bold="true"
       OnClick="store_Form" runat="server" />
</asp:Panel>

The most important thing to note about the event handlers is their signature. The Click event requires an EventArgs type as the second parameter; the Command event requires a CommandEventArgs type.

private void clear_Form(object sender, System.EventArgs e)
{
   if(pnlName.Visible)
   {
      txtFirstName.Text  ="";
      txtLastName.Text   ="";
      txtMiddleName.Text ="";
   } else
   {
      // Clear fields on pnlAddress
   }
}
private void store_Form(object sender, System.EventArgs e)
{
   // Code to verify and store Form
}

Core Note

Core Note

ASP.NET 2.0 adds an OnClientClick property to the Button, ImageButton, and LinkButton controls, which can be used to execute client-side script. The following code causes a JavaScript function to be executed when the button is clicked.

<asp:Button id=btn text="Client Click" OnClientClick="popup()"
   runat="server" />

Using Panels

The panel declaration specifies its location, size, and appearance attributes such as background color. Controls are placed on the panel by declaring them within the <asp:Panel /> tags. In this example, the text box and label controls are members of the panel's control collection:

<asp:Panel id="pnlName" runat="server"
   style="Z-INDEX: 101; LEFT: 20px;
   POSITION: absolute; TOP: 64px"
   BackColor = "LightGreen" Height="160px" Width="278px">
<TABLE>
   <TR>
      <TD><asp:Label id="lblFirstName" Runat="server"
              text="First Name:"></asp:Label></TD>
      <TD><asp:TextBox id="txtFirstName" MaxLength=30
              Runat="server"></asp:TextBox></TD></TR>
   <TR>
      <TD><asp:Label id="lblMiddleName" Runat="server"
              text="Middle Name:"></asp:Label></TD>
      <TD><asp:TextBox id="txtMiddleName" MaxLength=30
              Runat="server"></asp:TextBox></TD></TR>
   <TR>
      <TD><asp:Label id="lblLastName" Runat="server"
              text="Last Name:"></asp:Label></TD>
      <TD><asp:TextBox id="txtLastName" MaxLength=30
              Runat="server"></asp:TextBox></TD></TR>
</TABLE>
</asp:Panel>

It is interesting to note how ASP.NET tailors the HTML code to capabilities of the client browser. The HTML returned to Internet Explorer renders the panel as a <div> element.

<div id="pnlName" style="background-color:LightGreen;
      height:160px;width:278px;Z-INDEX: 103; LEFT: 20px;
      POSITION: absolute; TOP: 64px">

The Firefox and Netscape browsers receive HTML in which the panel is rendered as a table:

<table id="pnlName" cellpadding="0" cellspacing="0" border="0"
      bgcolor="LightGreen" height="160" width="278"
      style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute;
      TOP: 64px"><tr><td>

Core Note

Core Note

ASP.NET 2.0 adds a ScrollBars property to the panel control. It can be set to Vertical, Horizontal, or Both. Beware that not all browsers support this feature.

Text Box

With the exception of the MaxLength property that limits the amount of text entered in the control, the text box controls in this example rely on default property values. However, they can be customized easily to present a more meaningful interface. The most useful properties include Width and Column, which both specify the width of the control (Column is the better choice since all browsers recognize it); ReadOnly, which can be used to prevent the user from changing the content; Rows, which specifies the number of rows in a multi-line text box; and TextMode, which indicates whether the text box is SingleLine, MultiLine, or contains a Password. In the latter case, text entered into the text box is masked.

The text box also supports an OnTextChanged property that specifies an event handler method to call when text in the box is changed. However, this is a delayed event that is not processed on the server until a round-trip event occurs. You can prevent the event from being delayed by adding the AutoPostBack = true property to the control's declaration. However, for best performance, the program should be designed to process all control content in one trip.

List Controls

The four ASP.NET list controls—DropDownBox, ListBox, CheckBoxList, and RadioButtonList—provide alternative ways of representing a collection of data. All are data-bound controls that provide a visual interface for an underlying ListItemCollection; and all are derived from the System.Web.UI.WebControls.ListControl class that contributes the properties and methods used to populate the controls with data and detect selected control items (see Figure 16-9).

List control display data from underlying ListItem collection

Figure 16-9. List control display data from underlying ListItem collection

Filling the Control with Data

Individual data items within a list control are represented as a ListItem control. There are several ways to specify the text displayed for an item in the list control. The most common method is by placing text between the opening and closing tags of the ListItem control—referred to as the inner HTML content. You can also use the Text property to specify the text displayed in the list control for the item.

<asp:RadioButtonList id="RadioButtonList" BackColor="LightBlue"
       RepeatColumns=1 RepeatDirection="Vertical" runat="server">
   <asp:ListItem Value="1">Rembrandt />
   <asp:ListItem Value="2">Courbet />
   <asp:ListItem Value="3" Text="Manet" />
   <asp:ListItem Value="4">Degas />
</asp:RadioButtonList>

The ListItem control also exposes a Value property that allows you to associate a value with the item in the list control, in addition to the text displayed in the control. This is often used as a key when retrieving data related to the selected item from a database table.

As mentioned, list controls are data bound, which means they expose a DataSource property that can be set to reference a collection of data. The control's DataBind method is called to load the data from the source into the control. It is possible to bind to many kinds of data sources, such as database tables, hash tables, XML files, and even other controls.

As an example, let's declare a ListBox that will be bound to an array:

<asp:ListBox id="ListBox" runat="server" Rows=4 Width=150px>
</asp:ListBox>

In the script section of the .aspx page, we define an array and bind its contents to the ListBox. The DataBind method copies the data from the array into the control, creating a ListItem collection (see Figure 16-9).

<script  runat="Server">
void Page_Load(object sender, EventArgs e) {
   string[] artists = new string[4] {"Rembrandt","Courbet",
                   "Manet","Degas"};
   //
   if( !this.IsPostBack) {
      // Bind the first time the page is loaded
      ListBox.DataSource = artists;
      ListBox.DataBind();
   }
}

Note that the binding occurs only when the page is loaded the first time. ViewState is used to populate the control on subsequent postbacks.

Selecting an Item

When an item in a list control is selected, the SelectedIndexChanged event occurs. A postback to the server occurs only if the control specifies an event handler to be called, and sets the AutoPostBack attribute to true.

<asp:ListBox id="ListBox" runat="server"
   SelectionMode="Multiple" Rows=4
   AutoPostBack="true"
   OnSelectedIndexChanged="ShowSelections"
   Width=150px>
</asp:ListBox>

The event handler declaration has the familiar parameters of the EventHandler delegate declaration.

public void ShowSelections(Object sender, EventArgs e) {
   Label1.Text="";
   foreach (ListItem item in ListBox.Items)
   {
      if(item.Selected)
      {
         Label1.Text += item.Text + "<br>";
      }
   }
}

The event handler iterates through the items in the ListBox and displays those that are selected. By default, the ListBox permits only one item to be selected, but this can be overridden by setting SelectionMode to Multiple.

A single selected item is available through three properties: SelectedIndex returns the index of a selected item in the list; SelectedItem returns the entire item; and SelectedValue returns the value of the selected item. If there are multiple selected items, these properties return information on the selected item having the lowest index.

The DataList Control

The DataList control makes it easy to display data in a repeating pattern. The idea is to create a template that defines the layout for items it contains. Each “item” is defined as a mixture of visual Web controls and HTML. Item content comes from one or more fields in a data source. When an instance of the DataList control is set to a data source, such as a DataReader, each row of the data source is represented as an item in the DataList. The DataList is processed much like a ListBox control: Items.Count provides the number of items in the control, and the SelectedIndex and SelectedItem properties reference a specific item.

To illustrate the use of this control, let's create a Web page that lists DVDs for sale. The items for sale are displayed in two-column rows as shown in Figure 16-10. The row/column layout is specified in the DataList declaration:

<ASP:DataList id="MyDataList" RepeatColumns="2"
   RepeatDirection="Horizontal" OnItemCommand="Item_Command"
   runat="server">
DataList control is used to display items in repeated format

Figure 16-10. DataList control is used to display items in repeated format

The RepeatColumns property specifies the number of columns displayed, and RepeatDirection indicates the direction in which items are displayed. OnItemCommand specifies the method to be called when a button in the DataList control is clicked. In this example, the ItemCommand event fires when the user adds an item to the cart. Here is an example of the code to handle this event:

private void Item_Command(object source,
       System.Web.UI.WebControls.DataListCommandEventArgs e)
{
   // (1) Crucial: select an item in the DataList
   MyDataList.SelectedIndex = e.Item.ItemIndex;
   // (2) Get the value of a control in the selected item
   string id= ((Label)
        MyDataList.SelectedItem.FindControl("movID")).Text;
}

An item selected in the DataList is indexed by the SelectedIndex property. After an item is selected, the FindControl method can be used to obtain a reference to any control in the item definition. In this case, the value of the Label containing the movie's ID is assigned to a variable.

The ItemTemplate shown in Listing 16-5 describes the appearance of each item. The most important thing to note in this code is the use of DataBinder.Eval to bind to a property or column in the data source. As shown here, the method has two overloads, one of which takes a formatting parameter.

<%# DataBinder.Eval(Container.DataItem, "movie_ID") %>
<%# DataBinder.Eval(Container.DataItem,"r_price", "  {0:c2}") %>

Note that ASP.NET 2.0 offers a simpler, but equivalent version:

<%# Eval("movie_ID") %>

Either construct is replaced by the corresponding data from the data source. It can be displayed directly in the HTML stream or assigned to a control's property.

Example 16-5. Defining an ItemTemplate for a DataList Control

<ItemTemplate>
   <table cellpadding=10 style="font: 10pt verdana" width=380>
      <tr>
         <td width=1 bgcolor="00000"/>
         <td valign="top">
         <a href=showdvd.aspx?id=<%# DataBinder.Eval(
            Container.DataItem, "movie_ID") %>>
         <img align="top" border=0
            src='./images/<%# DataBinder.Eval(
            Container.DataItem, "movie_ImgID") %>' ></a>
         </td>
         <td valign="top">
         <b>Title: </b><%# DataBinder.Eval(
               Container.DataItem, "movie_title") %><br>
         <b>ID: </b>
            <asp:Label id="movID"
               Text=<%# DataBinder.Eval(Container.DataItem,
                  "movie_ID") %>
               runat="server" >
            </asp:Label></b><br>
         <b>Year:  </b><%# DataBinder.Eval(
               Container.DataItem, "movie_Year") %><br>
         <b>Price: </b><%# DataBinder.Eval(Container.DataItem,
               "r_price", "  {0:c2}") %><br>
         <%# DataBinder.Eval(Container.DataItem,
               "movie_shortdesc") %> <br>
         <asp:Button  Value="Select"  Text="Add to Cart"
               BackColor="#bda563"
               CommandName="cart"
               runat=server>
         </asp:Button> <br>
         </td>
      </tr>
   </table>
</ItemTemplate>

The easiest part of working with a DataList is binding it to a data source. In this example, we use a DataReader to load rows from a database. To display these rows as items in the control, set the DataSource to the reader and then bind it:

rdr= cmd.ExecuteReader();
MyDataList.DataSource= rdr;
MyDataList.DataBind();

The contents of the DataList can be changed at any time by reassigning and binding a new data source.

Core Note

Core Note

DataBinder.Eval and Eval use reflection to perform late-bound evaluation. The advantage of this approach is that it can determine the data type (making casting unnecessary) and has a simple formatting syntax. The disadvantage is that it is much slower than an early-bound approach. As an alternative—particularly when formatting is not needed—consider this early-bound syntax:

<%# ((IDataRecord)Container.DataItem)["movie_ID"] %>

IDataRecord casting is used when a DataReader is the data source. Cast with DataRowView when the data source is a DataSet.

In summary, the DataList acts as composite control that can be configured to display data in just about any format. In addition to the ItemTemplate, it supports header, footer, edit, and select templates that can be used to expand its capabilities beyond those described in this section.

Data Binding and Data Source Controls

Data binding enables the contents of a control to be populated by data from a designated data source. Technically speaking, the data source is any collection of data that implements the IEnumerable interface. In simple cases, the source may be an object such as an Array, ArrayList, Hashtable, or SortedList. More often, the data comes from an ADO.NET DataTable, DataSet, or IDataReader object. The control may bind directly to one of these objects, or indirectly, using the special data source controls introduced with ASP.NET 2.0.

Binding to a DataReader

The data reader provides a forward-only, read-only resultset that is the most efficient way to present data. It is generated by issuing the ExecuteReader method of the IDbCommand object. A control binds to the results by setting its DataSource property to the data reader object and executing its DataBind method.

The example in Listing 16-6 illustrates how a data reader is used to populate a ListBox with movie titles from the Films database (described in Chapter 10). The values displayed in the ListBox are based on the value assigned to the DataTextField property—in this case, the Movie_Title column in the movies table. The DataValueField property specifies an additional column value that is assigned to each item in the ListBox. When an item is selected, this latter value is returned by the SelectedValue property.

Example 16-6. Data Binding a List Box to a Data Reader

<%@ Page Language="C#" %>
<%@Import namespace="System.Data.SqlClient" %>
<%@Import namespace="System.Data" %>
<html>
<body>
<head><TITLE>Bind Films data to a ListBox</TITLE>
<script  runat="Server">
   void Page_Load(object sender, EventArgs e) {
      if(!this.IsPostBack) {
        getMovies();
      }
   }
   private void getMovies()
   {
      string cnstr=GetConnString(); // Get a connection string
      SqlConnection conn = new SqlConnection(cnstr);
      IDataReader rdr=null;
      IDbCommand cmd = new SqlCommand();
      cmd.Connection= conn;
      conn.Open();
      cmd.Connection=conn;
      cmd.CommandText="SELECT movie_id, movie_title FROM movies
          ORDER BY AFIRank";
      conn.Open();
      rdr = cmd.ExecuteReader();
      // Bind DataReader to ListBox
      ListBoxMovie.DataSource = rdr;
      ListBoxMovie.DataBind();
      conn.Close();
      rdr.Close();
   }
   </script>
   </head>
   <FORM NAME="FORM1" runat=server>
   <asp:ListBox
      id="ListBoxMovie"
      dataValueField = "movie_ID"
      dataTextField  = "movie_title"
      AppendDataBoundItems=true
      Rows="10"
      BackColor=#efefe4
      font-size=9pt
      runat="server" >
    <asp:ListItem Value=-1 Text="Select Movie" />
   </asp:ListBox>
</FORM>
</body>
</html>

Observe the presence of the AppendDataBoundItems property in the ListBox declaration. This property, a new feature added by ASP.NET 2.0, provides a way to specify whether data binding should overwrite any preexisting items in a list control. This is particularly useful for placing an entry in a list that precedes the actual data. In the preceding example, the ListBox declaration includes a ListItem that causes Select Movie to be placed in it first row.

Binding to a DataSet

It is often preferable to bind a server control to a data reader, rather than to a DataSet. To understand why, let's compare the two (see Figure 16-11). The data reader connects to a table and streams data into the control when the DataBind method is called. A data set, on the other hand, is a cache in memory that is filled with the resultset when the DataAdapter.Fill method is invoked. Its contents are then copied into the control when the control's DataBind method is called. In a WinForms application, the data set remains available to the application until it is closed; in a Web application, the data set disappears after a reply is sent to the browser. As we discuss in the next chapter, it can be saved as a Session variable, but this requires memory and can lead to scalability problems.

DataReader versus DataSet as a control's DataSource

Figure 16-11. DataReader versus DataSet as a control's DataSource

There are a couple of situations in which using a DataSet makes sense. One is when multiple controls are bound to the contents of a DataSet. In this code segment, a data set is built that contains a list of all the movies in a table. Two ListBox controls are then populated with different views of the data. One ListBox contains movies produced prior to 1951, and the other contains those produced on or after that year. The advantage of the DataSet in this case is that only one query is applied against the database.

string sql ="SELECT movie_id, movie_title,movie_year FROM movies
      ORDER BY movie_year";
SqlDataAdapter da = new SqlDataAdapter(sql,conn);
DataSet ds = new DataSet();
da.Fill(ds,"Movies");  // DataSet and DataTable
DataView dview = new DataView(ds.Tables["Movies"]);
dview.RowFilter = "movie_year < 1951";
// List box containing movies before 1951
ListBoxMovie.DataSource= dview;
ListBoxMovie.DataBind();
// List box containing movies produced after 1950
dview.RowFilter = "vendor_name > 1950";
ListBoxMovie2.DataSource= dview;
ListBoxMovie2.DataBind();

A DataSet is also useful when the Web application is designed as a three-tier Web site in which the presentation layer accesses a database through an intermediate data access layer. The data access layer contains a method or methods that return a DataSet in response to the call.

Let's look at how the .aspx file in Listing 16-6 can be converted from its current two-tier structure to a three-tier design. The first step is to remove the getMovies method and place it in a separate assembly. In the following code, the method is part of the DataMethods class and rewritten to return a data set containing the vendor data. This code is compiled into a DLL file and placed in the bin subdirectory below the Web page.

// datalayer.dll – place in in subdirectory below application
using System.Data.SqlClient;
using System.Data;
namespace myUtil{
   public class DataMethods{
      public DataSet getMovies(){
        string cnstr= GetConnString(); // Get a connection string
         SqlConnection conn = new SqlConnection(cnstr);
         IDbCommand cmd = new SqlCommand();
         cmd.Connection= conn;
         string sql="SELECT movie_id, movie_title FROM
               movies ORDER BY AFIRank";
         SqlDataAdapter da = new SqlDataAdapter(sql,conn);
         DataSet ds = new DataSet();
         da.Fill(ds,"Movies");
         return (ds);
      }
   }
}

The code changes in the .aspx file are minimal. An @Import directive is added so that the namespace in datalayer.dll can be accessed.

<%@Import namespace="myUtil" %>

The getMovies call is replaced with the following:

if(!this.IsPostBack) {
   DataMethods dm = new DataMethods();
   DataSet ds = dm.getMovies();
   ListBoxMovie.DataSource = ds;
   ListBoxMovie.DataBind();
}

The use of a data presentation layer promotes code reusability, hides the messy ADO.NET connection details, and results in cleaner code.

DataSource Controls

As we have seen, ASP.NET makes it easy to bind a control to data by simply setting the control's DataSource property to the collection of data it is to display. However, it is still up to the developer to assemble the collection of data. For example, using the data reader as a data source requires the following pattern of operations:

  1. Create a data connection.

  2. Create a Command object.

  3. Build a query and use the Command object to retrieve data into a data reader.

Data source controls encapsulate the functionality required to perform these operations—eliminating the need for coding by the developer. The data-bound control is no longer bound to the data collection, but to a data source control. To illustrate, Figure 16-12 shows how a grid can be populated by binding it to a data reader or a SqlDataSource control.

Comparison of data binding using ADO.NET code versus DataSource control

Figure 16-12. Comparison of data binding using ADO.NET code versus DataSource control

Data controls are not limited to database access. In fact, ASP.NET 2.0 supports data source controls that attach to six types of data:

  • AccessDataSourceBinds to a Microsoft Access database.

  • DataSetDataSourceBinds to non-hierarchical XML data.

  • ObjectDataSourceBinds to data through custom classes implemented in a data access layer.

  • SiteMapdataSourceBinds to XML site maps.

  • SqlDataSourceBinds to a SQL database.

  • XmlDataSourceBinds to XML documents.

We'll look at the SqlDataSource, ObjectDataSource, and XmlDataSource controls in this section.

SqlDataSource Control

This control represents a connection to a relational data store, such as SQL Server, DB2, or Oracle. It requires a .NET managed data provider with the capability to return a SQL resultset.

The SqlDataSource control is declared using standard Web control syntax.

Control Declaration:

<asp:sqldatasource  runat="server" id="controlID"

Properties:

ConnectionString

Connection string to access database.

ProviderName

Managed provider. Default is SqlClient.

DataSourceMode

Controls how the select command retrieves data.

Is a SqlDataSourceMode enumeration: DataSet or DataReader. Default is DataSet.

EnableCaching

True or false. Default is false. Can only be used if DataSourceMode is DataSet.

CacheDuration

How long the contents of the data source aremaintained in memory. Value is in seconds.

SelectCommand

SQL statement that retrieves data from associated data store.

DeleteCommand

SQL statement to delete row(s) from data store.

InsertCommand

SQL statement to insert row(s) into data store.

UpdateCommand

SQL statement to update row(s) in data store.

The four command properties are strings that contain either a SQL command or the name of a stored procedure (if the database supports it) to be executed. Each command can contain parameters whose values are defined by an associated collection of parameters. In an upcoming example, we'll see how the SelectParameters collection is used with SelectCommand. Before that, let's look at how the SqlDataControl is used to populate a Web control.

As in our earlier example, Listing 16-7 fills a ListBox with the name of movies from the Films database. However, in place of raw ADO.NET coding, it defines a data source control and assigns to its SelectCommand property a SQL select string that retrieves a list of movies from the database. A ListBox control is declared with its DataSourceID property set to the ID of the data source control. When the page is loaded, the list control is populated with the resultset.

Example 16-7. Binding a ListBox to a SqlDataSource Control

<%@ Page Language="C#"  %>
<%@Import namespace="System.Data.SqlClient" %>
<%@Import namespace="System.Data" %>
<HTML>
<HEAD><TITLE>Using a DataSource Control</TITLE>
</HEAD>
<body>
<form id="Form1" runat="server">
   <asp:sqldatasource runat="server" id="SqlDataSource1"
      connectionstring="SERVER=(local);DATABASE=FILMS;
      Integrated Security=SSPI; "
      providername = "System.Data.SqlClient"
      selectCommand= "SELECT movie_ID, movie_Title FROM movies
                        ORDER BY AFIRank" >
   </asp:sqldatasource>
   <table border=0>
   <tr><td>
   <asp:ListBox runat="server" id="ListBoxMovie"
      dataSourceid   = "SqlDataSource1"
      dataValueField = "movie_ID"
      dataTtextField = "movie_Title" />
   </td></tr>
   </table>
</form>
</body>
</html>

Let's extend this example so that when a movie is selected from the list, its cast members are displayed. To do this, we add a GridView, as shown in Figure 16-13.

Using data source controls to depict a parent-child relationship

Figure 16-13. Using data source controls to depict a parent-child relationship

The data for the GridView also comes from a new data source control. The purpose of this control is to dynamically retrieve a list of actors for any movie selected in the ListBox. The challenge is to identify the movie selected and to specify it in the query. Here's how it's done.

The query assigned to the SelectCommand property contains a parameter (@movieID) that serves as a placeholder for the actual movie ID. Within the data source control is another control, ControlParameter, that has the same name as the parameter in our query. It links the data source control to the ListBox via two properties: ControlID, which specifies the ListBox ID, and PropertyName, which specifies the value to assign to the query parameter. When an item in the ListBox is selected, ASP.NET replaces the parameter with the current SelectedValue of ListBoxMovie (which is a movie ID) and executes the query to retrieve the actor data.

<asp:SqlDataSource ID="ActorSource" RunAt="server"
   connectionstring="SERVER=(local);DATABASE=FILMS;
      Integrated Security=SSPI; "
   SelectCommand= "SELECT actor_first, actor_last, actor_sex FROM
      actor_movie LEFT JOIN actors ON actor_movie.actor_ID=
      actors.actor_ID WHERE movie_ID=@movieID">
      <SelectParameters>
         <asp:ControlParameter Name="movieID"
            ControlID="ListBoxMovie"
            PropertyName="SelectedValue"
         </asp:ControlParameter>
      </SelectParameters>
</asp:SqlDataSource>

The GridView control contains three columns that are bound to the data source control:

<asp:GridView ID="MovieGridView" DataSourceID="ActorSource"
   Width="100%" runat="server" AutoGenerateColumns="false"
   SelectedIndex="0" AutoGenerateSelectButton="true" >
      <Columns>
         <asp:BoundField HeaderText="First Name"
              DataField="actor_first" />
         <asp:BoundField HeaderText="Last Name"
                 DataField="actor_last" />
         <asp:BoundField HeaderText="Sex"
                 DataField="actor_sex" />
      </Columns>
</asp:GridView>

Core Note

Core Note

In addition to the ControlParameter that specifies the control from which a query's parameter value comes, ASP.NET recognizes five other parameter sources: CookieParameter, FormParameter, ProfileParameter, SessionParameter, and QueryStringParameter.

ObjectDataSource Control

The ObjectDataSource is used when data is retrieved through a data access layer, rather than directly from the database. To demonstrate, recall the three-tier structure we created earlier by placing the getMovies method in a separate assembly. An ObjectDataSource control can be declared to access this method by setting its typename field to the class containing the method and its selectmethod field to the name of the method.

<asp:objectdatasource
   id="ObjectDataSource1"
   runat="server"
   typename="myUtil.DataMethods"
   selectmethod="getMovies">
</asp:objectdatasource>

This could be used to populate the ListBox control in Listing 16-7 by replacing the SqlDataSource control with the ObjectDataSource control and resetting DataSourceID to ObjectDataSource1 in the ListBox declaration.

It is also worth noting that an ObjectDataSource can be used to fetch data from a Web Service. Because Web Services (described in Chapter 18) are nothing more than classes that expose remotely accessible methods, the typename and selectmethod properties can be used to refer to the class and method as if they were in a local assembly.

XmlDataSource Control

The XmlDataSource control is likely to be the most popular of the data source controls. It reads XML data from a local file or a stream transmitted across a network. It's particularly useful for handling the XML formats that are becoming standards for exchanging information across the Internet. To illustrate, we'll create an example that uses the control to read data in the increasingly popular RSS format.

RSS, which stands for Really Simple Syndication, is an XML formatting standard designed originally for news feeds. However, it is now used as a generic way for Web sites to periodically publish information feeds that can be picked up by RSS readers. It not only provides an easy way to distribute data, but the simple format makes it easy for the recipient to determine when any updates have occurred. Because the data is sent as an XML stream, the XmlDataSource control can play the role of a simple RSS reader. As an example, we pair it with a DataList control to capture and display a sample RSS feed from the BBC news network (see Figure 16-14).

Display RSS feed using XmlDataSource and DataList controls

Figure 16-14. Display RSS feed using XmlDataSource and DataList controls

The underlying XML conforms to the RSS standard (several versions are now in use). At the top level is the <rss> element that contains a required version attribute. Subordinate to it is a single <channel> element that contains a description of the channel along with its content. The content is supplied by <item> elements that have three mandatory subelements: <title>, <link>, and <description>. Their purpose should be clear from the portion of the XML feed shown here:

<rss version="0.91">
   <channel>
      <title>BBC News | Science/Nature | World Edition</title>
      <link>
         http://news.bbc.co.uk/go/click/rss/0.91/public/-
               /2/hi/science/nature/default.stm
      </link>
      <description>Updated every minute of every day</description>
      <item>
         <title>Huge 'star-quake' rocks Milky Way</title>
         <description>
            Astronomers say they are stunned by the explosive
            energy released by a super-dense star on the
            far side of our galaxy.
         </description>
      <link>
         http://news.bbc.co.uk/go/click/rss/0.91/public/-
                 /2/hi/science/nature/4278005.stm
      </link>
      </item>
      <item>
      ... other items go here
   </channel>
</rss>

Listing 16-8 shows how the XML is displayed using only a DataList and XmlDataSource control. The XmlDataSource control identifies the data source with the DataFile property. As mentioned, this can be a file or a URL. The purpose of the XPath property is to set a filter for the XML document so that only a subset of the document is returned. In this case, we are interested in only the <item> data.

The DataList control identifies the data source component it is bound to by setting the DataSourceID property to the component's ID—XmlDataSource1. The XPathBinder object is used to select items or nodes from the XML document. Its XPath method returns a single node, whereas the XPathSelect method returns an ArrayList of matching values. Both take an XPath expression (see Chapter 10) to identify the desired item.

<%# XPath("xpath-expression"[, "format"]) %>
<%# XPathSelect("xpath-expression") %>

Example 16-8. Displaying RSS Feed with a DataList and XmlDataSource Control

<asp:DataList ID="DataList1" Runat="server"
   RepeatColumns=1
   RepeatDirection="Horizontal"
   GridLines="Horizontal"
   BorderWidth="1px" BackColor="White" CellPadding="2"
   BorderStyle="None" BorderColor="#E7E7FF"
   DataSourceID="XmlDataSource1">
   <ItemTemplate>
      <asp:HyperLink ID="HyperLink1" Runat="server"
         Text=<%# XPath("title") %>
         NavigateUrl=<%# XPath("link") %>
         Target="_blank" Font-Names="Sans-Serif"
         Font-Size="X-Small">
      </asp:HyperLink><br/>
      <i><%# XPath("description")%></i><br /><br />
   </ItemTemplate>
   <AlternatingItemStyle BackColor="#F7F7F7">
   </AlternatingItemStyle>
   <ItemStyle ForeColor="#4A3C8C"
      Font-Size=9pt BackColor="#E7E7FF">
   </ItemStyle>
   <HeaderTemplate>BBC RSS Feed: Nature</HeaderTemplate>
   <HeaderStyle ForeColor="#F7F7F7"
      Font-Bold="True" BackColor="#4A3C8C">
   </HeaderStyle>
</asp:DataList>
<asp:XmlDataSource
   ID="XmlDataSource1"
   Runat="server"
   XPath="rss/channel/item"
   DataFile=
      "http://news.bbc.co.uk/rss/newsonline_world_edition/
      science/nature/rss091.xml">
</asp:XmlDataSource>

By binding a data component to a sophisticated visual data control, we are able to create an application in which all data binding is specified through declarations. This can eliminate the need for code to input data, iterate through it, and parse it into a format that can be displayed. In addition, data components have built-in caching features that improve the efficiency of accessing data and eliminate code-managed caching. We look at caching in the next chapter.

Validation Controls

One of the more frustrating Internet experiences is to fill in a long form, submit it, and—after a lengthy wait—have it rejected due to an invalid field entry. Any well-designed form should attempt to avoid this by including client-side verification to check fields before the form is submitted. Validation is typically used to ensure that a field is not empty, that a field contains a numeric value only, that a phone number or credit card has the correct format, and that an e-mail address contains the @ character. JavaScript is traditionally used for this purpose.

ASP.NET offers validation controls as a flexible alternative to implementing your own client-side JavaScript functions. The purpose of these controls is to perform a specific type of validation on an associated control. For example, a RequiredFieldValidator control checks an input control to ensure it is not empty.

Table 16-5 lists the six built-in validation controls along with their unique properties and values.

Table 16-5. Validation Controls

Control

Properties

Description and Possible Values

RequiredField
Validator

Checks whether input control field contains a value.

CompareValidator

Compares the value of an input control with a constant or other control.

Operator

Has value of:

Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, DataTypeCheck

Type

Currency, Date, Double, Integer, String.

ValueToCompare

Constant value used for comparison.

ControlToCompare

Other control to compare value with.

RangeValidator

Checks the value of the input control against a range of values.

MaximumValue

Constant value that represents upper value.

MinimumValue

Constant value that represents lowest value.

Type

Currency, Date, Double, Integer, String.

RegularExpression
Validator

Matches the value of the input control against a regular expression.

ValidationExpression

Regex to match input control's value against.

CustomValidator

Checks a field's value against custom validation logic.

ClientValidationFunction

JavaScript client-side function to perform validation.

OnServerValidate

Server-side method to perform validation.

ValidationSummary

Collects and lists all the error messages from the form validation process.

DisplayMode

Format of error messages:

BulletList, List, SingleParagraph

HeaderText

Title for error message summary.

ShowMessageBox

Display errors in pop-up box: true or false.

ShowSummary

Display errors on control: true or false.

Only the final control in the table, ValidationSummary, does not perform a validation. Instead, it displays a summary of the errors generated by the other validation controls.

Using Validation Controls

Validation controls are used only with controls that have a single input field type. These include the HTMLInputText, HTMLSelect, TextBox, DropDownList, and ListBox controls. ASP.NET implements the validation function on both the server and client side. To handle the client side, it includes a .js file containing validation code in the response to the browser. Note, however, that if scripting is disabled on the browser, only client-side checking will occur.

To illustrate the mechanics of using a validation control, here is the code to associate a RequiredFieldValidator and RangeValidator control with a TextBox control. The range validator control uses its MaximumValue and MinimumValue properties to ensure that the value entered in the text box is a numeric value from 0 to 12.

<td><asp:TextBox Width=30 id=hti maxlength=2
   runat=server></td>

<asp:RequiredFieldValidator id="htivalidator" runat=server
   ControlToValidate="hti"
   ErrorMessage="Must enter height value."
   Display="dynamic">
</asp:RequiredFieldValidator>

<asp:RangeValidator id="htirangevalidator"
   ControlToValidate="hti"
   MaximumValue="12"
   MinimumValue="0"
   Type="Integer"
   Display="dynamic "
   ForeColor="Blue"
   ErrorMessage="Invalid Height."
   runat=server>
</asp:RangeValidator>

This example also illustrates useful properties that all validator controls inherit from the BaseValidator class:

  • ControlToValidateSet to the identifier of the control to be validated.

  • Display. static, dynamic, or noneThis specifies how the error message takes up space on the form: static reserves space and makes the message visible when it is needed; dynamic uses no space on the form until it is displayed; none is used when the error message is to be displayed by the ValidationSummary control.

  • ForeColorSets the color of the error message text. Red is the default.

  • ErrorMessageThe message displayed when a validation exception is detected.

  • ValidationGroupA string value that enables validation controls to be grouped by assigning the same value to this property. When a Button control that has its ValidationGroup property set to a group value submits a form, only controls with validators in the group are validated. This allows sections of a page to be validated separately. This property is introduced in ASP.NET 2.0.

Of the controls listed in Table 16-4, the CustomValidator and ValidationSummary exhibit unique behavior that requires further explanation.

CustomValidator Control

There are many common validation patterns that the built-in validation controls do not handle. For example, the contents of one control may be dependent on another, such as when a credit card number format depends on the type of card selected. Another common example is to require that a string entered in a field conform to a minimum and maximum length. Cases such as these are handled with a CustomValidator control that points to server-side and/or client-side routines that implement custom validation logic. To demonstrate how this control works, let's use it to validate a field that accepts a password, which must be between 8 and 12 characters in length.

The declaration of the control is similar to that of the other validation controls. The main difference is the ClientValidationFunction and OnServerValidate fields that specify client and server routines to validate the associated password field.

<input type=password id="pw" runat="server" />
<br>
<asp:CustomValidator  id="pwvalidator"
   ControlToValidate="pw"
   ClientValidationFunction="checkPWClient"
   OnServerValidate="checkPW"
   Display=dynamic
   ErrorMessage="A password must be between 8 and 12 characters."
   runat="server"/>

The validation routines contain identical logic. The client side is written in JavaScript and the server side in C#. They are contained in separate <script/> blocks in the .aspx file.

<script language=javascript>
<!—
   // Client side function to check field length
   function checkPWClient(source, args)
   {
      var pw = args.Value;
      var ln= pw.length;
      args.IsValid=true;
      if(ln <8 || ln > 12) args.IsValid=false
   }
//-->
</script>
<script Language="C#"  runat="Server">
private void checkPW(object source, ServerValidateEventArgs args){
   if(args.Value.Length<8 || args.Value.Length>12)
         {args.IsValid=false;
   } else{
      args.IsValid=true;
   }
}
</script>

Two parameters are passed to the validation routines: source and args. Args is the more important of the two. Its value property exposes the content of the form field being validated. In this example, the length of the field value is checked. If it falls within the bounds, IsValid is set to true; otherwise, it is set to false. A false value triggers the error message defined by the CustomValidator control.

For consistency with the built-in validation controls, include both server- and client-side routines for custom validation. If only one is to be implemented, server side is always preferred.

ValidationSummary Control

This control collects all of the error messages generated by the validation controls and displays them as a list or customizable paragraph. The messages are displayed either within the control (as a <span> element within the HTML) or as a pop-up window by setting the ShowMessageBox to true.

<asp:ValidationSummary id=validsumm runat="server"
   ShowMessageBox=true
   DisplayMode=List
   ShowSummary=false>
</asp:ValidationSummary>

The most important factor to consider when deciding how to display validation error messages is that the ValidationSummary control displays messages when a form is submitted, and individual validation controls display a message when their associated control loses focus. Thus, displaying the message at a validation control provides immediate feedback to the user. Also note that if the validation control has its Display property set to static or dynamic, the error message is displayed by both the validation control and the ValidationSummary control.

Master and Content Pages

A principal design objective when creating a multi-page Web site is visual and functional consistency. Headers and footers should look the same, and the layout and use of controls should be similar from page to page. One way to achieve this is to drag and drop common controls on to each new Web page; but in many cases, a better approach is to create a template from which new pages can be derived. In Windows Forms programming, a form can be filled with controls and used as a base class to create other interfaces. ASP.NET has a similar feature known as master pages. The idea is to create a template, or master page, containing visual elements that will be common to other pages, as well as placeholders that will be filled in with the unique content from other pages. Pages that provide content for the master page are referred to as content pages.

The major advantages to this approach are that any changes made to a master page are automatically reflected in the content pages and that the act of creating content pages is limited to providing statements that specify the content to be associated with the placeholders in the master page.

To illustrate the fundamentals of using master pages, we'll create a master page that defines the layout for the Web page shown in Figure 16-15. In addition, we'll create two content pages that correspond to the menu items shown on the left side of the page.

Web page derived from a master page

Figure 16-15. Web page derived from a master page

Creating a Master Page

Master pages are so similar to regular .aspx pages that converting an .aspx page to a master page requires only three changes: the file must have the .master extension; the @Page directive is replaced with the @Master directive; and one or more ContentPlaceHolder controls are added to serve as containers that will be filled at runtime with content from content pages.

Listing 16-9 shows the master page used by content pages to create the Web page in Figure 16-15. The two menu items are implemented as HyperLink controls that reference the two content pages. Two ContentPlaceHolder server controls designate the area in the page where a content page's title and main body are placed.

Example 16-9. Master Page Definition—shell.master

<%@ Master  %>
<html>
<head>
   <title>Master Page for Shell Design Studio</title>
</head>
<body bgcolor=#ffffff link=#ffffff alink=#ffffff vlink=#ffffff>
<FORM id="mainform" runat="server">
<table width="500" cellpadding="0" cellspacing="0">
   <tr><td bgcolor=black align=center>
         <img src=./images/sdslogo.gif>
      </td><td>&nbsp;&nbsp;
      </td><td>
         <font size=4 face=Verdana> <b>SHELL</b> DESIGN STUDIO
      </td></tr>
   <tr>
      <td width=120 height=300 bgcolor=red valign=top>
         <asp:HyperLink id="homepage" NavigateUrl="home.aspx"
            Text="Home Page"
            Font-Bold="true" Font-Size=9pt
            Font-Names="Verdana"
            runat=server />
         <br><br>
         <asp:HyperLink id="clients" NavigateUrl="clients.aspx"
            Text="Our Clients"
            Font-Bold="true" Font-Size=9pt
            Font-Names="Verdana"
            runat=server />
      </td>
      <td>&nbsp;&nbsp;</td>
      <td valign=top>
         <hr size=1 color=red>
         <asp:contentplaceholder id="Header" runat="Server">
            <b>Introduction</b>
         </asp:contentplaceholder>
         <hr size=1 color=red>
         <asp:contentplaceholder id="PageBody" runat="Server">
            This is Default Content to be overridden
            by content pages
         </asp:contentplaceholder>
      </td></tr>
</table>
</FORM>
</body>
</html>

Creating a Content Page

A content page is an .aspx file containing <asp:Content> tags (instances of Content controls) that override corresponding <asp:contentplaceholder> tags in the master page. The ContentPlaceHolderID property of the content tag matches the ID of the placeholder where the content is to be inserted. The home.aspx content page in our example illustrates this. It contains two content tags that define the content for the Header and PageBody placeholders, respectively. The masterpagefile attribute specifies the master page from which this page inherits.

[home.aspx]
<%@ Page language="C#" masterpagefile=~/shell.master %>
<asp:content id="Header" runat="server"
   contentplaceholderid="Header">
   <font size=3 face=Verdana> <b>Introduction </b>
</asp:content>

<asp:content id="MainBody" runat="server"
   contentplaceholderid="PageBody">
   <font face=Verdana size=2>
   Shell Design Studios specializes in interior decorating for
   homes and offices. Our staff contains experts in art, color
   theory, architectural design and home technology.
</asp:content>

The content may consist of any combination of standard HTML markup code, images, managed code, and server controls. In this example, the MainBody placeholder is replaced with literal text for the home.aspx page and a list of clients—using the <UL> tag—for the clients.aspx content page.

[clients.aspx]
<%@ Page language="C#" masterpagefile=~/shell.master %>
<asp:content id="Header" runat="server"
   contentplaceholderid="Header">
   <font size=3 face=Verdana> <b>Our Clients </b>
</asp:content>

<asp:content id="MainBody" runat="server"
   contentplaceholderid="PageBody">
   <font face=Verdana size=2>
   <ul>
      <li>Swanburg Medical </li>
      <li>Lombard & Gable Law </li>
      <li>Coble Architectural Design</li>
   </ul>
</asp:content>

There are only a few commonsense rules to keep in mind when using master/content pages:

  • A content page does not have to provide content for all placeholders. When content is not mapped to a placeholder, its default value is used.

  • Content may include ASP.NET server controls. However, controls cannot be placed outside of the content tags.

  • A placeholder's ID is unique. You cannot, for example, map the same content to two sections in a master page.

Accessing the Master Page from a Content Page

Recall that .aspx files are compiled at runtime into (System.UI.Web) Page objects. The Page object serves as the naming container for all objects on the page. When a master page is involved, one of the objects included is a MasterPage object that, in turn, serves as a container for the ContentPlaceHolder and Content objects. This hierarchy of objects comprises the Web page that is rendered to the client.

To support master pages, the Page object includes a Master property that can be used to reference objects in a master page's control collection. Content pages have access to this property and can thus alter the appearance of the master page template when they are loaded. As an example of how this can be used, consider the menu on our sample Web page (see Figure 16-15). The items have the same appearance no matter which page is loaded. As a rule, Web pages should distinguish the menu item for the currently loaded page from the other items. One popular technique is to highlight it. This requires adding only a few lines of code to our content files:

<%@ Import namespace=System.Drawing %>
<script runat="Server">
// Highlight link for home.aspx page
void Page_Load(object sender, EventArgs e)
{
   // Change color of link to indicate current page
   HyperLink h = (HyperLink)Master.FindControl("homepage");
   h.BackColor=Color.Goldenrod;  // highlight menu item
}
</script>

The Master.FindControl method is used to reference the link pointing to the current page. In this case, the returned object's BackColor property is set to highlight the link.

Building and Using Custom Web Controls

This section provides an overview of how to write and consume a custom Web control. Its objective is twofold: to introduce the fundamentals of implementing a control and, in the process, provide insight into the architecture of the intrinsic .NET controls.

In its simplest form, a custom control is a class that inherits from the System.Web.UI.Control class, implements (overrides) a Render method, and uses its HtmlTextWriter parameter to emit the HTML code that represents the control. Add some public properties to define the control's behavior, and you have a custom control that functions like the built-in ASP.NET controls. More complex controls may require features such as data binding and caching support, which are not covered in this chapter. For a full understanding of those topics, refer to a good ASP.NET book.[1]

A Custom Control Example

Listing 16-10 defines a custom control that we will use to illustrate the basics of control creation. The purpose of this control is to display a large or small version of a company logo, along with the company's name. Two properties determine its appearance and behavior: LogoType takes a value of small or large to indicate the size of the image to be displayed, and Link specifies the URL to navigate to when the small logo is clicked.

Example 16-10. A Custom Control—logocontrol.cs

using System;
using System.Web;
using System.Web.UI;
namespace CompanyControls
{
   // (1) Inherit from the System.Web.UI.Control class
   public class CompanyLogo : Control
   {
      // Custom control to display large or small company logo
      private string logo_sz;   // "small" or "large"
      // Page to go to when logo is clicked
      private string myLink;
      public string LogoType
      {
         get {return logo_sz;}
         set {logo_sz = value;}
      }
      public string Link
      {
         get {return myLink;}
         set {myLink = value;}
      }
      // (2) Override the Render method
      protected override void Render(HtmlTextWriter output)
      {
         // (3) Emit HTML to the browser
        if (LogoType == "large"){
            output.Write("<a href="+Link+">");
            output.Write("<img src=./images/logo_big.gif
               align=middle border=0>");
            output.WriteLine("</a>");
            output.Write("&nbsp;&nbsp;");
            output.Write("<b style=font-style:24;");
            output.Write("font-family:arial;color:#333333;>");
            output.Write("STC Software</b>");
         } else {
            output.Write("<a href="+Link+">");
            output.Write("<img src=./images/logo_small.gif
              align=middle border=0>");
            output.WriteLine("</a>");
            output.Write<br>");
            output.Write("<b style=font-style:12;");
            output.Write("font-family:arial;color:#333333;>");
            output.Write("Shell Design Studio</b>");
         }
      }
   }
}

Let's examine the three distinguishing features of a custom control class:

  1. Inherits from System.Web.UI.Control.

    This class provides properties, methods, and events that the custom control requires. The most important of these is the Render method that we describe next. Other members include the Page events (Init, Load, PreRender, Unload) and members for managing child controls.

    If you are using Visual Studo.NET for control development, the base class will be System.Web.UI.WebControls.WebControl. This class derives from the Control class and adds several members, most of which affect appearance.

  2. Overrides the Render method.

    Each control must implement this method to generate the HTML that represents the control to the browser. Note that the Render method is not called directly; instead, a call is made to RenderControl, which then invokes Render. For controls that contain child controls, the RenderChildren method is available. This is called automatically by the Render method, and an implementation that overrides this method should include a base.Render() call if the control contains child controls.

  3. Uses HtmlTextWriter object to generate HTML code.

    This example uses the HtmlTextWriter.Write method to generate HTML for the control. This is the simplest approach, but HtmlTextWriter offers several other methods that you may prefer. One alternative is to use a set of helper methods that eliminate writing full literal strings.

//Following yields: <table border=0>
Output.WriteBeginTag("table")
Output.WriteAttribute("border","0");
Output.WriteEndTag("table")

A third approach uses “stack-based” methods to render code. It uses AddAttribute methods to define attributes for a tag that is then created with a RenderBeginTag method call and closed with a RenderEndTag call. Although the approach is verbose, it has the advantage of automatically detecting which version of HTML a browser supports and emitting code for that version.

The following code generates the same HTML as is in Listing 16-7 for the large image. It relies on a mixture of HtmlTextWriter methods and special tag, attribute, and style enumerations. Refer to the documentation of HtmlTextWriter for the lengthy list of methods and enumerations.

output.AddAttribute(HtmlTextWriterAttribute.Href,Link);
output.RenderBeginTag(HtmlTextWriterTag.A);  // <a
output.AddAttribute(HtmlTextWriterAttribute.Src,bgImg);
output.AddAttribute(HtmlTextWriterAttribute.Align,"middle");
output.AddAttribute(HtmlTextWriterAttribute.Border,"0");
output.RenderBeginTag(HtmlTextWriterTag.Img);
output.RenderEndTag();
output.RenderEndTag();                       // </a>
output.Write (" &nbsp;&nbsp;");
output.AddStyleAttribute(HtmlTextWriterStyle.FontSize,"24");
output.AddStyleAttribute(HtmlTextWriterStyle.FontFamily,"arial");
output.AddStyleAttribute(HtmlTextWriterStyle.Color,"#333333");
output.RenderBeginTag(HtmlTextWriterTag.B);
output.Write("Shell Design Studio");
output.RenderEndTag();

Using a Custom Control

The key to using a custom control is the @Register directive, which was discussed in Section 16.1. Its Assembly and Namespace attributes identify the assembly and namespace of the custom control. Its TagPrefix attribute notifies the ASP.NET runtime that any tag containing this prefix value refers to the control specified in the directive. Here is a Web page that includes the custom CompanyLogo control:

<%@ Page Language="C#" %>
<%@ Register Namespace="CompanyControls" TagPrefix="logo"
   Assembly="logocontrol"   %>
<script runat="server">
   protected void SendPage(object src, EventArgs e)
   {
      // Process page here.
   }
</script>
<html>
<body>
   <logo:CompanyLogo runat="server" id="lgc"
      Link="products.aspx"
      LogoType="large"/>
   <hr>
   <font size=2 face=arial color=black><center>
   This page contains ways to contact us
<br>
   <asp:Button runat="server" text="submit"
      OnClick="SendPage" />
</body>
</html>

The control that we have created behaves similarly to the built-in Web controls, but lacks one important feature that they all have: the capability to maintain its state during a postback operation.

Control State Management

Let's change the preceding code to set the LogoType property when the page is first loaded, rather than within the body of the code. We use the IsPostBack property for this purpose:

protected void Page_Load(object src, EventArgs e)
{
   if (!IsPostBack) {
      lgc.LogoType="large";
   }
}

On the initial request, LogoType is set and the page is returned with a large image. However, subsequent postbacks result in the small image being displayed because the value is not retained, and the code defaults to the small image. Recall that state is maintained between postbacks in the hidden _VIEWSTATE field. This field contains the values of the ViewState collection, so the secret to state control is to place values in this collection. It operates like a hash table—accepting name/value pairs—and is accessible by any control. The following code demonstrates how property values are placed in ViewState as a replacement for the simple fields used in Figure 16-7.

public class CompanyLogo : Control
{
   // Custom control to display large or small company logo
   public CompanyLogo() {
      ViewState["logo_sz"] = "small";
      ViewState["myLink"] = "";
   }
   public string LogoType
   {
      get {return (string) ViewState["logo_sz"]; }
      set {ViewState["logo_sz"]= value;  }
   }
   public string Link
   {
      get {return (string) ViewState["myLink"]; }
      set {ViewState["myLink"]= value;  }
   }
   // Rest of class code is here...

The property values are now maintained in the _VIEWSTATE field and persist between postbacks.

Composite Controls

At the beginning of the chapter, we created a Web page (refer to Figure 16-2) that calculates the Body Mass Index. This calculator consists of text boxes, labels, and a button. To turn this into a custom control, we could take our previous approach and override the Render method with a lengthy list of statements to generate the appropriate HTML. In addition, special code would have to be added to maintain the state of the control during postback operations. A better solution is to create a custom composite control.

A composite control is created from existing controls. Its advantage is that these controls, referred to as child controls, are very low maintenance. They render themselves—eliminating the need to override Render—and they maintain their state during postbacks. In addition, they let you program with familiar objects and their members, rather than output statements.

There are two major differences in the code used for the “from scratch” custom class in our preceding example and that of a composite control:

  • The composite does not have to override the Control.Render method to display controls. Instead, it must override the Control.CreateChildControls method to add existing controls to the collection of controls making up the composite control.

  • The custom control class should inherit from the INamingContainer interface. Its purpose is to indicate to ASP.NET that child controls exist and they should be placed in a separate namespace. This prevents name collision problems when a page contains more than one composite control.

Listing 16-11 contains the code to implement the BMI calculator as a composite control. The calculator comprises three text boxes, a label to display the result, and a button to invoke a method to perform the calculation. Most of the code of interest is in the overridden CreateChildControls method. It adds the standard controls to the collection, and uses LiteralControl to add HTML and descriptive information that helps format the control. To simplify the listing, code validation is included on only one control.

Example 16-11. A Composite Control—bmicompos.cs

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CompositionControls {
   public class BMIComposition : Control, INamingContainer {
       TextBox htf;
       TextBox hti;
       TextBox wt;
       Label bmi;
   private void getBMI(object sender, System.EventArgs e)
   {
      if (Page.IsValid) {
         decimal f =  Convert.ToDecimal(htf.Text);
         decimal inch = Convert.ToDecimal(hti.Text);
         decimal w = Convert.ToDecimal(wt.Text);
         decimal totinches = f * 12 + inch;
         decimal h2 = totinches * totinches;
         decimal massIndex = (w * 703 * 10/ h2)/10;
         bmi.Text = massIndex.ToString("##.##");
      }
   }
   protected override void CreateChildControls() {
      htf = new TextBox();
      hti = new TextBox();
      wt  = new TextBox();
      bmi = new Label();
      bmi.Width= 50;
      bmi.BorderStyle= BorderStyle.Solid;
      bmi.BorderWidth=2;
      htf.Width= 30;
      hti.Width= 30;
      hti.ID = "hti";
      wt.Width = 40;
      // Display calculator interface
      Controls.Add(new LiteralControl
              ("&nbsp;&nbsp;<b>BMI Calculator</b>"));
      Controls.Add(new LiteralControl
              ("<br>BMI: &nbsp;&nbsp;"));
      Controls.Add(bmi);
      Controls.Add(new LiteralControl("<br>Height: &nbsp;"));
      Controls.Add(htf);
      Controls.Add(new LiteralControl("&nbsp;&nbsp"));
      Controls.Add(hti);
      Controls.Add(new LiteralControl(" (feet/inches)"));
      Controls.Add(new LiteralControl("<br>Weight: "));
      Controls.Add(wt);
      Controls.Add(new LiteralControl("<br>"));
      // Validation control for inches accepted
      RangeValidator rv = new RangeValidator();
      rv.ControlToValidate="hti";
      rv.MaximumValue="12";
      rv.MinimumValue="0";
      rv.Type=ValidationDataType.Integer;
      rv.ErrorMessage="Inches must be 1-12";
      Controls.Add(rv);
      // Button to invoke BMI calculation routine
      Button calcBMI = new Button();
      calcBMI.Text = "Submit Form";
      calcBMI.Click += new EventHandler(this.getBMI);
      this.Controls.Add(calcBMI);
      }
   }
}

Note that getBMI performs the BMI calculation only if the Page.IsValid property is true. Include this check when validation controls are used, because server-side validation sets the IsValid flag to false if validation tests fail, but does not prevent code execution.

To make the control available, compile it and place it in the in directory of the Web page.

csc /t:library bmicompos.cs

The control is included in a page using the @Register directive as described earlier:

<%@ Register Namespace="CompositionControls" TagPrefix="bmicon"
   Assembly="bmicompos" %>
<HTML>
   <table border=0 color=#cccccc>
   <tr><td>
      <bmicon:BMIComposition runat="server" id="bmic" />
   </td></tr></table>
BMI composite control

Figure 16-16. BMI composite control

Selecting a Web Control to Display Data

ASP.NET developers have an overflowing toolbox of controls to choose from—many with overlapping functionality. Table 16-6 offers some recommendations for selecting a control, or combination of controls, to handle frequently encountered Web page tasks. Some of the suggested controls have been introduced in this chapter; but for other important ones, such as the GridView, DetailsView, and FormView, you should refer to an in-depth ASP.NET 2.0 book or reference source.

Table 16-6. Controls Recommended for Typical Web Page Applications

Page Requirement

Controls

Display multiple data records in a spreadsheet or grid format.

GridView. Provides sorting, pagination, and editing. Permits controls to be embedded in cells. Performance can be slow, so use only when these features are required. If displaying data is the only objective, use the DataList or Repeater.

Display multiple data records in a custom format using simple controls.

DataList or Repeater. Use the DataList if editing and event handling is required. Use the Repeater strictly to display data in a repeated format.

Display a parent-child relationship between data.

ListBox or DropDownList and a GridView. See Figure 16-13 on page 779.

Display a hierarchical view, such as a directory or XML elements.

TreeView. Allows nodes to be expanded to display data on lower level.

View, edit, or delete data, one record at a time.

DetailsView or FormView.

Web page with multiple sections or one that steps through a process in multiple steps.

Use multiple panels as containers for simple controls. Panels in ASP.NET 2.0 include a scrolling capability for IE browsers.

Summary

ASP.NET is a development platform that offers a variety of techniques to overcome the inherent problems that plague client-server interaction over the Internet. These problems include browser incompatibility, the difficulty of retaining an application's state between requests, and a reliance on interpreted script rather than compiled code to implement program logic.

The ASP.NET answer to these problems is server-side based model whose code is written in C# or VB.NET. A Web page is a class deriving from the Page class. Controls are all classes that implement a Render method to generate the HTML code that represents them in the browser. An important design feature of this model is the ability to separate presentation logic from the business logic by placing the latter in a code-behind file.

Controls fall into two categories: HTML server controls and Web controls. The former correspond to traditional HTML tags, but include a runat=server attribute that indicates they are run on the server. Web controls are much richer than HTML controls. They include list controls; data display controls—DataList and GridView—that are usually bound to data sources; and validation controls, which are helper controls that validate the content of other controls. If none of these controls meet an application's need, custom controls can be designed that extend existing controls or present a new interface.

Test Your Understanding

1:

What are the two standard HTTP methods of sending data to a Web host? Which does ASP.NET use as its default?

2:

Indicate whether the following statements are true or false.

  1. All controls in ASP.NET are classes.

  2. An .aspx page must be compiled before it can respond to a request.

  3. A user must have the .NET runtime installed to take advantage of ASP.NET Web pages.

  4. ASP.NET may render different HTML code for different browsers.

  5. A Web page may contain both HTML Server and Web controls.

3:

Which collection class contains the data for List controls?

4:

You set up an event handler to be called when the contents of a TextBox change. If the contents are changed, when is the event handler code executed?

5:

Which property enables the controls on a master page to be programmatically accessed?

6:

Which property must be set and which method executed to bind a control to a data source?

7:

Indicate whether the following statements are true or false with regard to a data-bound control.

  1. It can bind directly to a DataAdapter.

  2. It can bind directly to a DataReader.

  3. The control is populated with data when its Fill method is invoked.

  4. Its DataSource property can specify a DataSet or a data source control.

8:

You have a form containing a text box field that is used to input a phone number. Which control would you use to ensure that the phone number includes an area code?

9:

Which directive must a Web page include to use a custom control on the page?

  1. @Page
    
  2. @Import
    
  3. @Register
    
  4. @Assembly
    

10:

What role does the HtmlTextWriter class play in the implementation of a custom control?

11:

What are the advantages of creating a composite control versus a non-composite custom control?



[1] Essential ASP.NET by Fritz Onion (Addison-Wesley, 2003) is a good choice.

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

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