Chapter 17. The Asp.Net Application Environment

Topics in This Chapter

  • HTTP Request and Response Objects: These ASP.NET classes correspond closely to the HTTP request and response specifications, and expose information about a request or response through class properties.

  • Configuration Files: Configuration files provide an easy way to specify the behavior and performance of a Web application. Focus is on the web.config file.

  • ASP.NET Security: Forms Authentication is a platform-neutral technique that can be used to control who can access a Web page. An example demonstrates the use of Forms Authentication to manage authentication and authorization.

  • State Management: Both the Application and Session classes can be used to maintain information during a Web session or during the life of a Web application.

  • Caching: Data caching and output caching can be used to improve Web application performance.

  • Accessing Web ResourcesThe WebRequest and WebResponse classes provide a simple way to request and process Web pages for the purpose of extracting page content.

  • HTTP Pipeline: The HTTP pipeline is the combination of events, HTTP modules, and HTTP handlers that affect how requests and responses are handled as they pass between a client and server.

Chapter 16, “ASP.NET Web Forms and Controls,” dealt with the most visible aspect of ASP.NET—the Web page. It described a Web application in terms of the controls, HTML, and compilable C# code that comprise it. Much of the emphasis was on how to construct the interface presented to a client. This chapter shifts the focus to the underlying details of ASP.NET support for the Hypertext Transfer Protocol (HTTP), which defines how Web requests and responses are transported between client and host.

You'll find that a discussion of the ASP.NET environment is heavily tilted toward those issues that fall under the responsibility of the Web server—as opposed to a Web client. This is only natural because one of the purposes of ASP.NET is to enable developers and architects to manage the difficult side of Web performance, while allowing Web clients to remain blissfully thin and unaffected.

The chapter begins with a little background information on the structure of a response and request—as defined by the HTTP standard. It shows how the HttpRequest and HttpResponse objects serve as proxies that expose and extend the information you'll find in the standard HTTP message structure. Next, the role of configuration files is discussed—a significant role that allows XML elements to be used to manage the behavior and performance of Web applications at an application, domain, and machine level. Controlling who can gain access to a Web page or resource is the next topic. Included is an overview of the .NET options for user authentication and authorization, as well as a detailed example of forms authentication.

One of the challenges of designing a Web application is determining how to maintain and manage state information. A discussion of how to persist data between Web requests includes examples of using both Session and Application objects to store information. ASP.NET also offers data and output caching as a way to store state information or to buffer frequently used data or Web pages. When used correctly, caching can greatly improve Web performance. Its advantages and disadvantages are considered.

The client side is not totally abandoned. The penultimate section demonstrates how to access Web resources using the WebRequest and WebResponse classes. An example uses an HttpWebRequest object to retrieve a Web page and glean information about the server.

The final section discusses the HTTP pipeline—a metaphor for the series of internal events that occur along the roundtrip journey that a request and response travel.

HTTP Request and Response Classes

HTTP specifications[1] concisely define messages as “requests from client to server and responses from server to client.” At its most elemental level, the role of ASP.NET is to enable server-based applications to handle HTTP messages. The primary way it does this is through HTTPRequest and HttpResponse classes. The request class contains the HTTP values sent by a client during a Web request; the response class contains the values returned to the client.

HttpRequest Object

An HttpRequest object is available to a Web application via the Page.Request or Context.Request property. The object's properties represent the way that .NET chooses to expose the content to an underlying HTTP request message. Consequently, the best way to understand HttpRequest is to examine the layout of the message it represents.

HTTP Request Message Structure

Figure 17-1 represents the general structure of the HTTP request message as defined by the HTTP/1.1 specifications.

Structure of a request message

Figure 17-1. Structure of a request message

Unless you are writing a browser, it is not necessary to understand the full details of the standard. However, a general understanding is useful to a developer who needs to extract information from the HttpRequest object. A few observations:

  • The Request-Line consists of three parts: the method token, the Request-URI, and the protocol version. Several methods are available, the most common being the POST and GET methods described in Chapter 16. The Uniform Resource Identifier (URI) specifies the resource being requested. This most commonly takes the form of a Uniform Resource Locator (URL), but can be a file or other resource. The protocol version closes out the Request-Line.

  • The general-header is used to pass fields that apply to both requests and responses. The most important of these is the cache-control field that specifies, among other things, what can be cached and how long it can be cached.

  • The request-header is used by the client to pass additional information about the request, as well as the client. Most of the HttpRequest object's properties correspond to values in this header.

Viewing the Request Message

For debugging—or simply out of curiosity—you may want to view the raw contents of the HTTP request. A simple way to do this is to use the SaveAs method of the HttpRequest class to write the request to a file where it can be viewed with a text editor. The method, as shown here, takes two parameters: the name of the output file and a bool type switch that indicates whether HTTP headers are to be included in the output.

this.Request.SaveAs("c:\myrequest.txt",true);

Posting a form containing two text boxes to the Web server generates this sample output. Of most interest are the browser description, referring Web page, and text box content values.

POST /ideas/panel.aspx HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 158
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-us
Cookie: ASP.NET_SessionId=uszrfs45z4f20y45v0wyyp45
Host: localhost
Referer: http://localhost/ideas/panel.aspx
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; 
.NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.40607)

__VIEWSTATE=%2FwEPDwULLTEwODExMTgwOTAPZBYCA ...
   &txtFirstName=joanna&txtLastName=larson&btnSubmit=Submit

HttpRequest Class Structure

Table 17-1 summarizes selected properties of the HttpRequest class. Where applicable, it includes the underlying header and message field on which its content is based.

Table 17-1. HttpRequest Properties

HttpRequest Property

Request Message Field

Description

AcceptTypes

String array of client-supported MIME accept types.

Browser

(request-header) User-Agent

Identifies software program making request.

ClientCertificate

Returns the client's security certificate as an HttpClientCertificate object. Used with SSL when a client is configured with a personal certificate.

ContentEncoding

(entity-header) Content-Type

Character set of the entity body.

ContentLength

(entity-header) Content-Length

Size of client request.

ContentType

(entity-header) Content-Type

MIME content type of request.

Cookies

Not defined in HTTP protocol

Collection of client's cookie variables.

FilePath

Request-Line URI

The virtual path of the currently requested page.

Files

Files uploaded by the client.

Form

Body

The collection of form variables including ViewState.

Headers

general and request-header fields

Collection containing content of headers contained in request message.

HttpMethod

Request-Line method

HTTP data transfer method.

IsAuthenticated

(request-header) Authorization

Has user been authenticated. True or False.

IsSecureConnection

Request-Line

True if HTTPS used.

Path

Request-Line URI

Virtual path of current request.

PhysicalPath

Physical path of current page.

QueryString

Request-Line URI

Query string arguments.

RawUrl

Request-Line URI

Part of a URL following the domain name.

RequestType

Request-Line method field

HTTP data transfer method (GET, POST).

TotalBytes

(entity-header) Content-Length

Number of bytes in input stream.

Url

Request-Line URI field and Host field of header

URL of current request.

UrlReferrer

(request-header) Referer field

Information about the URL of the client's previous request that linked to the current URL.

UserAgent

(request-header) User-Agent

String containing raw information about the client software used for request—usually a browser.

UserHostAddress

IP host address of the remote client.

The built-in features of ASP.NET reduce the amount of direct interaction required between an application and the HttpRequest object. For example, we saw in Section 16.2, “Web Forms Controls,” that ASP.NET Web controls generate code that is automatically tailored to the client's browser—eliminating the need for the application code to implement logic to identify the browser. There are, however, some cases where an application must access the object fields directly. Logging Web statistics is one; another is working with cookies.

Cookies are used to store information on the computer of a client that has cookies enabled on his browser. The browser is responsible for passing the cookie value as part of the request and handling any cookies returned from the server. The Cookies attribute references the collection of cookies returned by the browser. The following code loops through the collection displaying the name, value, and expiration date of cookies in the collection.

// Read cookies returned by browser
foreach(string cookieName in Request.Cookies.AllKeys){
   HttpCookie cookie = Request.Cookies[cookieName];
   Response.Write(cookie.Name+" = "+cookie.Value
                  +"<br>Expires: "+cookie.Expires);
}

The only cookie in this collection is the ID used by ASP.NET to identify sessions. ASP.NET's use of cookies is discussed further in the next section.

.NET_SessionId = avsqkn5501m3u2a41e3o4z55
Expires: 1/1/0001 12:00:00 AM 

HttpResponse Object

The HttpResponse class contains properties that encapsulate the information returned in a response message. It also provides methods that construct the response and send it to the requestor. As we did with the Request object, let's begin by taking a quick look at the underlying HTTP response message represented by the Response object (see Figure 17-2).

Structure of a response message

Figure 17-2. Structure of a response message

HTTP Response Message Structure

The server responds with a status line that includes the message's protocol version, a success or error code, and a textual description of the error code. This is followed by the general header and the response header that provides information about the server. The entity header provides metainformation about the body contents or the resource requested.

Viewing an HTTP Response Message

ASP.NET provides the HttpWebRequest and HttpWebResponse classes for working with HTTP requests and responses, respectively. They are discussed in detail later in the chapter, but let's take a preliminary look at how they can be used to display portions of a response message.

Listing 17-1 contains a simple application that sends a request to a user-entered URL, receives the response, and extracts the status code and server description. Run the program from the command line by typing in the program name and domain name you want to contact:

Example:

showserver www.addison-wesley.de/

Output:

Web Host: Microsoft-IIS/5.0 Response Status: OK

Example 17-1. Getting Status and Server Information from a Response Message

//File: showserver.cs
using System; 
using System.Net; 
class WebClient
{
   // To run, type in:  showserver <domain name>
   public static void Main(string[] args) 
   {
      HttpWebRequest request; 
      HttpWebResponse response; 
      if(args.Length>0)
      {
         string url="http://"+args[0];
         // Create a request to the URL 
         request = (HttpWebRequest) WebRequest.Create(url); 
         try
         {
            response = (HttpWebResponse) request.GetResponse(); 
            Console.WriteLine("Web Host: "+response.Server);
            Console.WriteLine("Response Status: "+ 
                              response.StatusCode);
         } catch ( Exception ex)
         {
            Console.Write(ex.Message);
         }
      } else 
      {
         Console.Write("You must enter a domain name.");
      }
   }
}

HttpResponse Class Properties

Table 17-2 lists selected properties of the HttpResponse class. Some of those excluded exist only for ASP compatibility and have been deprecated by newer properties.

Table 17-2. Selected HttpResponse Properties

Property

Description

BufferOutput

Set to true or false to indicate whether output is buffered and sent only when a page has been completely processed. Default is true.

Cache

HttpCachePolicy object that describes the caching policy features such as expiration time and privacy.

Example: Response.Cache.SetExpires( DateTime.Parse("6:00:00PM"));

