Chapter 27. Working with the HTTP Runtime

<feature>

IN THIS CHAPTER

</feature>

This chapter tackles a number of advanced topics by digging deeper into the mechanics of how an ASP.NET page is processed. In this first section, you learn how to create a custom BuildProvider. A BuildProvider is a .NET class that generates source code from a file automatically. You learn how to create a custom BuildProvider that builds custom data access components automatically.

Next, you learn how to create a custom ExpressionBuilder. An ExpressionBuilder is responsible for parsing an expression into code. For example, when you use the <%$ ConnectionStrings:MyDatabase %> syntax to refer to a connection string, you are using the ConnectionStringExpressionBuilder in the background. In this chapter, you learn how to build a custom ExpressionBuilder that looks up values from an XML file.

You also learn how to work with HTTP Handlers. An HTTP Handler is a .NET class that executes whenever a request is made for a file at a certain path. For example, you can use a custom HTTP Handler to retrieve an image from a database table whenever someone requests a file with the extension .gif or .jpeg.

Finally, you learn how to create custom HTTP Modules. An HTTP Module is a .NET class that executes with each and every request. For example, you can implement a custom authentication system by creating a custom HTTP Module. You also can use a custom HTTP Module to create a custom logging module.

Creating a Custom BuildProvider

When you write an ASP.NET page and save the page to your computer’s file system, the ASP.NET page gets compiled dynamically into a .NET class in the background. The page is compiled dynamically by a BuildProvider.

The ASP.NET Framework includes a number of BuildProviders. Each BuildProvider is responsible for compiling a file with a particular extension that is located in a particular type of folder. For example, there are BuildProviders for Themes, Master Pages, User Controls, and Web Services.

When a BuildProvider builds, it builds a new class in the Temporary ASP.NET Files folder. Any class added to the folder becomes available to your application automatically. When you use Visual Web Developer, any public properties and methods of the class appear in Intellisense.

You can create your own BuildProviders. This can be useful in a variety of different scenarios. For example, imagine that you find yourself building a lot of ASP.NET pages that display forms. You can tediously build each ASP.NET page by hand by adding all the necessary form and validation controls. Alternatively, you can create a new BuildProvider that takes an XML file and generates the form pages for you automatically.

Or, imagine that you are spending a lot of time building data access components. For example, every time you need to access a database table, you create a new component that exposes properties that correspond to each of the columns in the database table. In this case, it would make sense to create a custom BuildProvider that generates the data access component automatically.

Creating a Simple BuildProvider

Let’s start by creating a really simple BuildProvider. The new BuildProvider will be named the SimpleBuildProvider. Whenever you create a file that has the extension .simple, the SimpleBuilderProvider builds a new class with the same name as the file in the background. The dynamically compiled class also includes a single method named DoSomething() that doesn’t actually do anything.

The SimpleBuildProvider is contained in Listing 27.1.

Example 27.1. App_CodeCustomBuildProvidersSimpleBuildProvider.cs

using System;
using System.Web.Compilation;
using System.CodeDom;
using System.IO;

namespace AspNetUnleashed
{
    public class SimpleBuildProvider : BuildProvider
    {
        public override void GenerateCode(AssemblyBuilder ab)
        {
            string fileName = Path.GetFileNameWithoutExtension(this.VirtualPath);
            string snippet = "public class " + fileName + @"
                {
                    public static void DoSomething(){}
                }";
            ab.AddCodeCompileUnit(this, new CodeSnippetCompileUnit(snippet));
        }

    }
}

All BuildProviders must inherit from the base BuildProvider class. Typically, you override the BuildProvider class GenerateCode() method. This method is responsible for generating the class that gets added to the Temporary ASP.NET Files folder.

An instance of the AssemblyBuilder class is passed to the GenerateCode() method. You add the class that you want to create to this AssemblyBuilder by calling the AssemblyBuilder.AddCodeCompileUnit() method.

In Listing 27.1, a CodeSnippetCompileUnit is used to represent the source code for the class. Any code that you represent with the CodeSnippetCompileUnit is added, verbatim, to the dynamically generated class. This approach is problematic.

Unfortunately, you can use the SimpleBuildProvider in Listing 27.1 only when building a C# application. It doesn’t work with a Visual Basic .NET application. Because the code represented by the CodeSnippetCompileUnit is C# code, using the SimpleBuildProvider with a Visual Basic .NET application would result in compilation errors. The SimpleBuildProvider would inject C# code into a Visual Basic .NET assembly.

The proper way to write the SimpleBuildProvider class would be to use the CodeDom. The CodeDom enables you to represent .NET code in a language neutral manner. When you represent a block of code with the CodeDom, the code can be converted to either C# or Visual Basic .NET code automatically. You learn how to use the CodeDom when we build a more complicated BuildProvider in the next section. For now, just realize that we are taking a shortcut to keep things simple.

When you add the SimpleBuildProvider to your project, it is important that you add the file to a separate subfolder in your App_Code folder and you mark the folder as a separate code folder in the web configuration file. For example, in the sample code on the CD that accompanies this book, the SimpleBuildProvider is located in the App_CodeCustomBuildProviders folder.

You must add a BuildProvider to a separate subfolder because a BuildProvider must be compiled into a different assembly than the other code in the App_Code folder. This makes sense because a BuildProvider is actually responsible for compiling the other code in the App_Code folder.

The web configuration file in Listing 27.2 defines the CustomBuildProviders folder and registers the SimpleBuildProvider.

Example 27.2. Web.Config

<configuration>
    <system.web>

      <compilation>
        <codeSubDirectories>
          <add directoryName="CustomBuildProviders"/>
        </codeSubDirectories>
        <buildProviders>
          <add extension=".simple" type="AspNetUnleashed.SimpleBuildProvider" />
        </buildProviders>
       </compilation>

    </system.web>
</configuration>

The web configuration file in Listing 27.2 associates the SimpleBuildProvider with the file extension .simple. Whenever you add a file with the .simple extension to the App_Code folder, the SimpleBuildProvider automatically compiles a new class based on the file.

Note