CharSet

Gets or sets the character set of the output stream.

ContentEncoding

An Encoding object that contains information about the character set of the current response. Encoding type includes ASCII, UTF-7, UTF-8, and others.

ContentType

String value containing MIME type of the output—for example, “text/html”.

Cookies

Collection of cookies sent to the client.

Filter

A developer-written Stream object that filters all data being sent to the client.

IsClientConnected

Indicates whether client is still connected to the server.

Output

TextWriter object that sends text output to the client software. Example: Response.Output.Write("{0} Years Old", age);

StatusCode

Status code returned in the response message status line.

StatusDescription

Status code description returned in the response message status line.

Particularly noteworthy is the Cache property, which can greatly affect how pages are displayed to a browser. It is used to define a caching policy that dictates if and how long pages are cached. We'll look at this property in Section 17.5, “Caching.”

An example that displayed the contents of a cookie was presented in the discussion of the HttpRequest class. Let's extend that example by demonstrating how the response object is used to create and return a cookie to the client.

// Create a cookie
HttpCookie myCookie = new HttpCookie("userid","007");
// Cookie will live for 10 minutes
// Timespan(days, hours, minutes, seconds)


myCookie.Expires = DateTime.Now.Add(new TimeSpan(0,0,10,0));
// Add to collection
Response.Cookies.Add(myCookie);
// Later... Read specific cookie
myCookie = Request.Cookies["userid"];
if(myCookie !=null) Response.Write("userid: "+myCookie.Value);

Using HttpResponse Methods

The Response.Write method, which we have used in numerous examples to emit output into the response stream, is the most commonly used method. Other useful methods include the following:

  • RedirectRedirects the client's request to another URL.

  • AppendHeaderAdds an HTTP header to the response stream.

  • ClearContentClears the contents of the response stream.

  • EndStops page execution and sends buffered output to the client.

  • WriteFilePlaces the contents of a file directly into the output stream.

A simple, but useful, example (see Listing 17-2) illustrates how these methods can be used to download a file. The client request for this page includes a query string with the name of a requested file. The page locates the file (or returns an error message) and uses WriteFile to send it to the user. The AppendHeader, ClearContent, and End methods prepare and manage the response stream.

Example 17-2. Using HttpResponse to Download a File

//File: requestfile.aspx
<%@ Page Language="C#"  %>
<script Language="C#"  runat="Server">
   private void Page_Load(object sender, EventArgs e)
   {
      //http://localserver/ideas/requestfile.aspx?file=notes.txt
      string fileRequest = Request.QueryString["file"];
      if(fileRequest!=null) {
         // File is store in directory of application
         string path = Server.MapPath(fileRequest);
         System.IO.FileInfo fi = new System.IO.FileInfo(path);
         if (fi.Exists) {
            Response.ClearContent(); // Clear the response stream


            // Add a header to indicate attachment type
            Response.AppendHeader("Content-Disposition", 
                                  "attachment;filename="+fi.Name);
            Response.AppendHeader("Content-Length",
                                  fi.Length.ToString());
            // Use octet-stream to indicate unknown media type
            Response.ContentType="application/octet-stream";  
            // Write file to output stream
            Response.WriteFile(fi.FullName);
            Response.End(); // Flush buffer output to the client
         }  else {
            Response.Write(path+" does not exist.");
         }
      } else {
         Response.Write("No Download file was specified.");
      }
   }
</script>

ASP.NET and Configuration Files

One of the advantages of working with ASP.NET, as a developer or administrator, is the ease of configuring and customizing the settings. The use of XML-based configuration files replaces the previous ASP approach that required shutting down and restarting server software to change a setting or replace DLLs. Now, changes that affect the underlying behavior of a Web page can be made dynamically. The beauty of this approach is that changes can be made using a simple text editor—although an XML editor is preferable—and that ASP.NET automatically detects and applies these changes when they occur.

Configuration information is stored in a required machine.config file and optional web.config files. The format of these files is the same; the difference is their scope.

As shown in Figure 17-3, configuration files can be placed in a hierarchy on the Web server. Each file lower down in the hierarchy overrides the settings (with a few exceptions) of files above it. The machine.config file is at the top of the hierarchy, and its settings apply to all applications running on the machine. Web.config files placed in application directories and subdirectories apply their settings to a specific application or pages in an application.

Hierarchy of configuration files

Figure 17-3. Hierarchy of configuration files

Core Note

Core Note

As an alternative to a text editor, ASP.NET includes a configuration editor with the IIS management console. It is accessed from the Properties menu of a virtual directory.

A Look Inside web.config

The web.config file is an XML-formatted text file comprising numerous sections and subsections. Listing 17-3 offers a sampling of sections you're likely to find most useful: <configSections>, <appSettings>, <location>, <system.web>, and <connectionStrings>. After summarizing key features of these predefined sections, we'll look at how to create a custom configuration section.

Example 17-3. Sample web.config File (continued)

<configuration>
<!-- (1) Define custom configurations                -->
   <configSections>
      <section name="RewriterConfig" 
         type="RewriteSectionHandler, URLRewriter" />
   </configSections>
   <RewriterConfig>
      <!-- Contents of custom configuration section -->
   </RewriterConfig>


<!-- (2) Place application data in here              -->
   <appSettings>
      <add key="mainFont" value="arial" />
      <add key="fontSize" value="2" />  
   </appSettings>
<!-- (3) Define system.web settings for a directory  -->
   <location path="calculatorsmi">  
      <system.web>
         <trace enabled="true"
         pageOutput="true" />
      </system.web>  
   </location>
<!-- (4) Main ASP.NET Application settings            -->
   <system.web>
      <sessionState  
         cookieless="false"
         timeout=20  />
   ...
   </system.web>  
<!-- (5) Connection string for database               --> 
   <connectionStrings> 
      <!-- connection string description  -->
   </connectionStrings>
</configuration>

<appSettings> Configuration Section

This area is used by developers to hold constant data values required by an application. These values typically include preference settings that define the appearance of a Web page. This entry illustrates how font settings are added using an <add> element and a key-value pair of attributes:

<add key="mainFont" value="arial" />

Values are retrieved from the appSettings section using the static AppSettings property of the ConfigurationSettings class:

myFont = ConfigurationSettings.AppSettings["mainFont"];

<location> Configuration Section

As mentioned previously, web.config files can be placed hierarchically within a directory structure, with files at lower levels overriding the settings of files in parent directories. This approach permits you to tailor the actions of ASP.NET down to the application and page level. However, this flexibility brings with it the potential problem of keeping track of the settings in multiple configuration files. As an alternative, ASP.NET provides a way to configure the settings for multiple directories in a single web.config file by using <location> elements. The <location> section operates as a configuration file within a configuration file. The element has a path attribute that takes the value of a virtual directory name. The settings within the contained <system.web> block apply to the virtual directory.

As an example, suppose that during application development we want to view the trace information as we test our applications. Because we don't want this voluminous output in the final product, we can set up a virtual “test” directory and enable tracing for that directory only.

<location path="test">
   <system.web>
      <trace enabled="true" pageOutput="true" />
   </system.web>
</location>  

This has the same effect as placing a web.config file with this trace setting in the physical directory associated with the “test” virtual directory.

<system.web> Configuration Section

This is the area in web.config where an administrator can configure just about any aspect of the ASP.NET environment. It can be used to set session time-out length, user authentication and authorization rules, the type of session state management used, and the default culture used for processing requests. Table 17-3 summarizes the more important elements in this section.

Table 17-3. Selected system.web Elements

Element

Use

<compilation>

Sets default language and debug option.

<customErrors>

Defines custom errors and error pages.

<trace>

Enables or disables the trace feature for an application.

<pages>

Sets default Web page attributes.

<globalization>

Defines response/request encoding and culture-specific setting for Web pages.

<processModel>

Used to configure process setting for an IIS Web server.

<authentication>

Specifies authentication mode for a client: Windows, Forms, Passport, None.

<authorization>

Allows or denies access to resources.

<membership>

Defines how the Membership class manages user credentials and authentication.

<identity>

Specifies whether client impersonation is used on a Web page request.

<sessionState>

Configures the session state features of ASP.NET.

Of particular interest are the authentication, authorization, and membership elements that are discussed in Section 17.3, “ASP.NET Application Security.” Other useful sections are summarized here.

The <compilation> Section

Use the debug attribute to indicate whether debugging symbols will be included in the compiled code. The defaultLanguage attribute sets the default language used in a Web page's script blocks. Multiple languages can be selected by using a semicolon to separate them.

<compilation  debug="true"  defaultLanguage="C#"  />

The <customErrors> Section

The primary use of this section is to redirect errors to custom Web pages. Here is an entry that establishes a default error page, and a page to handle the HTTP 404 status code that arises when a resource cannot be found:

<customErrors defaultRedirect = "defaultError.aspx" mode="On" >
   <error statusCode="404" redirect="Error404.aspx" />
</customErrors>

The mode attribute takes the value On, Off, or RemoteOnly. The latter value, which is also the default, specifies that the default page is called only if the request comes from a machine other than the Web server. This permits local developers to see details of the actual error, whereas clients see only the custom page. Off should be used when a developer is testing pages on a remote machine. Note that multiple <error> elements can be included in the section.

The <trace> Section

The <trace> element is used to enable application-level trace logging. This is in contrast to the trace attribute of the <%@Page> directive that provides only page-level tracing. Here is an example that illustrates the use of the <trace> element and its associated attributes:

<trace
   enabled="true"
   pageOutput="false"
   traceMode="SortByTime"
    requestLimit="5",
   localOnly="false"
</trace>

Aside from the enabled attribute that turns tracing on or off, the most interesting attributes are pageOutput and localOnly.

  • pageOutput specifies whether the trace log is appended to the Web page for display on the client's browser. If set to false, the output can be viewed in the trace.axd page of the application's root. Here is an example:

    Application URL:

    http://localhost/calculation

    Trace log:

    http://localhost/calculation/trace.axd

  • The requestLimit attribute sets the maximum number of trace logs maintained.

  • localOnly indicates whether tracing is enabled for localhost users only or all users. The default is true.

The <sessionState> Section

One of the more powerful features of ASP.NET is the ability to maintain state information for a session; and the <sessionState> element plays a definitive role in selecting how it is implemented. By setting its mode attribute to one of four values, the software architect can specify where the session state information is stored.

  • off disables the use of session state management.

  • Inproc enables in-process state management (aspnet_state.exe).

  • StateServer state is stored by a surrogate process running on a selected server.

  • SqlServer state is maintained in a temporary SQL Server table.

These options are described in detail in Section 17.4. Our interest in this section is to understand the elements and attributes that define how ASP.NET manages session state. To illustrate, here are the default settings for the <sessionState> element:

<sessionState 
   mode="InProc"
   stateConnectString="tcpip=127.0.0.1:42424"
   sqlConnectionString="data 
       source=127.0.0.1;Trusted_Connection=yes"
   cookieless="false"
   timeout="15"
/>

The timeout attribute specifies the number of minutes the session can be idle before it is abandoned. The cookieless attribute indicates whether the identification of the client session is maintained using a cookie or by mangling the URL (placing encoded session information in the URL line). The other two attributes are dependent upon the mode setting: stateConnectString specifies the IP address of the machine running the Session State Server process, and sqlConnectionString contains the connection string value used to access SQL Server if it is used to maintain state information.

The <connectionStrings> Section

Prior to .NET version 2.0, developers usually resorted to storing connection strings as a key-value pair in the <appSettings> section. ASP.NET 2.0 now provides a predefined <connectionStrings> section for this purpose. As shown in this example, the section takes three attributes: name, connectionString, and providerName.

<connectionStrings>
  <add name="movies"
    connectionString=
      "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=/movies.mdb;"
    providerName="System.Data.OleDb"/>
</connectionStrings>

The connection string is retrieved using the ConfigurationSettings class, which retrieves the string associated with a specified name attribute. Note that multiple strings may be defined.

string cs= 
ConfigurationSettings.ConnectionStrings["movies"].ConnectionString;

Adding a Custom Configuration Section

The web.config file can be a convenient place to stash information needed by an application. We saw earlier how simple name/value pairs can be stored in the <appSettings> section. Suppose, however, your application requires data represented by a more complex XML structure. The solution is to add a custom configuration section to web.config. However, it is not as simple as choosing a configuration name and inserting appropriately formatted code into the configuration file. When you try to access a Web application that uses the configuration file, you'll receive an Unrecognized Configuration Section error.

It turns out that each section in web.config has a special handler that parses the section and makes its content available to an application. This handler is a class that implements the System.Configuration.IConfigurationSectionHandler interface and is declared in the <configSections> section of the web.config file.

Core Note

Core Note

You can view the section handlers for the standard predefined sections, such as <appSections> in the machine.config file.

To demonstrate how section handlers work, let's create one for the <RewriterConfig> section shown in the code that follows. The first thing to note is the <configSections> element that contains a definition of the new section. Its type attribute specifies the handler (class) that parses the <RewriterConfig> section. The data section represents URLs that have been changed and need to be replaced with a new URL.

<configSections>
   <sectionGroup name="RewriterConfig">
      <section name="RewriterRules"
         type="ConfigRewriter.RulesHandler,RulesConfigHandler" />
   </sectionGroup>
</configSections>

<!--   Custom Configuration Section for URL Rewriting -->
<RewriterConfig>
   <RewriterRules>
      <Rule>
      <OldPath>/ideas/calculator.aspx</OldPath>
      <NewPath>/utilities/calculator.aspx</NewPath>
      </Rule> 
      <Rule>

         <OldPath>/ideas/bmi.aspx</OldPath>
         <NewPath>/utilities/bmi.aspx</NewPath>
      </Rule> 
   </RewriterRules>
</RewriterConfig>

After the XML data structure is determined, the next step in developing a section handler is to create a class that is populated with the contents of the section and contains members that make the data available to an application. In this example, data is defined by any number of <Rule> elements. Consequently, the class must provide some sort of collection to return multiple values. We'll use a Hashtable for the purpose. The GetNewPath method accepts a string value that it uses as a key to retrieve and return its associated value from the hash table.

// File: SectionRules.cs
using System.Collections;
namespace ConfigRewriter
{
   // Class to contain content of configuration section
   public class RulesData
   {
      private Hashtable paths;
      // Constructor accepts hash table as parameter
      public RulesData (Hashtable pathCollection){
         paths = pathCollection; 
      }
      // Use old path as key and return hash table value
      public string GetNewPath(string OldUrl) 
      {
         return ((string)paths[OldUrl]);
      } 
   }
}

The final step, shown in Listing 17-4, is to create the section handler code. Its primary function is to implement the Create method. This method is passed an XMLNode parameter that corresponds to the <RewriterRules> node in our section. It is used to parse its child nodes and fill the hash table with its contents. An instance of the RulesData class is instantiated by passing the hash table to its constructor. This object is then available to a Web application.

Example 17-4. Configuration Section Handler

//File: RedirectConfigHandler.cs
using System.Xml;
using System.Configuration;
using System.Collections;
namespace ConfigRewriter
{
   public class RulesHandler: IConfigurationSectionHandler
   {
     public object Create(object parent, object input, 
                          XmlNode section)
     {
        Hashtable paths = new Hashtable();
        string oldUrl="";
        XmlElement root = (XmlElement) section;
        foreach (XmlNode node in root.ChildNodes){
           foreach(XmlNode child in node.ChildNodes) 
           {
              if(child.Name=="OldPath") oldUrl= child.InnerText;
              if(child.Name=="NewPath")
              {
                 paths[oldUrl]= child.InnerText;
              }
           }
        } 
        RulesData urlRules = new RulesData(paths);
        return urlRules;      
      }
   }
}

After the section has been constructed, accessing its content is trivial: use the ConfigurationSettings.GetConfig method to return an instance of the section object, and use its properties to retrieve values.

// Retrieve object returned by section handler
// and use it to access content of section
RulesData r;
r = ConfigurationSettings.GetConfig(
      "RewriterConfig/RewriterRules")
      as RulesData;
if(r!=null)
// Pass old path to method and receive redirected path
string newURL = r.GetNewPath("/ideas/bmi.aspx");

ASP.NET Application Security

Web-based applications must address many of the same security issues that are faced by applications situated on a local network. The most important of these—and the subject of this section—is client authentication. By this, we mean the capability to identify the user attempting to access a Web resource. Unlike traditional static Web sites that permit anonymous user access, an application that wants to tailor its content for the user, or restrict access based on authorization rights, must first authenticate the user.

ASP.NET offers three forms of client authentication:

  • Windows Authentication. Relies on the relationship between IIS (Internet Information Server) and the underlying operating system's directory security. Its options include using the native Microsoft Windows login authentication.

  • Microsoft Passport ServiceMicrosoft's proprietary “single sign-on” authentication service. Requires subscribing to the Passport network and installing the Passport SDK to link .NET to the service.

  • Forms Authentication. Allows an application to define user credentials and handle authentication based on it own requirements. This approach relies on .NET classes to create and manage a cookie that maintains user authentication information during the lifetime of a user's session.

The most flexible—and least proprietary—of these techniques is Forms Authentication. It represents the natural evolution from a do-it-yourself approach that requires creating a login screen, writing code to match user credentials against a database or file, and building a cookie to maintain client data during a session. ASP.NET offers substitutes for each of these tasks, which can be easily plugged into a developer's custom solution. The remainder of this section examines this form of authentication.

Forms Authentication