Build Providers execute at different times depending on the type of folder. Build Providers associated with the App_Code folder execute immediately after a new file is saved. (Oddly, the Build Provider executes twice.) Build Providers associated with the Web or App_Data folders execute when a file is requested.

For example, adding the file in Listing 27.3 to your App_Code folder causes the SimpleBuildProvider to create a new class named Mike.

Example 27.3. App_CodeMike.simple

Hello!
Hello!
Hello!

The actual content of the file that you create doesn’t matter. The SimpleBuildProvider ignores everything about the file except for the name of the file.

You can see the new file created by the SimpleBuildProvider by navigating to the Sources_App_Code folder contained in the folder that corresponds to your application in the Temporary ASP.NET Files folder. The contents of the auto-generated file are contained in Listing 27.4.

Example 27.4. mike.simple.72cecc2a.cs

#pragma checksum "C:Chapter27CodeCSApp_CodeMike.simple" "{406ea660-64cf-4c82-
b6f0-42d48172a799}" "AD2E00BE337DD88E4E4B07F6B4580617"
public class Mike
{
  public static void DoSomething(){}
}

Any class added to the Temporary ASP.NET Files folder is available in your application automatically. For example, the page in Listing 27.5 uses the Mike class.

Example 27.5. ShowSimpleBuildProvider.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        Mike.DoSomething();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show SimpleBuildProvider</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    </div>
    </form>
</body>
</html>

The Mike class appears in Intellisense. For example, if you type Mike followed by a period, the DoSomething() method appears (see Figure 27.1).

Using a BuildProvider to generate a class dynamically.

Figure 27.1. Using a BuildProvider to generate a class dynamically.

Creating a Data Access Component BuildProvider

In the previous section, we created a simple but useless BuildProvider. In this section, we create a complicated but useful BuildProvider. The DataBuildProvider generates a data access component automatically from an XML file. For example, if you add the XML file in Listing 27.6 to your project, then the DataBuildProvider generates the class in Listing 27.7 automatically.

Example 27.6. App_CodeMovie.data

<Movies>
  <add name="Title" />
  <add name="Director" />
  <add name="BoxOfficeTotals" type="Decimal" />
</Movies>

Example 27.7. movie.data.72cecc2a.cs

#pragma checksum "C:Documents and SettingsSteveMy DocumentsASP.NET 3.5
  UnleashedChapter27CodeCSApp_CodeMovie.data" "{406ea660-64cf-4c82-b6f0-
  42d48172a799}" "2E0F31E6B8F9D4B8687F94F0305E6D15"
//———————————————————————————————————————
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.1378
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//———————————————————————————————————————

namespace Data {
    using System;

    public partial class Movie {

        private string _Title;
        private string _Director;
        private Decimal _BoxOfficeTotals;

        public Movie() {
        }

        public virtual string Title {
            get {
                return this._Title;
            }
            set {
                this._Title = value;
            }
        }

        public virtual string Director {
            get {
                return this._Director;
            }
            set {
                this._Director = value;
            }
        }

        public virtual Decimal BoxOfficeTotals {
            get {
                return this._BoxOfficeTotals;
            }
            set {
                this._BoxOfficeTotals = value;
            }
        }

        /// <summary>Returns List of Movie</summary>
        public static System.Collections.Generic.List<Movie> Select(System.Data.SqlClient.SqlConnection con) {
            System.Collections.Generic.List<Movie> results = new System.Collections.Generic.List<Movie>();
            System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient. SqlCommand();
            cmd.Connection = con;
            string cmdText = "SELECT Title,Director,BoxOfficeTotals FROM Movies";
            cmd.CommandText = cmdText;
            System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader();
            int counter;
            for (counter = 0; reader.Read(); counter = (counter + 1)) {
                Movie record = new Movie();
                record.Title = ((string)(reader["Title"]));
                record.Director = ((string)(reader["Director"]));
                record.BoxOfficeTotals = ((Decimal)(reader["BoxOfficeTotals"]));
                results.Add(record);
            }
            return results;
        }

        /// <summary>Returns List of Movie</summary>
        public static System.Collections.Generic.List<Movie> Select(string connectionStringName) {
            System.Collections.Generic.List<Movie> results = new System.Collections.Generic.List<Movie>();
            System.Configuration.ConnectionStringSettings conStringSettings = System.Web.Configuration.WebConfigurationManager.ConnectionStrings [connectionStringName];
            string conString = conStringSettings.ConnectionString;
            System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection();
            con.ConnectionString = conString;
            try {
                con.Open();
                results = Movie.Select(con);
            }
            finally {
                con.Close();
            }
            return results;
        }
    }
}

The XML file in Listing 27.6 contains the name of a database table (Movies) and a list of columns from the database table. When you add the file in Listing 27.6 to your project, the class in Listing 27.7 is generated automatically.

The data access component in Listing 27.7 contains a property that corresponds to each of the columns listed in the Movie.data file. Furthermore, each property has the data type specified in the Movie.data file.

Notice, furthermore, that the Movie data access component includes two Select() methods. You can retrieve all the records from the Movies database table in two ways: by passing an open SqlConnection object to the Select() method or by passing the name of a connection string defined in the web configuration file to the Select() method.

The page in Listing 27.8 illustrates how you can use the Movie data access component within an ASP.NET page (see Figure 27.2).

Displaying data returned by a dynamically generated data access component.

Figure 27.2. Displaying data returned by a dynamically generated data access component.

Example 27.8. ShowDataBuildProvider.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    void Page_Load()
    {
        grdMovies.DataSource = Data.Movie.Select("Movies");
        grdMovies.DataBind();
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdMovies"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Unlike the SimpleBuildProvider created in the previous section, the DataBuildProvider uses the CodeDom to represent code. This means that you can use the DataBuildProvider in both Visual Basic .NET and C# applications. The DataBuildProvider generates the data access component in different languages automatically. For example, if you use the DataBuildProvider in a C# application, the BuildProvider generates the code in Listing 27.6 in C#.

Unfortunately, the code for the DataBuildProvider is much too long to include here. The entire code is included on the CD that accompanies the book. The file in Listing 27.9 contains part of the DataBuildProvider code.

Example 27.9. DataBuildProvider.cs (Partial)

using System;
using System.Collections.Generic;
using System.Web.Compilation;
using System.CodeDom;
using System.Xml;
using System.IO;
using System.Web.Hosting;

namespace AspNetUnleashed
{
    public class DataBuildProvider : BuildProvider
    {
        string _className;

        public override void GenerateCode(AssemblyBuilder ab)
        {
            // Load the XML file
            XmlDocument xmlData = new XmlDocument();
            xmlData.Load(HostingEnvironment.MapPath(this.VirtualPath));

            // Generate code from XML document
            CodeCompileUnit dataCode = GetDataCode(xmlData);

            // Add the code
            ab.AddCodeCompileUnit(this, dataCode);
        }

        private CodeCompileUnit GetDataCode(XmlDocument xmlData)
        {
            // Add class
            _className = Path.GetFileNameWithoutExtension(this.VirtualPath);
            CodeTypeDeclaration dataType = new CodeTypeDeclaration(_className);
            dataType.IsPartial = true;

            // Add constructor
            AddConstructor(dataType);

            // Add properties
            AddProperties(dataType, xmlData);

            // Add Select method
            AddSelect(dataType, xmlData);

            // Add Select with conString overload
            AddSelectConString(dataType, xmlData);

            // Create namespace
            CodeNamespace dataNS = new CodeNamespace("Data");

            // Add class to namespace
            dataNS.Types.Add(dataType);

            // Create code unit
            CodeCompileUnit dataCode = new CodeCompileUnit();

            // Add namespace to code unit
            dataCode.Namespaces.Add(dataNS);

            // Add default namespaces
            dataNS.Imports.Add(new CodeNamespaceImport("System"));

            return dataCode;
        }
    }
}

The DataBuildProvider’s GenerateCode() method loads a .data file into an XmlDocument. Notice that the VirtualPath property represents the path of the file that is being built. For example, if you add a file named Products.data to your project, then the VirtualPath property would represent the path to the Products.data file.

Next, the code for the data access component is created from the XML file by the GetDataCode() method. The GetDataCode() method makes heavy use of the CodeDom to generate the code in a language-neutral manner.

Working with the CodeDom is a strange and tedious experience. You must build up a block of code by building a code tree. In Listing 27.9, a CodeCompileUnit named dataCode is created. A CodeNamespace named dataNS that represents a namespace is created and added to the CodeCompileUnit. And, a CodeTypeDeclaration named datatype that represents a class is added to the namespace. After the class is created, the methods and properties are added to the class block by block.

Creating a Custom ExpressionBuilder

An ExpressionBuilder class generates one expression from another expression. Typically, you use an ExpressionBuilder to look up a particular value given a particular key.

The ASP.NET Framework includes the following ExpressionBuilder classes:

  • AppSettingsExpressionBuilderRetrieves values from the appSettings section of the web configuration file.

  • ConnectionStringsExpressionBuilderRetrieves values from the connectionStrings section of the web configuration file.

  • ResourceExpressionBuilderRetrieves values from resource files.

The ConnectionStringsExpressionBuilder has been used throughout this book whenever a connection string has needed to be retrieved.

You use the following syntax when working with an ExpressionBuilder:

<%$ ConnectionStrings:MyDatabase %>

The <%$ and %> tags are used to mark an expression that should be parsed by an ExpressionBuilder. The prefix ConnectionStrings is mapped to the particular ExpressionBuilder class that is responsible for parsing the expression.

ExpressionBuilders must always be used with control properties. For example, you cannot display a connection string in a page like this:

<%$ ConnectionStrings:MyDatabase %>

Instead, you must display the connection string like this:

<asp:Literal
  Id="ltlConnectionString"
  Text='<%$ ConnectionStrings:MyDatabase %>'
  Runat="server" />

You can create a custom ExpressionBuilder when none of the existing ExpressionBuilder classes do what you need. For example, you might want to store your application settings in a custom section of the web configuration file. In that case, you might want to create a custom ExpressionBuilder that grabs values from the custom configuration section.

Creating a Lookup ExpressionBuilder

In this section, you learn how to extend the ASP.NET Framework by building a custom ExpressionBuilder class. We’ll create a Lookup ExpressionBuilder that looks up string values from an XML file.

The LookupExpressionBuilder class is contained in Listing 27.10.

Example 27.10. App_CodeLookupExpressionBuilder.cs

using System;
using System.CodeDom;
using System.Web.UI;
using System.ComponentModel;
using System.Web.Compilation;
using System.Xml;
using System.Web.Hosting;
using System.Web.Caching;

namespace AspNetUnleashed
{
    public class LookupExpressionBuilder : ExpressionBuilder
    {
        public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
        {
            CodeTypeReferenceExpression refMe = new CodeTypeReferenceExpression(base.GetType());
            CodePrimitiveExpression expression = new CodePrimitiveExpression(entry.Expression);
            return new CodeMethodInvokeExpression(refMe, "GetEvalData", new CodeExpression[] { expression });
        }

        public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
        {
            return GetEvalData(entry.Expression);
        }

        public override bool SupportsEvaluate
        {
            get
            {
                return true;
            }
        }

        public static string GetEvalData(string expression)
        {
            XmlDocument lookupDoc = (XmlDocument)HostingEnvironment.Cache["Lookup"];
            if (lookupDoc == null)
            {
                lookupDoc = new XmlDocument();
                string lookupFileName = HostingEnvironment.MapPath ("~/Lookup.config");
                lookupDoc.Load(lookupFileName);
                CacheDependency fileDepend = new CacheDependency(lookupFileName);
                HostingEnvironment.Cache.Insert("Lookup", lookupDoc, fileDepend);
            }

            string search = String.Format("//add[@key='{0}']", expression);
            XmlNode match = lookupDoc.SelectSingleNode(search);
            if (match != null)
                return match.Attributes["value"].Value;
            return "[no match]";
        }

    }
}

Before you can use the LookupExpressionBuilder class, you need to register it in the web configuration file. The web configuration file in Listing 27.11 includes an <expressionBuilders> section that registers the LookupExpressionBuilder class for the prefix lookup.

Example 27.11. Web.Config

<configuration>
  <system.web>
    <compilation>
      <expressionBuilders>
        <add expressionPrefix="lookup"
           type="AspNetUnleashed.LookupExpressionBuilder" />
      </expressionBuilders>
    </compilation>
  </system.web>
</configuration>

The LookupExpressionBuilder uses an XML file named Lookup.config to contain a database of lookup values. This file contains key and value pairs. A sample Lookup.config file is contained in Listing 27.12.

Example 27.12. Lookup.config

<lookup>
  <add key="WelcomeMessage" value="Welcome to our Web site!" />
  <add key="Copyright" value="All content copyrighted by the company." />
</lookup>

Finally, the page in Listing 27.13 uses the LookupExpressionBuilder. It contains a Literal control that displays the value of a lookup expression named WelcomeMessage (see Figure 27.3).

Displaying text generated by an ExpressionBuilder.

Figure 27.3. Displaying text generated by an ExpressionBuilder.

Example 27.13. ShowLookupExpressionBuilder.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show LookupExpressionBuilder</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Literal ID="Literal1"
        Text="<%$ lookup:WelcomeMessage %>"
        runat="Server" />

    </div>
    </form>
</body>
</html>

You create a custom ExpressionBuilder by inheriting a new class from the base ExpressionBuilder class. The ExpressionBuilder class has the following methods:

  • GetCodeExpressionReturns the code that is used to evaluate an expression.

  • EvaluateExpressionEvaluates the expression in the case of no-compile ASP.NET pages.

  • ParseExpressionReturns a parsed version of the expression.

The ExpressionBuilder class also supports the following property:

  • SupportsEvaluateWhen true, the ExpressionBuilder can be used in no-compile ASP.NET pages.

When you use an ExpressionBuilder in a normal ASP.NET page, the ExpressionBuilder returns code that is integrated into the compiled ASP.NET page. The GetCodeExpression() method returns a block of code that is injected into the compiled ASP.NET page class that gets created in the Temporary ASP.NET Files folder.

Because an ExpressionBuilder might be used with either a Visual Basic .NET or C# ASP.NET page, the code returned by the GetCodeExpression() method must be language neutral. This means that you must represent the code that gets executed with the CodeDom.

In Listing 27.11, the GetCodeExpression() method returns an instance of the CodeMethodInvokeExpression class. This class represents an expression that invokes a class method. In this case, the CodeMethodInvokeExpression class is used to represent the expression LookupExpressionBuilder.GetEvalData(). In other words, the ExpressionBuilder adds code to the compiled ASP.NET page class that invokes the GetEvalData() method contained in Listing 27.10.

As an alternative to creating a normal ASP.NET page, you can create something called a no-compile ASP.NET page. A no-compile ASP.NET page is not compiled dynamically. You create a no-compile ASP.NET page by adding the following attribute to a <%@ Page %> directive:

<%@ Page CompilationMode="Never" %>

Note

No-compile ASP.NET pages are discussed in Chapter 1, “Overview of the ASP.NET Framework.”

If you want an ExpressionBuilder to work with no-compile ASP.NET pages, then you must return the value True from the ExpressionBuilder.SupportsEvaluate property and implement the EvaluateExpression() method. The EvaluateExpression is executed at runtime when the no-compile ASP.NET page is requested. In Listing 27.11, the EvaluateExpression() method simply calls the GetEvalData() method.

Creating HTTP Handlers

An HTTP Handler is a .NET class that executes whenever you make a request for a file at a certain path. Each type of resource that you can request from an ASP.NET application has a corresponding handler.

For example, when you request an ASP.NET page, the Page class executes. The Page class is actually an HTTP Handler because it implements the IHttpHandler interface.

Other examples of HTTP Handlers are the TraceHandler class, which displays application-level trace information when you request the Trace.axd page and the ForbiddenHandler class, which displays an Access Forbidden message when you attempt to request source code files from the browser.

You can implement your own HTTP handlers. For example, imagine that you want to store all your images in a database table. However, you want use normal HTML <img> tags to display images in your web pages. In that case, you can map any file that has a .gif or .jpeg extension to a custom image HTTP handler. The image HTTP handler can retrieve images from a database automatically whenever an image request is made.

Or imagine that you want to expose an RSS feed from your website. In that case, you can create a RSS HTTP Handler that displays a list of blog entries or articles hosted on your website.

You can create an HTTP Handler in two ways. You can either create something called a Generic Handler, or you can implement the IHttpHandler interface in a custom class. This section explores both methods of creating an HTTP Handler.

Creating a Generic Handler

The easiest way to create a new HTTP Handler is to create a Generic Handler. When you create a Generic Handler, you create a file that ends with the extension .ashx. Whenever you request the .ashx file, the Generic Handler executes.

You can think of a Generic Handler as a very lightweight ASP.NET page. A Generic Handler is like an ASP.NET page that contains a single method that renders content to the browser. You can’t add any controls declaratively to a Generic Handler. A Generic Handler also doesn’t support events such as the Page Load or Page PreRender events.

In this section, we create a Generic Handler that dynamically generates an image from a string of text. For example, if you pass the string Hello World! to the handler, the handler returns an image of the text Hello World!.

The Generic Handler is contained in Listing 27.14.

Example 27.14. ImageTextHandler.ashx

<%@ WebHandler Language="C#" Class="ImageTextHandler" %>

using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;

public class ImageTextHandler : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        // Get parameters from querystring
        string text = context.Request.QueryString["text"];
        string font = context.Request.QueryString["font"];
        string size = context.Request.QueryString["size"];

        // Create Font
        Font fntText = new Font(font, float.Parse(size));

        // Calculate image width and height
        Bitmap bmp = new Bitmap(10, 10);
        Graphics g = Graphics.FromImage(bmp);
        SizeF bmpSize = g.MeasureString(text, fntText);
        int width = (int)Math.Ceiling(bmpSize.Width);
        int height = (int)Math.Ceiling(bmpSize.Height);
        bmp = new Bitmap(bmp, width, height);
        g.Dispose();

        // Draw the text
        g = Graphics.FromImage(bmp);
        g.Clear(Color.White);
        g.DrawString(text, fntText, Brushes.Black, new PointF(0, 0));
        g.Dispose();
        // Save bitmap to output stream
        bmp.Save(context.Response.OutputStream, ImageFormat.Gif);
    }

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

}