The general framework for implementing Forms Authentication is based on a mixture of configuration files, authentication modules, ASP.NET security classes, and the script (C# code) to work with the methods and properties of these classes. Figure 17-4 illustrates how it works.

Forms Authentication steps that occur when a page is first requested

Figure 17-4. Forms Authentication steps that occur when a page is first requested

A web.config file defines a login page to which a user's browser is redirected when it first attempts to access a Web resource. This login page accepts the client's credentials—usually name and password—and determines if they are valid. If so, the user is authenticated and ASP.NET methods are used to create a FormsAuthenticationTicket that contains the user's security information. The ticket is encrypted and stored as a cookie. The cookie may be configured to expire at the end of the session, or remain on the client's machine so that the user can be automatically identified in future sessions. An optional step in this process is to add authorization information about a client to the ticket. For example, users may be assigned to groups that define their access rights. The code to add this role information can be placed in the global.asax file to handle the application's AuthenticateRequest event.

After these steps are complete and the user is authenticated, control passes to the Web page originally requested. Subsequent session authentication uses information in the authentication cookie and avoids these steps.

Security Settings in web.config for Forms Authentication

The web.config file plays an important role in supporting authentication and authorization in ASP.NET security. Listing 17-5 presents many of its elements and optional attribute values that are used to select and implement a security method.

The first thing to note is the mode attribute in the <authentication> element, which specifies the type of authentication being used. Contained in the <authentication> section is a <forms> tag or <passport> tag (not shown) that corresponds to the type of authentication chosen. The <forms> tag, which is used in a later example, contains several key attributes. The loginurl specifies the login page that a user is directed to when she has not been authenticated. The timeout attribute specifies how long a user may wait between requests before the authentication ticket expires. Thus, if the value is set to 5 minutes and the user has not requested a page in that interval, she is forced to log in again at her next request. The protection attribute specifies how, or if, ticket authentication information is processed before it is written to a cookie. You'll normally set this to All.

The <forms> element may contain a <credentials> element that can be used as a mini-database to list each user and his password. The advantage of placing them here, as we see in a later example, is that .NET authentication classes provide methods to automatically perform authentication against these names. To add a measure of security for storing the passwords, the passwordFormat attribute is provided to specify how the passwords are encrypted.

Web.config may also contain an <authorization> section that explicitly allows or denies access to users based on their name or role (think of role as a group the user belongs to). The <allow> and <deny> tags are processed in the order they occur in the file. When the tag's policy can be applied to the user, processing stops. If no allow or deny policy matches the user, the user is authorized to access the resource.

Example 17-5. Authentication and Authorization Configuration Sections

<Configuration>
   <system.web>
      <authentication mode="{Windows|Forms|Passport|None}">
         <forms name="Cookiename"
            loginurl="path to login file"
            timeout="minutes"
            protection="{None|All|Encryption|Validation}"
            path="Cookiepath"  >
         <credentials passwordFormat="Clear|MDS|SHA1}">
            <user name="User Name" password="password" />
         </credentials>  
         </forms>
      </authentication>

      <authorization>
         <allow users="comma-separated list" 
            roles="roles list" />
         <deny  users="comma-separated list" 
            roles="roles list />

      </authorization>
      ...
   </system.web>
   ...
</configuration>

An Example of Forms Authentication

We'll now create a simple Web page that can only be accessed by users who provide a valid name and password. The example is then extended to demonstrate how client role information can be incorporated in the authorization process. As you work through the example, the most important things to observe are

  • The interrelationship between the web.config file and authentication methods used in the login screen code.

  • How roles can be assigned to users by the use of global.asax.

  • How to access authentication ticket information through the underlying .NET classes that manage authentication.

Using web.config for Authentication and Authorization

The configuration file segment in this example is used for both user authentication and authorization. The <credentials> element contains the names and passwords of three users who may access the system. The <authorization> element contains rules that deny login access to all anonymous users as well as one individual user. Only users Joanna and Ruth are permitted to access the Web page.

<system.web>
   <authentication mode="Forms">
      <forms name="AppCookie" 
         loginUrl="applogin.aspx" protection="All"
         timeout="10" path="/"   >
         <credentials passwordFormat="Clear">
            <user name="joanna" password="chicago" />
            <user name="ruth"   password="raleigh" />
            <user name="kim"    password="newyork" />
         </credentials>
      </forms>
   </authentication>
   <authorization>
      <deny users="?,kim" />
   </authorization>

The same authorization rights can be granted by a combination of allow/deny rules:

<allow users="joanna,ruth"  />
<deny users="*" />

This denies access to all users (the asterisk (*) wildcard selects all users) except for those that are explicitly allowed. When both <allow> and <deny> are present, the order in which they are placed can be important. In this example, placing the <deny> rule first would deny access to all users, and the <allow> rule would be ignored.

The Login Screen—Using the FormsAuthentication Class

The web.config file used in this example redirects the initial request for a Web page to applogin.aspx, which accepts login credentials and performs user authentication. The login part is easy—two text boxes are used to accept a user name and password. The technique for authenticating the user is more interesting.

Traditionally, login screens validate a user-supplied name and password against a database or file containing valid users. You are still free to do this in ASP.NET, but there are other options. These include storing names and passwords in the web.config file, or using the ASP.NET Membership class and its preconfigured database to manage user credentials. For demonstration purposes, this example uses web.config as the data store. Keep in mind that this is not the recommended approach if there are more than a few users, or multiple servers are used, which would require synchronizing the configuration file across machines.

As Listing 17-6 illustrates, the FormsAuthentication class plays the key role in authenticating a user and creating the authentication ticket that identifies the user. This important class provides static properties and methods that are used to manipulate and manage authentication information. Two of its methods are used here: Authenticate accepts a name and password, which it attempts to validate against the data in web.config; RedirectFromLoginPage redirects the user to the page originally requested, and creates an authentication cookie for the user. The second parameter to this method indicates whether the cookie should persist on the user's machine beyond the lifetime of the session.

Example 17-6. Login Screen Using ASP.NET Authentication

<%@ Page Language="C#" %>
<html>
<head>
<title>login</title>
<script  runat="Server">
  void verify_Form(object sender, System.EventArgs e)
  {
    // Code to verify user against a database goes here...
    // or use .NET to verify user using web.config info.
    // Name and password are compared against web.config content
    if( FormsAuthentication.Authenticate(
           txtName.Text,txtPW.Text))
    {
      // Redirect to original form and 
      // create authentication ticket
      bool persistCookie=false;
      FormsAuthentication.RedirectFromLoginPage(txtName.Text, 
            persistCookie);
    }
    else
    { errmsg.Text="Cannot log in user."; }
  }
</script>
</head>
<body >
<form id=Form1 method=post runat="server">
  <asp:Panel id="pnlName" 
        style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute; 
        TOP: 64px"
        BackColor = "#efefe4"
        Height="120px" Width="278px"
        runat="server" >
  <TABLE>
    <TR>
      <TD><asp:Label id="lblName" Runat="server" 
                 text="User ID:"></asp:Label></TD>
      <TD><asp:TextBox id="txtName" Runat="server"> 
             </asp:TextBox></TD></TR>
    <TR>
      <TD><asp:Label id="lblPW" Runat="server" 
                 text="Password:"></asp:Label></TD>
      <TD><asp:TextBox id="txtPW" TextMode="Password"
           Runat="server"> </asp:TextBox></TD></TR>
    <TR>
      <TD colspan=2 align=center>
          <asp:Button ID="btnSubmit" 
              Text="Submit" Font-Bold="true"
              OnClick="verify_Form" Runat="server" /></TD></TR>
    <TR>
      <TD colspan=2 align=center>
             <asp:Label id=errmsg runat="server" /></TD>
    </TR>
  </TABLE>
</asp:Panel>
</FORM>
</body>
</html>

Adding Role-Based Authorization with global.asax

Authentication is often only the first step in permitting a user to access Web resources; the second step is authorization—the process of determining what resources the user is authorized to access. If all users have full access, this step can be ignored. However, if a user's access rights, or role, are determined by membership in a group, this group must be identified and associated with the user.

After a user is created in ASP.NET, information about that user is available through the Page.User property. This property returns a principal object, so named because it implements the IPrincipal interface. This interface has two important members (see Figure 17-5) that the principal object implements: Identity, which can be used to get the name of the user, and the IsInRole method that is used to check a user's group membership.

User information is encapsulated in a GenericPrincipal object

Figure 17-5. User information is encapsulated in a GenericPrincipal object

The IsInRole method is passed a role name and returns a bool value indicating whether the user is in that role. It is the developer's task to assign the roles to the authenticated user object so that this method will have an internal list to check against. Looking at Figure 17-5, you would expect this list to be part of the GenericPrincipal object—and you would be right. The constructor for this class accepts as one of its parameters a string array of roles to be associated with the user. To assign these roles, the application can take advantage of the AuthenticateRequest event that occurs when the identity of a user is established. A handler for this event is placed in the global.asax file.

Listing 17-7 shows how code in the event handler assigns roles to the current user. It determines the user roles—in this case, calling GetRoles—and places them in a string array. This array, along with the current Identity, is passed to the GenericPrincipal constructor to create a new object that updates the value of the User property.

Example 17-7. Using global.asax to Add Role Information for a User

<%! file:global.asax %>
<%@ Import Namespace="System.Security" %>
<%@ Import Namespace="System.Security.Principal" %>
<%@ Application   %>
<script language = "C#" runat=server>
protected void Application_AuthenticateRequest(
                   object src, EventArgs e)
{
   if (Request.IsAuthenticated)
   {
      string currUser= Context.User.Identity.Name;
      string roles= GetRoles(currUser);
      string[] appRoles = roles.Split(new char[]{'|'});
      // Create GenericPrincipal class and add roles to it
      // GenericPrincipal(IIdentity identity, string[] roles)
      Context.User = new GenericPrincipal(
                         Context.User.Identity, appRoles);
   }
}
private string GetRoles( string username )
{
   // Code here would query database for user's role(s).
   // Return role as delimited string that can split into array.
   return "Administrator|Operator";
}

Viewing Authentication Information

After the authentication steps are complete, an application may want to log information or make decisions based on information about the user. The User property exposes all of the members shown in Figure 17-5. Further authentication details, such as when the authentication cookie expires or when it was issued, can be obtained from properties exposed by the authentication ticket. As shown in Listing 17-8, an instance of the cookie is created and then converted to a FormsAuthenticationTicket object by using the Decrypt method of the FormsAuthentication class. The properties of this object are then displayed.

Example 17-8. Displaying Authentication Details

// Display authentication details
Response.Write("Client: "+ User.Identity.Name+"<br>");
if(User.IsInRole("Operator")) Response.Write(
          "Role: Operator<br>");
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if(authCookie !=null)
{
   // Create ticket from cookie and display properties
   FormsAuthenticationTicket authTicket = null;
   authTicket = FormsAuthentication.Decrypt(authCookie.Value); 
   Response.Write("Issued: "+authTicket.IssueDate+"<br>");
   Response.Write("Expiration: "+authTicket.Expiration+"<br>");
   Response.Write("Persistent: "+authTicket.IsPersistent);
}

Maintaining State

The standard for the HTTP protocol is contained in a document known as RFC 2616. In printed form, it is more than 120 pages in length. If you search this document, you'll find that not one page, section, or sentence discusses a method for maintaining state information between HTTP requests. The term cookie is completely absent. So, unlike human memory where “our thoughts are linked by many a hidden chain” (Alexander Pope), the Internet protocol is designed to be stateless—each request unlinked to the preceding one.

Yet, for Internet applications to flourish, a means has to exist to identify specific users. Online purchasing, surveys, and the need to recognize user preferences depend on it. The first efforts to maintain state information relied on using features defined in the HTTP protocol: hidden fields that could be sent back and forth using the POST method, and a string full of values that could be placed after the ? character in an HTTP URL path. The former technique is used to maintain .NET ViewState information; the latter constitutes a “query string” consisting of name/value pairs.

A third client-side approach to managing state is the use of cookies, a small (4K) text-only string that is stored in browser memory during a session and may be written to disk. Many applications maintain session-related state information, such as a shopping cart's identification, in cookies. ASP.NET identifies each session by creating a cookie containing a unique session ID.

These approaches are plagued by a common weakness: They can be compromised by the client. Query strings and hidden fields can be altered; cookies can be altered or simply turned off on the browser.

To overcome these shortcomings, ASP.NET offers two general types of server-side state management: session state, which maintains information for the life of the session; and application state, which maintains information across multiple sessions. Although these techniques maintain information on a server—rather than passing it back and forth between requests—they still rely on a cookie, where possible, to uniquely identify a user's session. However, the server-based solution presents another problem: All session activity must be handled by that server possessing the state information. This is incompatible with large Web sites where a server-selection algorithm assigns requests to any machine in a Web farm. An ASP.NET solution to this is the use of a central database to store session state information.

Table 17-4 summarizes the client- and server-side state management approaches. Of those listed, this section focuses on the application and session management techniques.

Table 17-4. State Management Techniques for Web Applications

Technique

Web Farm

Compatible

Description

Query String

Yes

Data passed as part of URL.

View State

Yes

ASP.NET hidden View_State field used.

Cookie

Yes

Information passed to/from browser in a cookie.

Application

No

An Application object maintains information available to all sessions accessing an application.

Session: In-Process

No

Session state is stored and managed using the Aspnet_wp.exe process.

Session: State Server

Yes

Session state is stored in the Aspnet_state.exe process.

Session: SQL Server

Yes

State information is maintained in a temporary table in a database.

Application State

Application state data is maintained in an instance of the HttpApplicationState class—an in-memory dictionary holding key-value objects. The contents of this dictionary object are available to all sessions using that application.

The data is usually loaded within the global.asax file when the Application_ Start event fires.

protected void Application_Start(object sender, EventArgs e)
{
   Application["targetBMI"]= 21; 
   Application["obeseBMI"] = 26; 
}

Users of the application then have read and write access to the data without having to regenerate it. This is particularly useful when working with data from a database. It can also be a convenient way to initialize variables used for collecting usage statistics. (See Section 17.2 for background on global.asax file.)

Accessing and Updating Application State Data

All sessions using the application may access the Application object through the Application property of the System.Web.UI.Page type. The only thing to note about accessing data is that it must be cast, because the information is stored as an object. Here is a segment that illustrates the primary properties and methods for working with the Application object:

int target = (int) Application["targetBMI"];
// Change value
Application["targetBMI"] = 22;
// List all HttpApplicationState values
foreach( string s in Application.AllKeys){
       Response.Output.Write("<br>{0} = {1}",s,Application[s]);
}
// Remove Application item
Application.Remove("obeseBMI");

Not illustrated are the Lock and UnLock methods that allow application variables to be updated in a thread-safe manner.

Considerations in Using Application State

The variables contained in application state have two primary uses: as read-only data globally available across all of the application's sessions, and as variables that are updated to keep statistics regarding the application's use. Both of these uses can be handled more efficiently using a different approach.

The ASP.NET data cache provides a more flexible approach for making read-only data available on an application-wide basis. Its contents are as accessible as those in the application object, and it offers the advantage of having its contents automatically refreshed at a specified time interval. (Its use is described in Section 17.5.) As a rule-of-thumb, use application state to preload small amounts of read-only data required by an application; use the data cache when working with large amounts of data.

The problem with maintaining usage data in application state is that the data is available only during the life of the application. When it ends, the data is gone. In addition, if there are multiple servers, the data will apply to only one server. A better approach is store the information in a central database.

Core Note

Core Note

Application recycling refers to shutting down and restarting an application based on criteria such as time, memory usage, and number of client requests. It is set using the <processModel> tag in the web.config file. The following setting recycles the application every two hours.

<processModel timeout="120">

Session State

When a new client requests an ASP.NET application, ASP.NET creates a cookie containing a unique session ID and returns it to the client's browser. This ID, or session key, is used to identify subsequent requests from the client. State information unique to each session is maintained in an instance of the HttpSessionState object. The contents of this object are available through the Session properties of both the Page and HttpContext class. Note that Session can be used in place of the fully qualified HttpContext.Session. Working with session state is analogous to working with application state. Many applications perform state session initialization in a Session_Start event handler in the global.asax file. This segment initializes variables for an online survey:

// global.asax file
protected void Session_Start(object sender, EventArgs e)
{
   Session["Start"]= DateTime.Now; 
   Session["QuestionsAnswered"] = 0; 
}

The variables can be updated in the application with the same syntax used for the Application object:

int ict= (int)Session["QuestionsAnswered"]+1;
Session["QuestionsAnswered"]=ict;

The HttpSessionState class contains several members that are useful in manipulating and interrogating the Session object. The following code demonstrates their use:

// Session ID is unique to each session
Response.Write("<br>Session ID: "+Session.SessionID);
// Mode may be InProc, StateServer, SqlServer, Off
Response.Write("<br>Processing Mode: "+Session.Mode);
// Session times out after 20 minutes (default) of inactivity
Response.Write("<br> Timeout: "+Session.Timeout);
// Number of items in session state
Response.Write("<br>Items: "+Session.Count);
// List key-values in session state
foreach( string key in Session.Keys) 
      Response.Output.Write("<br>{0} = {1}",key, Session[key]);

The most interesting of these properties, by far, is Session.Mode, which indicates where the Session object is configured to store its data. Session state information can be stored using one of three methods: InProc, StateServer, or SqlServer. Although the choice does not affect the way information is accessed through the Session object, it does affect application performance, manageability, and extensibility. Let's look at the details of using all three backing stores (see Figure 17-6).

Session state data store options

Figure 17-6. Session state data store options

In-Process Session State

By default, ASP.NET stores session state in-process using the aspnet_wp.exe process. This means that the information is managed in memory space where the application is running. This has the advantage of providing fast access to the data. The drawbacks are that it must run on a single server—precluding its use with Web farms—and that all session data is lost if the server reboots. Given this, it is recommended for single-server sites that need to maintain moderate amounts of data in state.

To select in-process session state management, set the mode attribute to InProc in the <sessionState> element of the web.config file. As shown here, you can also specify a time limit specifying how long the process may be idle before the session data is discarded.

<configuration>
   <system.web>
      <sessionState mode="InProc" timeout="30"/>
   </system.web>
</configuration>

Out-of-Process Session Using a State Server Process

The concept of out-of-process session state management entails the use of a mechanism outside of the application's process to store state data. ASP.NET provides two such mechanisms: a separate process running on a selected state server and the use of a data server to store state information.

When session state mode is used, session state is stored in the Aspnet_ state.exe process. This allows an application running on multiple servers to share state information. Because the server running this process is accessed via a network on the Internet, performance is slower than using the in-process approach. Also, the data is susceptible to a server reboot.

To access a state server, make sure the machine selected is running the Aspnet_state.exe process. Any server accessing the state server must set the <sessionState> element in its web.config file to point to the state server. The default port used is 42424. On a Windows-based operating system, this can be changed through the registry.

<configuration>
   <system.web>
      <sessionState mode="StateServer"  
         stateConnectionString="192.168.1.109:42424"
      />
   </system.web>
</configuration>

Out-of-Process Session Using SQL Server

Using a SQL Server database to store session state data also offers the capability of sharing data among machines. In addition, SQL Server security features can be used to selectively provide access to the data; and, of course, the data is not transient because it exists in tables. The disadvantages of this approach are that it only works with Microsoft SQL Server and it is the slowest of the three session state modes.

To prepare a machine running SQL Server to handle session state requires no more than creating the tables that ASP.NET expects. The InstallPersistSqlState.sql script takes care of this. By default, the script is located in the systemrootMicrosoft.NETFrameworkversion folder. It creates a database named ASPState that contains the state tables.

Web.config is used to tell ASP.NET that session information for the application(s) is stored on a SQL Server. The <sessionState> element is set to specify the mode and connection string.

<configuration>
  <system.web>
    <sessionState mode="SQLServer"
      sqlConnectionString="datasource=x; user id=sa; password="/>
  </system.web>
</configuration>

SQL Server should be considered when there is a need to maintain data beyond the life of the session. For example, sites often persist the content of a shopping cart so it is available when the user logs in again.

Because the use of SQL Server slows Web page performance, it should be used only on those pages in an application that require state data. The @Page directive has an EnableSessionState attribute that dictates how state is used on the page. It can be set to false to disable session state, true to enable read and write session state, or readOnly.

Caching

Consider a Web page with a list box containing the names of a thousand movie actors whose movies are displayed when an actor is selected. The first time the page is requested, a query is sent to the database, the list box is populated with the results, and the rendered HTML is returned to the user's browser. This is a relatively slow and expensive process. Multiply it by a thousand user requests and Web server response time will slow measurably.

One solution is to use caching to bypass most, or all, of this reprocessing by keeping an HTML page or data in memory. Subsequent requests for the page are handled using the cached information—obviating the need to fetch data from the database. A sound caching strategy can improve Web server response more than any other single factor. This section presents the factors to be weighed in designing a caching strategy and the two broad categories of caching available in ASP.NET: output caching and data (or request) caching.

Page Output Caching

ASP.NET permits a developer to indicate whether all or part of a Web Form should be cached, where it should be cached, and how long it should remain in the cache before it expires and must be refreshed. The key to cache control for a Web Form is the @OutputCache directive:

<%@OutputCache Duration="30" Location="Any" 
   VaryByParam="none" %>

Its attributes declare the specific caching policies in effect for that Web page. Let's look at how these attributes are used.

Core Note

Core Note

ASP.NET processes the @OutputCache directive by translating its attributes into HTTPCachePolicy method calls.

Specifying Cache Duration

The mandatory Duration attribute specifies the length, in seconds, that the page or control is cached. Setting this to 20 seconds generates the underlying statement:

Response.Cache.SetExpires(DateTime.Now.AddSeconds(20));

Specifying the Caching Location

The Location attribute specifies where the caching can occur. In general, caching occurs on a server, browser, or somewhere in between, such as on a proxy server. The values of this attribute correspond to these locations: Any, Client, DownStream, Server, ServerAndClient, or None. DownStream refers to a cache-capable device other than the origin server. None disables caching everywhere.

Conditional Caching with VaryByParam, VaryByHeader, and VaryByCustom

In Chapter 16, we developed a BMI calculator Web page in which the user enters a height and weight value and the BMI value is returned. Because there are thousands of combinations of height (htf, hti) and weight (wt) parameters, several slightly different versions of the same page are likely to be created. The VaryByParam attribute is available for cases such as this. Its value indicates the parameter(s) (from a POST or query string) that should be considered when selecting a page to cache. For example, the following statement causes a page to be cached for each unique combination of wt, hti, and htf values:

<%@ OutputCache Duration="60"  VaryByParam="wt;hti;htf" %>

Figure 17-7 shows four requests that result in three unique pages being cached with their calculated BMI value. Note that we could have also assigned an asterisk (*) to VaryByParam to indicate that each parameter's value affects the cache.

Cachedpage: VaryByParam="wt;hti;htf"

Figure 17-7. Cachedpage: VaryByParam="wt;hti;htf"

Be aware that you can create some unexpected results if you set VaryByParam incorrectly. If only the wt parameter were specified in this example, requests with a weight of 168 and heights of 5'1" and 6'1" would return the same Web page. The page returned to both would be the one requested earliest.

VaryByHeader caches a different version of a page, based on the value of the Request Header fields (refer to Figure 17-1). This is the most commonly used with the Accept-Language field to ensure that different language versions of a Web page are cached.

The final conditional attribute to be familiar with is VaryByCustom. This is most useful when you have a Web page that is rendered differently depending on the client's browser type and version. To create different page versions by browser type, set the VaryByCustom attribute to "Browser".

<%@ OutputCache Duration="60"  VaryByParam="*"
                               VaryByCustom="Browser" %>

This attribute also can be set to recognize custom values that your program generates. Please refer to caching documentation for details on this.

Caching a Partial Page (Fragment Caching)

All parts of a Web page are not created equally. Some, such as headers and links, rarely change, whereas other sections, such as the latest stock exchange quotes, change by the minute. A desirable caching strategy is to identify the static objects on the page and cache only those. This can be done in ASP.NET by creating custom controls (.ascx files) for the sections of the Web page to be cached. Custom controls have their own OutputCache directive that determines how they are cached, irrespective of the form containing them.

To illustrate the concept, let's create a simple control to be embedded within a Web page (see Figure 17-8). This segment simply contains a couple of links to other Web sites.

<!-- File: CacheFrag.ascx  -->
<%@ OutputCache Duration="30" VaryByParam="None" %>
<b>My Links</b><br>
<a href=http://www.moviesites.org>Movies</a><br>
<a href=http://www.imdb.com>Movie Reviews</a>
<br>
Example of custom control with its own caching policy

Figure 17-8. Example of custom control with its own caching policy

The application Web page to hold this control displays a title and date. Note that its caching is turned off by setting Location to None. The result is that only the control is cached.

<!-- File: CacheMain  -->
<%@ Page Language="C#"  %>
<%@ OutputCache Location="None" VaryByParam="*" %>
<%@ Register TagPrefix="frag" TagName="fraglinks" 
                Src="CacheFrag.ascx" %>
<HTML>
   <HEAD><TITLE>Sample Web Site</TITLE>
   <body>
      <center> <b>Fragment Sample</b>
      <br>
      <% Response.Output.Write(DateTime.Now.ToString("f")); %>
      </center>
      <frag:fraglinks id="fragmentlink" runat="server" />
   </body>
</html>

Data Caching

Data caching, sometimes referred to as request caching, is often an alternative to using application state or ViewState to store read-only data. It is as easy to use as the Application object, and adds the flexibility of assigning expiration and priority values to the stored data.

Adding Items to a Data Cache

The simplest way to add data to a data cache is to use the key-value syntax of the Cache property of the Page class:

Cache["userid"] = "rigoletto";

The Cache.Insert method is often a better approach since it takes full advantage of the fact that each cache entry is actually an instance of the CacheEntry class. This class contains many useful properties that are set by passing parameters to the Insert method:

Syntax:

Example:

public void Insert(
   string key,
   object value,
   CacheDependency dependencies,
   DateTime absoluteExpiration,
   TimeSpan slidingExpiration,
   CacheItemPriority priority,
   CacheItemRemovedCallBack cb
);
Cache.Insert (
   "userid", 
   "rigoletto",
   null,
   DateTime.Now.AddMinutes(20),
   TimeSpan.Zero,
   CacheItemPriority.Normal,
      null
);

Either the absoluteExpiration or slidingExpiration parameter can be used to determine when the cached data expires. The former sets a specific date and time; the latter sets the expiration to occur a specified amount of time after the value is last accessed. To disable sliding expiration, assign the value Cache.NoSlidingExpiration.

The dependencies parameter allows cached data to be tied to files, directories, or other objects in the cache. Establishing this dependency causes the cached data to be removed when the object it is dependent upon changes. Thus, if cached data comes from a file and the file contents are modified, the data in the cache is removed.

string filename = "actors";
CacheDependency dep = new CacheDependency(fileName);
cache.Insert("key", "value", dep);

The priority parameter makes sense when you understand that data in a cache is not guaranteed to remain there. Because there is no limit on what can be placed in the cache, ASP.NET periodically invokes a resource scavenging process to remove less important data from the cache. The determination of what is important is based on the priority of the resource. By setting this priority to a CacheItemPriority enum value, you can reduce, or increase, its likelihood of being removed through scavenging. Enum values may be set to AboveNormal, BelowNormal, Default, High, Low, Normal, and NotRemovable.

Retrieving Items from the Data Cache

To retrieve data from the cache, simply specify the key (case is ignored) of the desired data:

String user = (string) Cache["userid"];

Because the data is stored as an object, it is necessary to cast the result. If no data exists for the key, a null value is returned.

Data Caching as a Substitute for ViewState

The true value of data caching comes into play when large amounts of data need to be saved to restore a page's state. As was explained in the previous chapter, ViewState automatically saves data and state information for Web controls on a page as long as the page posts to itself. Figure 17-9 illustrates this. In it, we have Web page A containing a list box populated with several hundred items from a database. In steps 1 and 2, a user performs some action that results in a modified version of the same page being returned to the client. State is retained automatically because the contents of the page's list box—and any other fields—are passed in the ViewState field.

Use a data cache to preserve information between separate pages

Figure 17-9. Use a data cache to preserve information between separate pages

Suppose the user selects an item in the list box on page A that requires loading page B to show more details about the item. The server constructs the description as page B and returns it to the browser, discarding page A. After viewing this page, the user now wants to return to A. In the absence of caching, page A must be reconstructed by retrieving data from the database. To avoid this, the contents of the list box can be cached prior to navigating to page B (step 3):

// Step 3: Save listbox data before making server request 
Cache["listbox"]= actors.Items;

In step 6, page A is constructed using data from the cache to restore the list box.

// Step 6: Fill list box from cache
if(Cache["listbox"] !=null){
   ListItemCollection li= (ListItemCollection)Cache["listbox"];
   for(int i=0;i< li.Count; i++){
      ListItem a= li[i];
      actor.Items.Add(a);
   }
}
Cache["listbox"] = null;  // Clear cache

The cache is cleared after the page is restored; otherwise, both the cache and view state will contain the contents of the ListBox. It is worth noting that another option is to turn view state off for the ListBox (EnableViewState=false) and rely on caching to maintain its content.

Core Note

Core Note

Data caching should be considered as a substitute for ViewState when data is read-only, or there is a large amount of it, or its update frequency is in minutes or hours rather than seconds, or when the data is not unique to individual users.

Creating a Web Client with WebRequest and WebResponse

As you would expect in a chapter on ASP.NET, the role of the Web client does not receive much attention. It's usually assumed to be a browser that requests a Web page specified by the user, and renders the returned HTML content. Despite the evolution of browser features, its primary role remains to display what it receives. It is of less use if you want to parse and extract portions of the received content—or want to examine the HTTP message headers. For this, you need a Web client—a program that communicates with an HTTP server and includes custom methods to perform operations on the response object. This section demonstrates how easy it is to write a Web client using .NET classes to handle the HTTP request/response duties.

WebRequest and WebResponse Classes

The System.Net namespace contains classes that are intended for applications involved with network communications. Included in these are the abstract WebRequest and WebResponse classes that contain generic properties to upload and download data given a specific Uniform Resource Identifier (URI). These classes are designed to support the response/request model—not a specific protocol. The details of that are left to descendant classes. To handle the HTTP protocol, we call on the HttpWebRequest and HttpWebResponse classes. Note that other classes are available to support the file:// URI scheme and FTP operations.

Web Client Example

This example accepts a URL, sends a request for its Web page, and uses the response object to display the server description, IP address(es) of the server, and source code for the requested Web page. Figure 17-10 shows the interface for the application.

Example using HttpWebRequest and HttpWebResponse classes

Figure 17-10. Example using HttpWebRequest and HttpWebResponse classes

The basic steps for communicating synchronously with the HTTP server are quite simple:

  1. Create an HttpWebRequest object that identifies the URL using the Create method:

    request = (HttpWebRequest) WebRequest.Create(url); 
    
  2. Get a response from the Internet resource using the WebRequest.GetResponse method:

    response = (HttpWebResponse) request.GetResponse(); 
    
  3. Get the Stream used to read the body of the response and read its contents.

    Stream s = response.GetResponseStream();
    string strContents = new StreamReader(s).ReadToEnd();
    

Note that request and response are cast to HttpWebRequest and HttpWebResponse types, respectively. As mentioned, these subclasses deal specifically with the HTTP protocol (see Listing 17-9).

Example 17-9. Using WebRequest and WebResponse to Scrape a Web Page

private void btnURL_Click(object sender, System.EventArgs e)
{
   // Fetch web page for requested URL
   HttpWebRequest request; 
   HttpWebResponse response; 
   if(txt_URL.Text.Length>0)
   {
      lblServer.Text="";
      tbIP.Text="";
      string serverPath= txt_URL.Text;
      string url="http://"+serverPath;
      // create a request to the url 
      request = (HttpWebRequest) WebRequest.Create(url); 
      request.Timeout= 7000; // timeout after 7 seconds
      try
      {
         response = (HttpWebResponse) 
                  request.GetResponse(); 
         lblServer.Text= response.Server;
         // Get a stream to send the web page source 
         Stream s = response.GetResponseStream(); 
         string strContents = new 
                   StreamReader(s).ReadToEnd(); 
         // Place Web page source in text box
         HTMLViewer.Text= strContents;  
         s.Close();
         ListIP(serverPath);   // List IP address(es)
      } 
      catch ( Exception ex)
      {
         lblServer.Text= ex.Message;
      }
   }
   else 
   {
      lblServer.Text= "Please enter a domain name.";
   }
}
private void ListIP(string uri)
{
   // List IP addresses for this domain
   // Use only server name part of URI for IP resolution
   int ndx= uri.IndexOf("/");
   if(ndx>0) uri= uri.Substring(0,ndx);
   string ips="";
   // Get a list of IP addresses for the URI
   // Dns contacts the Internet Domain Name System
   IPHostEntry IPHost = Dns.GetHostByName(uri);
   foreach(IPAddress addr in IPHost.AddressList)
   ips+= addr.ToString()+"
";
   tbIP.Text= ips;
}

You may encounter Web pages that check the UserAgent property of the request object and do not allow their pages to be downloaded unless they can identify the browser. You can assign a legitimate browser value to overcome this.

HTTP Pipeline

The purpose of this section is to provide background information about the components and classes that come into play between the time a Web server receives a request and the time it finishes crafting a response. An understanding of this HTTP pipeline can be crucial to improving Web application performance, because it enables a developer to create components that affect how ASP.NET responds to requests. The references to HTTP modules and handler factories can make the subject appear daunting, but underneath there is a simple logic at work that is rooted in event handling.

Processing a Request in the Pipeline

As a request makes its way to a server, several events occur. ASP.NET permits a developer to interact with the request or response at the point of these events—through special code referred to as an HTTP module or by script in a global.asax file. Both the module and file function as event handlers, and the nature of what goes on in these event handlers is the topic of this section. Figure 17-11 depicts how a request flows from the beginning of the HTTP pipeline to the endpoint, where a handler provides a response.

Handling a request within the HTTP pipeline

Figure 17-11. Handling a request within the HTTP pipeline

Let's take a simplified look at the sequence of events that occur.

  1. Request is received by Web server. The HTTP request for the bmi.aspx resource is passed to the ASP.NET Worker Process (aspnet_wp.exe). It creates an instance of the HttpRuntime class, which initiates processing of the request.

    The processing begins with the creation of an HttpContext object. You can think of this as the central repository of all information pertaining to the request. When another class needs information about the request, it can find it in an HttpContext field or property. In addition to the HttpRequest and HttpResponse fields, the class also exposes state information through HttpSessionState and HttpApplicationState properties.

  2. HttpApplicationFactory provides the HttpApplication object to process the requestAfter the HttpContext object is created, the HttpRuntime calls on the static factory method HttpApplicationFactory.GetApplicationInstance to create or find an HttpApplication object to process the request.

  3. HttpApplication is initialized with HTTP modules that filter request and response. HTTP modules are used to process a request and response as they pass through the pipeline. Predefined ASP.NET modules provide authentication, caching, and session state services.

  4. Locate handler factory. After the HttpApplication object takes over the processing from the HttpRuntime, it locates the HandlerFactory that finds or creates the HTTP handler object. It does this by examining the type of request (.aspx) and searching the machine.config file or web.config for the name of the handler that maps to this request type. Different handlers are needed for the different types of resources that may be requested. For example, a request for a Web page is handled differently than one for a text or XML file.

  5. Handler processes request. In our example, the handler class is the requested .aspx page. It may seem strange that the page is a handler, but the main requirement for a handler is that it implement the IHttpHandler interface—which the .aspx page does via its Page class.

  6. Response is constructed and returned. The ProcessRequest method of the handler is called to generate the response. This method takes an HttpContext object parameter that gives it access to the requested information needed to complete the processing.

As requests and responses move through the HTTP pipeline, an ordered chain of deterministic events—starting with the HttpApplication.BeginRequest event and concluding with the HttpApplication.EndRequest event—defines each stage of the processing. In addition, random error events can arise at any time from unhandled exceptions.

A developer can greatly improve the robustness and effectiveness of an application by selectively developing handlers for these events. Handlers are typically used to trap error events and direct them to a custom error page, log session lengths and number of users, authenticate users, and adorn pages with common header and footer information. These events can be handled using either a custom application class or custom HTTP modules. We'll look at both, and see how they compare.

A third place to customize the pipeline is at the endpoint, where an HTTP handler processes a resource request. As we will see, different resources, such as .aspx or .soap files, use their own handler components. ASP.NET makes it easy to write your own handler to manage special resources

HttpApplication Class

The HttpApplication object handles most of the duties required to process a request. Its properties expose caching and application/session state information, as well as a collection of predefined HttpModules. Equally important are the set of events it exposes. We can customize an application by including code in a global.asax file to handle these events.

Table 17-5 lists the HttpApplication events in the order that they occur in the HTTP pipeline. The Error event is an exception because it can occur at any time during the process.

Table 17-5. HttpApplication Events

Event

Description

BeginRequest

Fires when ASP.NET receives a request. A request can be modified here before it reaches the page.

AuthenticateRequest

The identity of the user has been established.

AuthorizeRequest

User authorization has been verified.

ResolveRequestCache

Fires when ASP.NET determines whether to handle request from a cached page or pass request to a handler.

AcquireRequestState

Acquires the state of the current session.

PreRequestHandlerExecute

Fires before the request is sent to a handler. In the case of a request for an .aspx file, the handler is the page itself.

PostRequestHandlerExecute

Fires after the handler has packaged a response. The response object contains the text returned to the client.

ReleaseRequestState

Fires after all request handlers have been executed and ASP.NET is ready to store session state information.

UpdateRequestCache

Data being sent to client is stored in the cache for future requests.

EndRequest

Occurs when request has been processed.

Error

Occurs in response to unhandled application exception.

Your code can provide a handler for these events by creating a component that uses a delegate to subscribe to the event or define event handlers in the global.asax file. We'll demonstrate the former approach when discussing HTTP modules, but first let's look at how to use the global.asax file.

global.asax File

This file is stored in the root of the virtual directory containing the application(s) it is to be associated with. Its primary purpose is to hold the handlers that respond to the Application object's events. It is similar to an .aspx file in that it is compiled into an assembly when the Web application is first accessed. Unlike the Web page, however, it inherits from the HttpApplication class.

Here is a simple global.asax file that displays copyright information at the bottom of each page returned by an application in its virtual directory. It does this by implementing a custom handler for the EndRequest event. Notice that the format of the event handler for this, and all Application events in the global.asax file, is Application_eventname.

<%! file:global.asax %>
<%@ Application Language="C#" %>
<script runat=server>
protected void Application_EndRequest(object sender, EventArgs e)
{
   this.Context.Response.Output.Write(
      "<br>&copy2005 BMI Software");
}
</script>

Global.asax supports four other important events that are not members of the HttpApplication class: Application_Start, Application_End, Session_Start, and Session_End. The purpose of these events, which should be clear from their name, is to mark the beginning and end of an application and the sessions using it. They are the most frequently implemented event handlers in the global.asax file—performing the bookend operations of state initialization and cleanup. To illustrate this, let's extend the preceding global.asax example to keep track of the number of sessions that request an application and display this session number below the copyright.

The session counters are kept in an HttpApplicationState object represented by the Application property of the HttpApplication base class. It acts like a dictionary to hold information during the lifetime of an application (refer to Section 17.4, “Maintaining State”).

As shown in Listing 17-10, counters that track the number of sessions and sessions cancelled are initialized to zero when the Application_Start event fires. The session counter is incremented when a Session_Start occurs, and the counter for terminated sessions is incremented when a Session_Ends event occurs.

Example 17-10. Using global.asax to Count Sessions

<%! global.asax %>
<%@ Application Language="C#" %>
<script language = "C#" runat=server>
// Can also use Application_OnStart syntax
void Application_Start(Object Sender, EventArgs e)
   {
      // Initialize counters with application scope
      Application["Sessions"] = 0;
      Application["TerminatedSessions"] = 0; 
   }
void Session_Start(Object Sender, EventArgs e)
   {
      Application.Lock();
      Application["Sessions"]=(int) Application["Sessions"] + 1;
      Application.UnLock();
   }
void Session_End(Object Sender, EventArgs e)
   {
      Application.Lock();
      Application["TerminatedSessions"] = 
         (int) Application["TerminatedSessions"] + 1;
      Application.UnLock();
   }
protected void Application_EndRequest(object sender,
                                      EventArgs e)
   {
      string sessionCt= "Session #: 
            "+Application["Sessions"].ToString();
      // Display copyright and current session number
      this.Context.Response.Output.Write(
            "<br>&copy2005 BMI Software<br>"+sessionCt);
   }
</script>

Using a Code-Behind File with global.asax

As with the .aspx file, code can be separated from the global.asax file and placed in a code-behind file. The code is compiled into a DLL and placed it in the in subdirectory of the application. The Inherits attribute of the Application directive points to the class implemented in the DLL.

To convert the preceding example to use a code-behind file, we remove code from the global.asax file, leaving only the Application directive:

<%! file:global.asax %>
<%@ Application  Inherits="sessionctr" %>

The code-behind file must be implemented as a class that inherits from HttpApplication. Here is a segment of the code:

// file: sessionctr.cs
using System;
using System.Web;
public class sessionctr: HttpApplication
{
   void Application_OnStart(Object Sender, EventArgs e)
   {
      Application["Sessions"] = 0;
      Application["TerminatedSessions"] = 0; 
   }
   // Remainder of assembly code goes here
}

HTTP Modules

An HttpModule is a component that implements the IHttpModule interface. The role of the module is similar to that of the global.asax file: It processes events (refer to Table 17-2) associated with response and request messages in the HTTP pipeline. In many cases, you can implement the same functionality using either the module or global.asax file. We discuss the factors that affect your choice later in this section.

Listing 17-11 shows the predefined modules that ship with ASP.NET. The physical modules (assemblies) are stored in the Global Assembly Cache (GAC) and are made available by listing them in the <httpModules> section of the machine.config file. They perform such diverse tasks as user authentication, caching, and state management. Although you do not work with the modules directly, they should give you an idea of the type of pre- and post-request processing that can be performed.

Example 17-11. HTTP Modules in machine.config File

<httpModules>
   <add name="OutputCache" 
      type="System.Web.Caching.OutputCacheModule" />
   <add name="Session" 
      type="System.Web.SessionState.SessionStateModule" />
   <add name="WindowsAuthentication" 
      type="System.Web.Security.WindowsAuthenticationModule" />
   <add name="FormsAuthentication" 
      type="System.Web.Security.FormsAuthenticationModule" />
   <add name="PassportAuthentication" 
      type="System.Web.Security.PassportAuthenticationModule" />
   <add name="UrlAuthorization" 
      type="System.Web.Security.UrlAuthorizationModule" />
   <add name="FileAuthorization" 
      type="System.Web.Security.FileAuthorizationModule" />
   <add name="ErrorHandlerModule" 
      type="System.Web.Mobile.ErrorHandlerModule,
      System.Web.Mobile, Version=1.0.5000.0, Culture=neutral,
      PublicKeyToken=b03f5f7f11d50a3a" />
</httpModules>

Implementing a Custom HttpModule

There are two minimal requirements for building a functioning HttpModule: It must contain a class that implements the IHttpModule interface and it must register an event handler to process one of the HttpApplication events. An example of this is shown in Listing 17-12. The code adds copyright information to the bottom of all pages returned by the application—exactly the same function performed by the earlier global.asax file.

The IHttpModule interface consists of two methods—Init and Dispose—that must be accounted for. We use Init to register the event handler that will be called when the EndRequest event occurs. Note that access to the events is provided through the HttpApplication object that Init receives as a parameter.

Example 17-12. Module to Append Copyright to Web Pages

using System;
using System.Web;
namespace CustomModules{
public class FooterModule: IHttpModule
{
   public void Init(HttpApplication httpApp)
   {
      httpApp.EndRequest += new EventHandler(this.OnEndRequest);
   }
   public void Dispose() {}
   // 
   public void OnEndRequest (Object obj, EventArgs e)
   {
      HttpApplication httpApp = (HttpApplication) obj;
      httpApp.Context.Response.Output.Write(
               "<br>&copy2005 BMI Software");
   }
}
}

Deploying a Custom HttpModule

To use the module with a Web application, compile it and place it in the in subdirectory of the application or in the Global Assembly Cache (GAC). Next, the assembly must be registered in either the web.config or machine.config file. In this example, we use a web.config file, which is placed in the root of the application's virtual directory. A portion of the file is shown here:

<configuration>
   <system.web>
      <httpModules>
         <add name="copyright"
         type="CustomModules.FooterModule, footmodule" />
      </httpModules>
   </system.web>
</configuration>

The key part is the <add> element that takes the form:

<add name=friendly name  type=class name, assembly />

This element is enclosed by the <httpModules> elements. Multiple <add> elements can be used to specify multiple modules.

URL Rewriting with a Custom Module

URL rewriting is the process of changing a requested URL so that it is redirected to another resource. This is commonly used to ensure that bookmarked links continue to work when directories and resource names are changed on a Web server.

An HTTP module provides a convenient and easy-to-use approach for rewriting a URL. The implementation logic in the module is straightforward:

  1. An HttpApplication event invokes event handler code.

  2. The event handler examines the Context.Request.Path property for the requested path.

  3. If the path URL needs to be redirected, the Context.RewritePath method is called with the substitute path.

The most important issue to be resolved in developing a rewriter is selecting an event in the request's lifecycle where the URL checking and modification should occur. The three HttpAppliction event candidates are shown in Table 17-6, along with the criteria for selecting them. (Authentication is discussed in Section 17.5.)

Table 17-6. Event Choices for Implementing a URL Rewriter Module

Event

Criteria for Selecting Event

BeginRequest

Use unless forms authentications is also used.

AuthenticateRequest

Use if Windows authentication is used.

AuthorizeRequest

Use if Forms Authentication is used and Windows authentication is not.

In general, if no authentication is being used, place the rewrite logic in an event handler for the BeginRequest event.

Listing 17-13 contains code for a simple rewriter module with the rewriting logic included in the AuthorizeRequest event handler. Look closely at this code. It first calls a static method Rewrites.getRewrites() that returns a hash table in which the keys are old URLs and the associated value is the replacement URL. The hash table is checked to see if it contains the currently requested path and if it's in the table, a call is made to Context.RewritePath. The new URL is passed to it, and the method automatically takes care of details such as including any query string with the new URL.

Example 17-13. Custom HTTP Module to Rewrite a URL

using System;
using System.Web;
using System.Collections;
namespace CustomModules
{
   public class ReWriteModule: IHttpModule
   {
      public void Init(HttpApplication httpApp)
      {
         httpApp.AuthorizeRequest += new 
               EventHandler(this.OnAuthorizeRequest);
      }
      public void Dispose() {}
      // Determine if requested URL needs to be rewritten
      public void OnAuthorizeRequest( Object obj, EventArgs e)
      {
         // Get hash table containing old and replacement URLs
         Hashtable urls = Rewrites.getRewrites();
         HttpApplication httpApp = (HttpApplication) obj;
         // Get path of requested URL
         string path = httpApp.Context.Request.Path.ToUpper();
         // See if path is in hash table 
         if(urls.ContainsKey(path))
            path= (string)urls[path];
         httpApp.Context.RewritePath(path);   // Rewrite URL
      }
   }
}

The module is registered by placing the following entry in the web.config file:

<add name="redirector"
       type="CustomModules.ReWriteModule, rewritemodule" />

The process of identifying URLs to be rewritten can be implemented in various ways. This example stores the old and replacement URLs in an XML file using the following format:

<!--  File: redirector.xml   -->
<RewriterConfig>
   <RewriterRules>
      <Rule>
         <OldPath>/ideas/calculator.aspx</OldPath>
         <NewPath>/utilities/calculator.aspx</NewPath>
      </Rule>
      <Rule>
         <OldPath>/ideas/bmi.aspx</OldPath>
         <NewPath>/utilities/bminew.aspx</NewPath>
      </Rule>
   </RewriterRules>
</RewriterConfig>

This information could also be stored in the web.config file, although some overhead is required to do this. For details, refer to “Adding a Custom Configuration Section” on page 824.

Choosing Between an HTTP Module and global.asax

In many cases, the functionality you want to add to a Web server environment can be implemented using HTTP modules or the global.asax file. As a guide to making the choice, let's review their features:

  • Both can contain handlers to service HttpApplication events.

  • Global.asax can receive notification of session and application start and end events. An HTTP module cannot service these events.

  • Modules are deployed at the machine (machine.config) or virtual directory (web.config) level; global.asax operates at a directory level only.

Either can be used to handle HttpApplication events. If the feature being added applies to all applications on the server, a module is the better choice; if the feature is specific to an application, use a global.asax file.

HTTP Handlers

When a request is made for a resource, ASP.NET directs control to the appropriate handler for that resource. For example, if the request is for an .aspx file, control passes to the PageHandlerFactory component, which returns the requested .aspx page. The default ASP.NET handlers are registered in the machine.config file within the <httpHandlers> section. An extract of that file shows how the resource type is mapped to the class capable of creating a handler for it:

<httpHandlers>
   <add verb="*" path="trace.axd" 
           type="System.Web.Handlers.TraceHandler" />
   <add verb="*" path="*.aspx"
           type="System.Web.UI.PageHandlerFactory" />
   <add verb="*" path="*.ashx"
           type="System.Web.UI.SimpleHandlerFactory" />
   <add verb="GET,HEAD" path="*"
           type="System.Web.StaticFileHandler" />
   ...
</httpHandlers>

An HTTP handler is a component that implements the System.Web.IhttpHandler interface. Unlike an HTTP module, only one handler is called to process a request. ASP.NET maps a request to the target handler based on information in a machine.config or web.config file. The request can be mapped directly to a handler or to a handler factory that creates or retrieves the appropriate handler. As shown in the preceding machine.config file, .aspx requests are handled by a PageHandlerFactory class. We'll look at both techniques.

Implementing a Custom HTTP Handler

ASP.NET provides handlers for .aspx, .soap, .asmx (Web services), and other standard ASP.NET file types. So why create a custom handler? The primary motivation is to support new file extensions or existing file extensions that require added processing features. For example, when a text file (.txt) is requested, ASP.NET simply returns the contents of the file. As an exercise, let's improve this with a handler that accepts a URL with the name of the text file and a query string parameter that specifies a keyword to search for in the file. The output from the handler is a listing of the text file with all instances of the keyword highlighted, as well as a count of the number of times the keyword occurs in the document.

The IHttpHandler interface contains two members that must be implemented in our custom handler class: the ProcessRequest method and the Reuseable property. As shown in Listing 17-14, ProcessRequest contains the code that processes the HTTP request; IsReusable indicates whether the instance of the handler can be reused for other requests. This is applicable only when a handler factory is providing handlers and pools them for reuse.

This handler responds to URL requests that contain a text file and query string containing a keyword to search for in the file:

HTTP://www.scilibrary.com/films.txt?kwd=Bogart

The context object provides access to the file name through the Request.PhysicalPath property. An attempt is made to open the text file and read it line by line. Each line is searched for the keyword using Regex.Match. If a match is found, the method BoldWord is called to place HTML bold (<b>) tags around the text. This method also increments the word counter.

Example 17-14. Custom HTTP Handler to Display a Text File

// file: txthandler.cs
using System;
using System.Web;
using System.IO;
using System.Text.RegularExpressions;
public class TextHandler: IHttpHandler
{
   static int wordCt=0;
   static string BoldWord(Match m) 
   {
      // Get the matched string and place bold tags around it
      string kwd = m.ToString();
      kwd="<b>"+kwd+"</b>";
      wordCt += 1;
      return kwd;
   }
   public void ProcessRequest(HttpContext ctx)
   {
   // Get file to be opened
   string filePath= ctx.Request.PhysicalPath;
   int ndx= filePath.LastIndexOf(@"");
   string fileName = filePath.Substring(ndx+1);
   // Keyword to search for in file
   string keyWord = ctx.Request["kwd"];
   // Create HTML response
   ctx.Response.Output.Write("<html><body >");
   ctx.Response.Output.Write("File: "+fileName+
            " &nbsp;Keyword: <b>"+keyWord+"</b><hr size=1>");
   string line;
   try {
      StreamReader reader= new StreamReader(filePath);
      // Read lines of file and display in response 
      while ((line = reader.ReadLine()) != null) 
      {
         if (keyWord!= null) {
         // search for keyword and highlight
            string newLine = Regex.Replace(line, keyWord,
                 new MatchEvaluator(TextHandler.BoldWord), 
                                    RegexOptions.IgnoreCase);
            line= newLine;
         }
         ctx.Response.Output.Write("<br>"+line);
      }
      reader.Close();
   } catch (Exception e)
   {
      ctx.Response.Output.Write("<br>"+e.Message);
   }
   // Display number of matches
   ctx.Response.Output.Write("<br><br>Word Matches: " + 
          wordCt.ToString());
   ctx.Response.Output.Write("</body></html>");
   wordCt=0; // reset since it is static
   }
   public bool IsReusable
   {
      get {return false;}
   }
}

The final code is compiled and placed in the in subdirectory of the application.

Deploying a Custom HTTP Handler

For ASP.NET to be aware of the handler, an entry is made in the machine.config or application's web.config file. In this example, we place an <add> element in the web.config file. This element contains three attributes that define the handler and the conditions for using it. Verb specifies the type of request to be handled, such as GET or POST. The * is a wildcard character that represents any type request. The path attribute indicates the requested resource name that is mapped to the handler; and type specifies the handler class or handler factory and its containing assembly.

<httpHandlers>
   <add verb="*" path="*.txt" type="TextHandler,txthandler"/>
</httpHandlers>

The final step in deployment is to make IIS (Internet Information Service) aware that requests for the .txt extension are to be handled by ASP.NET. This is done by using Internet Services Manager to map the file extension to the ISAPI extension DLL (aspnet_isapi.dll). Follow these steps:

  1. Invoke Internet Services Manager and right-click Default Web Site.

  2. Select Properties – Home Directory – Configuration.

  3. Click Add and a pop-up window appears.

  4. Fill in the Executable field with the path to the aspnet_isapi.dll file. This file should be in the version directory of the Framework installation.

  5. Fill in the Extension field with *.txt.

With these steps completed, TextHandler is now called to process all requests for .txt files. Figure 17-12 shows output from a sample request.

Output from HTTP handler that displays text files

Figure 17-12. Output from HTTP handler that displays text files

Core Note

Core Note

If an extension used by an HTTP handler is not mapped in IIS to ASP.NET, IIS attempts to return the contents of any file requested having that extension. ASP.NET never sees the request.

Summary

The ASP.NET environment offers a variety of types and configuration files that enable a developer or software architect to customize how Web requests are handled by a server. Configuration files such as web.config provide a flexible and easy way to customize the ASP.NET settings. These files contain sections that enable/disable tracing, specify the default culture of a page, define the way session state information is stored, and define an authentication/authorization security model. The security model enables an application to limit page access to users based on credentials such as their name, password, and role. Credential information may be stored in the web.config file, an XML file, or an application's proprietary database.

ASP.NET provides several mechanisms for handling both Application and Session state data. The application data is available to all sessions using that application; session data is limited to the individual session and can be configured to support Web farms and permanent storage in a database. An alternative to using an application state object is data caching. ASP.NET provides an area in memory where CacheEntry objects are stored and made available to all sessions. Output caching is also supported to enable an entire Web page or Web page fragment to be stored in a cache on the server, a proxy server, or the browser. This reduces the load on a server by allowing a Web page to be retrieved from memory rather than being recreated on the server.

Advanced Web application development can benefit from an understanding of the underlying ASP.NET architecture that supports HTTP communications. Central to this is the HTTP pipeline through which requests and responses are transported. After a request or response enters this pipeline, it can be parsed, modified, rerouted, or rejected. Both the global.asax file and HTTP module can be designed to respond to pipeline events. The event handlers they supply are used to do such things as append standard information to all responses, maintain Web statistics, and transparently reroute requests when necessary. At the end of the pipeline, a handler is responsible for providing the requested resource. Custom handlers can be written to handle specially defined resources or enhance the existing handlers.

Test Your Understanding

1:

True or False: A Web application requires at least one web.config file in its directory tree.

2:

Name the two places that trace output can be viewed.

3:

Regarding ASP.NET security:

  1. List the three types of .NET authentication available.

  2. Where is the authentication type specified?

  3. What is the difference between authentication and authorization?

  4. Which property of the Page class provides information about an authenticated user?

4:

Which mechanism(s) for storing session state information supports multiple servers?

5:

You have a reasonably static Web page that is rendered differently for different browsers and you want to refresh the cached page every three hours. What would be included in your OutputCache directive for this page?

6:

What factors influence whether state data is maintained in a data cache or an application state variable?

7:

What is resource scavenging, and how can a data cache prevent it from occurring?

8:

Fill in the blanks:

As a _________ or __________ passes through the HTTP pipeline, an __________ object fires a series of events that may be handled by a/an ___________ or in the _________ file.



[1] RFC 2616—Hypertext Transport Protocol—HTTP/1.1

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

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