The ImageTextHandler in Listing 27.14 includes one method and one property. The ProcessRequest() method is responsible for outputting any content that the handler renders to the browser.

In Listing 27.14, the image text, font, and size are retrieved from query string fields. You specify the image that you want to return from the handler by making a request that looks like this:

/ImageTextHandler.ashx?text=Hello&font=Arial&size=30

Next, a bitmap is created with the help of the classes from the System.Drawing name-space. The bitmap is actually created twice. The first one is used to measure the size of the bitmap required for generating an image that contains the text. Next, a new bitmap of the correct size is created, and the text is drawn on the bitmap.

After the bitmap has been created, it is saved to the HttpResponse object’s OutputStream so that it can be rendered to the browser.

The handler in Listing 27.14 also includes an IsReusable property. The IsReusable property indicates whether the same handler can be reused over multiple requests. You can improve your application’s performance by returning the value True. Because the handler isn’t maintaining any state information, there is nothing wrong with releasing it back into the pool so that it can be used with a future request.

The page in Listing 27.15 illustrates how you can use the ImageTextHandler.ashx file. This page contains three HTML <img> tags that pass different query strings to the handler (see Figure 27.4).

Example 27.15. ShowImageTextHandler.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show ImageTextHandler</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <img src="ImageTextHandler.ashx?text=Some Text&font=WebDings&size=42" />
    <br />
    <img src="ImageTextHandler.ashx?text=Some Text&font=Comic Sans MS&size=42" />
    <br />
    <img src="ImageTextHandler.ashx?text=Some Text&font=Courier New&size=42" />

    </div>
    </form>
</body>
</html>
Displaying text images with an HTTP Handler.

Figure 27.4. Displaying text images with an HTTP Handler.

Implementing the IHttpHandler Interface

The big disadvantage of a Generic Handler is that you cannot map a Generic Handler to a particular page path. For example, you cannot execute a Generic Handler whenever someone requests a file with the extension .gif.

If you need more control over when an HTTP Handler executes, then you can create a class that implements the IHttpHandler interface.

After you create a class that For example, the class in Listing 27.16 represents an Image HTTP Handler. This handler retrieves an image from a database table and renders the image to the browser.

Example 27.16. App_CodeImageHandler.cs

using System;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

namespace AspNetUnleashed
{
    public class ImageHandler : IHttpHandler
    {
        const string connectionStringName = "Images";

        public void ProcessRequest(HttpContext context)
        {
            // Don't buffer response
            context.Response.Buffer = false;

            // Get file name
            string fileName = VirtualPathUtility.GetFileName(context.Request.Path);

            // Get image from database
            string conString = WebConfigurationManager.ConnectionStrings [connectionStringName].ConnectionString;
            SqlConnection con = new SqlConnection(conString);
            SqlCommand cmd = new SqlCommand("SELECT Image FROM Images WHERE FileName=@FileName", con);
            cmd.Parameters.AddWithValue("@fileName", fileName);
            using (con)
            {
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader(CommandBehavior. SequentialAccess);
                if (reader.Read())
                {
                    int bufferSize = 8040;
                    byte[] chunk = new byte[bufferSize];
                    long retCount;
                    long startIndex = 0;
                    retCount = reader.GetBytes(0, startIndex, chunk, 0, bufferSize);
                    while (retCount == bufferSize)
                    {
                        context.Response.BinaryWrite(chunk);

                        startIndex += bufferSize;
                        retCount = reader.GetBytes(0, startIndex, chunk, 0, bufferSize);
                    }
                    byte[] actualChunk = new Byte[retCount - 1];
                    Buffer.BlockCopy(chunk, 0, actualChunk, 0, (int)retCount - 1);
                    context.Response.BinaryWrite(actualChunk);
                }
            }

        }

        public bool IsReusable
        {
            get { return true; }
        }
    }
}

implements the IHttpHandler interface, you need to register the class in the web configuration file. The web configuration file in Listing 27.17 includes an httpHandlers section that associates the .gif, .jpeg, and .jpg extensions with the Image handler.

Example 27.17. Web.Config

<configuration>
  <connectionStrings>
    <add name="Images"
      connectionString="Data Source=.SQLExpress;Integrated
           Security=True;AttachDBFileName=|DataDirectory|ImagesDB.mdf;
           User Instance=True"/>
  </connectionStrings>
    <system.web>

      <httpHandlers>
        <add path="*.gif" verb="*"
           type="AspNetUnleashed.ImageHandler" validate="false" />
        <add path="*.jpeg" verb="*"
           type="AspNetUnleashed.ImageHandler" validate="false" />
        <add path="*.jpg" verb="*"
           type="AspNetUnleashed.ImageHandler" validate="false" />
      </httpHandlers>

    </system.web>
</configuration>

When you register a handler, you specify the following four attributes:

  • pathEnables you to specify the path associated with the handler. You can use wildcards in the path expression.

  • verbEnables you to specify the HTTP verbs, such as GET or POST, associated with the handler. You can specify multiple verbs in a comma-separated list. You can represent any verb with the * wildcard.

  • typeEnables you to specify the name of the class that implements the handler.

  • validateEnables you to specify whether the handler is loaded during application startup. When true, the handler is loaded at startup. When false, the handler is not loaded until a request associated with the handler is made. This second option can improve your application’s performance when a handler is never used.

The page in Listing 27.18 uses the ImageHandler to render its images. The page enables you to upload new images to a database named ImagesDB. The page also displays existing images (see Figure 27.5).

Displaying images with the ImageHandler.

Figure 27.5. Displaying images with the ImageHandler.

Example 27.18. ImageUpload.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    protected void btnAdd_Click(object sender, EventArgs e)
    {
        if (upFile.HasFile)
        {
                srcImages.Insert();
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .fileList li
        {
            margin-bottom:5px;
        }
    </style>
    <title>Image Upload</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        id="lblFile"
        Text="Image File:"
        AssociatedControlID="upFile"
        Runat="server" />
    <asp:FileUpload
        id="upFile"
        Runat="server" />
    <asp:Button
        id="btnAdd"
        Text="Add Image"
        OnClick="btnAdd_Click"
        Runat="server" />
    <hr />

    <asp:GridView
        id="grdImages"
        DataSourceID="srcImages"
        AutoGenerateColumns="false"
        ShowHeader="false"
        GridLines="None"
        Runat="server">
        <Columns>
        <asp:ImageField
            DataImageUrlField="FileName"
            DataAlternateTextField="FileName" />
        </Columns>
    </asp:GridView>

    <asp:SqlDataSource
        id="srcImages"
        ConnectionString="<%$ ConnectionStrings:Images %>"
        SelectCommand="SELECT FileName FROM Images"
        InsertCommand="INSERT Images (FileName,Image) VALUES (@FileName,@FileBytes)"
        Runat="server">
        <InsertParameters>
            <asp:ControlParameter Name="FileName" ControlID="upFile" PropertyName="FileName" />
            <asp:ControlParameter Name="FileBytes" ControlID="upFile" PropertyName="FileBytes" />
        </InsertParameters>
    </asp:SqlDataSource>

    </div>
    </form>
</body>
</html>

Registering Extensions with Internet Information Server

The web server included with Visual Web Developer maps all requests to the ASP.NET Framework. For example, if you create an HTTP Handler that handles requests for .gif files, then you don’t have to do anything special when using the handler with the Visual Web Developer web server.

Internet Information Server, on the other hand, does not map all requests to the ASP.NET Framework. In particular, it does not map requests for .gif files to ASP.NET. If you want to use a special extension for a handler, then you must configure Internet Information Server to map that extension to the ASP.NET Framework.

If you are serving your pages with Internet Information Server 6.0 (included with Windows Server 2003), then you can create something called a wildcard application mapping. A wildcard application mapping enables you to map all page requests to an application such as the ASP.NET Framework. Follow these steps to configure a wildcard mapping for ASP.NET:

  1. Open Internet Information Services by selecting Start, Control Panel, Administrative Tools, Internet Information Services.

  2. Open the property sheet for a particular website or virtual directory.

  3. Open the Application Configuration dialog box by selecting the Directory tab and clicking the Configuration button.

  4. Select the Mappings tab.

  5. Click the Insert button at the bottom of the Mappings tab to open the Add/Edit Application Extension Mapping dialog box (see Figure 27.6).

    Adding a wildcard application mapping.

    Figure 27.6. Adding a wildcard application mapping.

  6. In the Executable field, enter the path to the ASP.NET ISAPI DLL. (You can copy and paste this path from the Application Mapping for the .aspx extension.)

After you complete these steps, all requests made for any type of file are handled by the ASP.NET Framework. If you make a request for a .gif image, then any handlers that you have registered in the web configuration file for the .gif extension will execute.

Earlier versions of Internet Information Server, such as the version included with Microsoft Windows XP, do not support wildcard application mappings. You must map each file extension that you want to associate with the ASP.NET Framework one by one. Follow these steps to map the .gif extension to the ASP.NET Framework:

  1. Open Internet Information Services by selecting Start, Control Panel, Administrative Tools, Internet Information Services.

  2. Open the property sheet for a particular website or virtual directory.

  3. Open the Application Configuration dialog box by selecting the Directory tab and clicking the Configuration button.

  4. Select the Mappings tab (see Figure 27.7).

    Adding an application mapping.

    Figure 27.7. Adding an application mapping.

  5. Click the Add button to open the Add/Edit Application Extension Mapping dialog box.

  6. In the Executable field, enter the path to the ASP.NET ISAPI DLL. (You can copy and paste this path from the Application Mapping for the .aspx extension.)

  7. In the Extension field, enter .gif.

After you complete these steps, requests for .gif images are handled by the ASP.NET Framework. If you have registered an HTTP handler for the .gif extension in the web configuration file, then the HTTP Handler will execute whenever someone makes a request for a .gif file.

Creating an Asynchronous HTTP Handler

When you create an HTTP Handler by creating either a Generic Handler or implementing the IHttpHandler interface, you are creating a synchronous handler. In this section, you learn how to create an asynchronous handler.

The advantage of creating an asynchronous handler is scalability. The ASP.NET Framework maintains a limited pool of threads that are used to service requests. When the ASP.NET Framework receives a request for a file, it assigns a thread to handle the request. If the ASP.NET Framework runs out of threads, the request is queued until a thread becomes available. If too many threads are queued, then the framework rejects the page request with a 503 Server Too Busy response code.

If you execute an HTTP Handler asynchronously, then the current thread is released back into the thread pool so that it can be used to service another page request. While the asynchronous handler is executing, the ASP.NET framework can devote its attention to handling other requests. When the asynchronous handler completes its work, the framework reassigns a thread to the original request and the handler can render content to the browser.

Note

You can configure the ASP.NET thread pool with the httpRuntime element in the web configuration file. You can modify the appRequestQueueLimit, minFreeThreads, and minLocalRequestFreeThreads attributes to control how many requests the ASP.NET Framework queues before giving up and sending an error.

You create an asynchronous HTTP handler by implementing the IHttpAsyncHandler interface. This interface derives from the IHttpHandler interface and adds two additional methods:

  • BeginProcessRequestCalled to start the asynchronous task.

  • EndProcessRequestCalled when the asynchronous task completes.

For example, the file in Listing 27.19 contains an asynchronous handler that grabs an RSS feed from the Microsoft MSDN website.

Example 27.19. App_CodeRSSHandler.cs

using System;
using System.Web;
using System.Net;
using System.IO;

namespace AspNetUnleashed
{
    public class RSSHandler : IHttpAsyncHandler
    {
        private HttpContext _context;
        private WebRequest _request;

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            // Store context
            _context = context;

            // Initiate call to RSS feed
            _request = WebRequest.Create ("http://msdn.microsoft.com/asp.net/rss.xml");
            return _request.BeginGetResponse(cb, extraData);
        }

        public void EndProcessRequest(IAsyncResult result)
        {
            // Get the RSS feed
            string rss = String.Empty;
            WebResponse response = _request.EndGetResponse(result);
            using (response)
            {
               StreamReader reader = new StreamReader(response.GetResponseStream());
               rss = reader.ReadToEnd();
            }
            _context.Response.Write(rss);
        }

        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            throw new Exception("The ProcessRequest method is not implemented.");
        }
    }
}

The handler in Listing 27.19 implements both the BeginProcessRequest() and EndProcessRequest() methods required by the IHttpAsyncHandler interface.

The BeginProcessRequest() method uses the WebRequest class to request the page that contains the RSS headlines from the MSDN website. The WebRequest.BeginGetResponse() method is used to retrieve the remote page asynchronously.

When the BeginGetResponse() method completes, the handler’s EndProcessRequest() method is called. This method retrieves the page and renders the contents of the page to the browser.

Before you can use the RSSHandler, you need to register it in your web configuration file. The web configuration file in Listing 27.20 includes an <httpHandlers> section that registers the RSSHandler and associates the handler with the .rss extension.

Example 27.20. Web.Config

<configuration>
    <system.web>

      <httpHandlers>
        <add path="*.rss" verb="*" type="AspNetUnleashed.RSSHandler"/>
      </httpHandlers>

    </system.web>
</configuration>

After you register the RSSHandler, you can execute the handler by making a request for any file that ends with the extension .rss. If you have a news reader, such as SharpReader, then you can enter a path like the following in the reader’s address bar:

http://localhost:2026/YourApp/news.rss

The page in Listing 27.21 contains a GridView and XmlDataSource control. The XmlDataSource control calls the RssHandler to retrieve the headlines that are displayed in the GridView control (see Figure 27.8).

Retrieving an RSS feed asynchronously.

Figure 27.8. Retrieving an RSS feed asynchronously.

Example 27.21. ShowRSSHandler.aspx

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

    void Page_Load()
    {
        string pagePath = Request.Url.OriginalString;
        string rssPath = Path.ChangeExtension(pagePath, ".rss");
        srcRSS.DataFile = rssPath;
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show RSS Handler</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:GridView
        id="grdRSS"
        DataSourceID="srcRSS"
        AutoGenerateColumns="false"
        Runat="server">
        <Columns>
        <asp:TemplateField HeaderText="Articles">
        <ItemTemplate>
            <asp:HyperLink
                id="lnkRSS"
                Text='<%# XPath("title") %>'
                NavigateUrl='<%# XPath("link") %>'
                Runat="server" />
        </ItemTemplate>
        </asp:TemplateField>
        </Columns>
    </asp:GridView>

    <asp:XmlDataSource
        id="srcRSS"
        XPath="//item"
        Runat="server" />
    </div>
    </form>
</body>
</html>

Working with HTTP Applications and HTTP Modules

Whenever you request an ASP.NET page, the ASP.NET Framework assigns an instance of the HttpApplication class to the request. This class performs the following actions in the following order:

  1. Raises the BeginRequest event.

  2. Raises the AuthenticateRequest event.

  3. Raises the AuthorizeRequest event.

  4. Calls the ProcessRequest() method of the Page class.

  5. Raises the EndRequest event.

Note

This is not a complete list of HttpApplication events. There are a lot of them!

The entire page execution lifecycle happens during the fourth step. For example, the Page Init, Load, and PreRender events all happen when the Page class ProcessRequest() method is called.

The HttpApplication object is responsible for raising application events. These application events happen both before and after a page is executed.

You might want to handle one of the application events for several reasons. For example, you might want to implement a custom authentication scheme. In that case, you would need to handle the AuthenticateRequest event to identify the user.

Or you might want to create a custom logging module that tracks the pages that your website users visit. In that case, you might want to handle the BeginRequest event to record the pages being requested.

If you want to handle HttpApplication events, there are two ways to do it. You can create a Global.asax file, or you can create one or more custom HTTP Modules.

Creating a Global.asax File

By default, the ASP.NET Framework maintains a pool of HttpApplication objects to service incoming page requests. A separate HttpApplication instance is assigned to each request.

If you prefer, you can create a custom HttpApplication class. That way, an instance of your custom class is assigned to each page request.

You can create custom properties in your derived class. These properties can be accessed from any page, control, or component. You also can handle any application events in your custom HttpApplication class.

You create a custom HttpApplication class by creating a special file named Global.asax in the root of your application. Every application can have only one of these files.

For example, the Global.asax file in Listing 27.22 can be used to track the number of page requests made for any page.

Example 27.22. Global.asax

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">

    private string _conString;
    private SqlConnection _con;
    private SqlCommand _cmdSelect;
    private SqlCommand _cmdInsert;

    public override void Init()
    {
        // initialize connection
        _conString = WebConfigurationManager.ConnectionStrings["Log"]. ConnectionString;
        _con = new SqlConnection(_conString);

        // initialize select command
        _cmdSelect = new SqlCommand("SELECT COUNT(*) FROM Log WHERE Path=@Path", _con);
        _cmdSelect.Parameters.Add("@Path", SqlDbType.NVarChar, 500);

        // initialize insert command
        _cmdInsert = new SqlCommand("INSERT Log (Path) VALUES (@Path)", _con);
        _cmdInsert.Parameters.Add("@Path", SqlDbType.NVarChar, 500);
    }

    public int NumberOfRequests
    {
        get
        {
           int result = 0;
           _cmdSelect.Parameters["@Path"].Value = Request. AppRelativeCurrentExecutionFilePath;
           try
           {
               _con.Open();
               result = (int)_cmdSelect.ExecuteScalar();
           }
           finally
           {
               _con.Close();
           }
           return result;
        }
    }

    void Application_BeginRequest(object sender, EventArgs e)
    {
        // Record new request
        _cmdInsert.Parameters["@Path"].Value = Request. AppRelativeCurrentExecutionFilePath;
        try
        {
            _con.Open();
            _cmdInsert.ExecuteNonQuery();
        }
        finally
        {
            _con.Close();
        }
    }
</script>

The Global.asax page in Listing 27.23 handles the Application BeginRequest() event. You can handle any application event by following the naming pattern Application_EventName where EventName is the name of the HttpApplication event.

In Listing 27.23, the Application_BeginRequest() handler is used to record the path of the page being requested. A SqlCommand object is used to record the page path to a database table named Log.

The Global.asax file also extends the base HttpApplication class with a custom property named NumberOfRequests. This property retrieves the number of requests made for the page at the current path.

Finally, the Global.asax includes an Init() method that overrides the base HttpApplication's Init() method. In Listing 27.23, the Init() method is used to initialize the SqlConnection and two SqlCommand objects used in the Global.asax file.

The Init() method is called when the class represented by the Global.asax is initialized. It is called only once, when the class is first created.

Warning

The same instance of the HttpApplication object is re-used for multiple page requests (although never for multiple page requests at the same time). Any value that you assign to a property in a Global.asax file is maintained over the multiple page requests.

The page in Listing 27.23 displays the value of the custom property exposed by the Global.asax file (see Figure 27.9). Notice that the ApplicationInstance property is used to refer to the instance of the HttpApplication class associated with the page. Because the Global.asax file is compiled dynamically in the background, any properties that you declare in the Global.asax file are exposed as strongly typed properties.

Displaying the NumberOfRequests property.

Figure 27.9. Displaying the NumberOfRequests property.

Example 27.23. ShowGlobal.aspx

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Show Global</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    This page has been requested
    <%= this.ApplicationInstance.NumberOfRequests %>
    times!

    </div>
    </form>
</body>
</html>

Creating Custom HTTP Modules

An HTTP Module is a .NET class that executes with each and every page request. You can use an HTTP Module to handle any of the HttpApplication events that you can handle in the Global.asax file.

Behind the scenes, the ASP.NET Framework uses HTTP Modules to implement many of the standard features of the framework. For example, the ASP.NET Framework uses the FormsAuthenticationModule to implement Forms authentication and the WindowsAuthenticationModule to implement Windows authentication.

Session state is implemented with an HTTP Module named the SessionStateModule. Page output caching is implemented with an HTTP Module named the OutputCacheModule, and the Profile object is implemented with an HTTP Module named the ProfileModule.

When a new instance of an HttpApplication class is created, the HttpApplication loads all of the HTTP Modules configured in the web configuration file. Each HTTP Module subscribes to one or more HttpApplication events. For example, when the HttpApplication object raises its AuthenticateRequest event, the FormsAuthenticationModule executes its code to authenticate the current user.

In this section, we create a simple authentication HTTP Module. The HTTP Module doesn’t allow you to request a page unless you include the proper query string with the request. The code for the custom HTTP Module is contained in Listing 27.24.

Example 27.24. App_CodeQueryStringAuthenticationModule.cs

using System;
using System.Web;

namespace AspNetUnleashed
{
    public class QueryStringAuthenticationModule : IHttpModule
    {
        public void Init(HttpApplication app)
        {
            app.AuthorizeRequest += new EventHandler(AuthorizeRequest);
        }

        private void AuthorizeRequest(Object sender, EventArgs e)
        {
            // Get context
            HttpApplication app = (HttpApplication)sender;
            HttpContext context = app.Context;

            // If the request is for Login.aspx, exit
            string path = context.Request.AppRelativeCurrentExecutionFilePath;
            if (String.Compare(path, "~/login.aspx", true) == 0)
                return;

            // Check for password
            bool authenticated = false;
            if (context.Request.QueryString["password"] != null)
            {
                if (context.Request.QueryString["password"] == "secret")
                    authenticated = true;
            }

            // If not authenticated, redirect to login.aspx
            if (!authenticated)
                context.Response.Redirect("~/Login.aspx");
        }

        public void Dispose() { }
    }
}

The class in Listing 27.25 implements the IHttpModule interface. This interface includes two methods:

  • InitEnables you to subscribe to HttpApplication events.

  • DisposeEnables you to clean up any resources used by the HTTP Module.

In Listing 27.25, the Init() method adds an event handler for the HttpApplication AuthorizeRequest event. When the HttpApplication raises the AuthorizeRequest event, the HTTP Module’s AuthorizeRequest() method executes.

The AuthorizeRequest() method checks for a password=secret query string. If the query string does not exist, then the user is redirected to the Login.aspx page. (The method also checks whether the user is requesting the Login.aspx page to avoid a vicious circle.)

Before you can use the QueryStringAuthenticationModule, you must register the HTTP Module in the web configuration file. The web configuration file in Listing 27.25 includes an <httpModules> section that registers the module.

Example 27.25. Web.Config

<configuration>
    <system.web>

      <httpModules>
        <add name="QueryStringAuthenticationModule"
             type="AspNetUnleashed.QueryStringAuthenticationModule"/>
      </httpModules>

    </system.web>
</configuration>

After you register the HTTP Module, if you attempt to request any page without including the password=secret query string, then you are redirected to the Login.aspx page. (If the Login.aspx page doesn’t exist, you receive a 404 - Not Found error message.)

Summary

In this chapter, you learned how to extend the ASP.NET Framework by extending different parts of the HTTP Runtime. In the first section, you learned how to create a custom BuildProvider. For example, you learned how to create a BuildProvider that dynamically generates a data access component from an XML file.

Next, you explored the topic of ExpressionBuilders. You learned how to use an ExpressionBuilder to automatically replace one expression with another. For example, we created a custom ExpressionBuilder that enables you to look up a value from an XML file.

The topic of HTTP Handlers was also explored. You learned two methods of creating custom HTTP Handlers. You learned how to create a Generic Handler, and you learned how to create an HTTP Handler by implementing the IHttpHandler interface. You also saw how you can improve the scalability of your ASP.NET applications by implementing asynchronous HTTP Handlers.

Finally, you learned two methods of handling applicationwide events. You learned how to create a custom HttpApplication by creating a Global.asax file. You also learned how to handle application events by implementing a custom HTTP Module.

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

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