The ASP Column: HTTP Modules

MSDN Magazine

HTTP Modules
 
Download the code for this article: ASP0205.exe (40KB)
O

ne of ASP.NET's most useful features is the extensibility of the HTTP pipeline, the path that data takes between client and server. This month I'm going to take a look at HTTP modules in ASP.NET. You can use them to extend your ASP.NET applications by adding pre- and post-processing to each HTTP request coming into your application. For example, if you wanted custom authentication facilities for your application, the best technique would be to intercept the request when it comes in and process the request in a custom HTTP module. I'll start by looking at the HTTP pipeline.

The HTTP Pipeline

      To understand the role of HTTP modules within ASP.NET, you need to understand how the HTTP pipeline works. Once an HTTP request comes in over port 80 (the regular HTTP port or port 443 for HTTPS and the secure sockets layer), the request passes through a number of stages making up the HTTP pipeline before it's actually fielded by your application.
      Microsoft® Internet Information Services (IIS) is the first participant in the chain. While ASP.NET has its own object model, process isolation scheme, and session state management scheme based upon classes from the .NET Framework, IIS is still used to route the request to the ASP.NET runtime. IIS maps the ASP.NET file extensions to ASPNET_ISAPI.DLL, an ISAPI extension provided with ASP.NET. The job of ASPNET_ISAPI.DLL is to forward the request to the ASP.NET worker process, ASPNET_WP.EXE. From that point on, the request is wrapped up into an instance of HttpContext and piped through a number of ASP.NET classes. The HttpContext class includes members representing such things as the Response and the Request and also includes security information along with anything else you might want to know about the request.
      The next step for the request is to pass through an instance of HttpApplication. This stage is useful for maintaining application-scope methods, data, and events. I'll look at HttpApplication closely since this class is pivotal in setting up HTTP modules.
      After the HttpApplication object massages the request, it pushes the request through one or more HttpModule objects. There are a number of system-level HTTP modules, providing services ranging from authentication to state management to output caching. The number of modules that get to intercept the request is based upon settings within the host machine's machine.config file and the application's web.config file. In classic ASP, this role of providing pre- and post-processing fell upon ISAPI filters. It turns out that ASP HTTP modules are more straightforward to write.
      The final piece of the chain is the HttpHandler. If you've been working with ASP.NET for a while, you're familiar with the System.Web.UI.Page class. The Page class is an HttpHandler object, implementing the interface IHttpHandler. Classes implementing IHttpHandler can hook into the HTTP pipeline and service requests through the interface's ProcessRequest method.

Figure 1 HTTP Request
Figure 1 HTTP Request

      For each request that comes in, if the requested URI maps to an ASP.NET extension, ASP.NET will look for an IHttpHandler implementation in the web.config file associated with that extension. If you change nothing in your web.config files and surf to an ASPX page, ASP.NET creates a handler based on System.Web.UI.Page. Alternatively, you can map individual HTTP handlers to distinct URIs through the web.config file. Figure 1 shows the path an HTTP request takes as it meanders through the HTTP pipeline. The events that are hooked to HttpModule classes include BeginRequest, AuthenticateRequest, AuthorizeRequest, and so on.

ASP.NET HTTP Modules

      An HTTP module is simply a class that implements the System.Web.IHttpModule interface:

public interface IHttpModule
{
  void Dispose();
  void Init(HttpApplication context);
}

When an HttpModule is hooked into the pipeline (via an entry in web.config), the ASP.NET runtime calls the module's Init and Dispose methods. Init is called when the module attaches itself to the HttpApplication object and Dispose is called when the module is detached from HttpApplication. The Init and Dispose methods represent the module's opportunity to hook into a variety of events exposed by HttpApplication. These events include the beginning of a request, the end of a request, a request for authentication, and so forth. Notice the HttpApplication parameter passed in to the Init method. Usually, the Init method takes the HttpApplication object and maps event handlers to the desired events.
      Figure 2 shows some C# code defining a simple HttpModule that attaches to an application's BeginRequest and EndRequest events to provide simple pre- and post-processing before and after each request. This code is compiled into an assembly that goes into the application's bin directory. To wire this handler up into the processing chain, the web.config file simply declares the module within the httpModules section, as shown here:

<configuration>
 <system.web>
  <httpModules>
   <add type=
    "HttpModuleExamples.CustomHttpModule, HTTPModules"
    name="CustomHttpModule" />
  </httpModules>
 </system.web>
</configuration>

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

      The following code shows a plain vanilla ASPX page that uses the HttpModule in the chain:

<html><body><head>
   <br>
   Hello World! Here's how the HttpModules work...
   <br>
   <br>
</body> </html>

ASP.NET now routes all requests through the CustomHttpModule's OnBeginRequest method at the beginning of the request, and the OnEndRequest method at the end of the request. A simple ASPX page such as the one in Figure 3 will appear when you surf to the page.

Figure 3 A Simple ASPX Page
Figure 3 A Simple ASPX Page

      BeginRequest and EndRequest aren't the only events you can intercept within an HttpModule. Figure 4 shows all the available events you can route through an HttpApplication and trap within an HttpModule. Catching any of these events is simply a matter of setting up an event handler for the event you want to handle. Figure 5 shows some C# code for an HttpModule intercepting the AuthenticateRequest event.

Terminating Requests Early

      One of the most common reasons for intercepting an HTTP request is to terminate the request early if something goes wrong. For example, if you're handling authentication by yourself, you might stop the request if the credentials are incorrect. If you're writing a SOAP server, you may want to discontinue the request if a non-SOAP request comes through. The HttpApplication class has a method named CompleteRequest that finishes the request. You can call CompleteRequest and set the StatusCode and StatusDescription properties of the context object to let the client know about the issue. Figure 6 shows the interception of a request and its completion when the connection is not secure.

System-provided Modules

      A good many ASP.NET features are added using this HttpModule technique. These features include output caching, session state, Windows® authentication, forms authentication, Passport authentication, URL authorization, and file authorization. Figure 7 shows these features and the modules responsible for them.
      Each of the predefined HttpModules is declared within the host-wide machine.config file, as shown in Figure 8.
      The list of HttpModules attached to an app is represented as a collection of IHttpModule references named HttpModuleCollection, which can be accessed as the Modules property of the HttpApplication class. At run time, the collection includes all the system-provided modules declared within machine.config as well as any specified in web.config. The HttpModuleCollection is a list of references to IHttpModule keyed by either the name of the module or an ordinal. You can interrogate the collection to find out which modules are attached, as shown in Figure 9. Figure 10 shows the modules as they're listed on the Web page.

Figure 10 Attached HttpModules
Figure 10 Attached HttpModules

      The HttpModules are normally wired up by ASP.NET as the application starts. If you want to manage the modules yourself, you may use the HttpModuleCollection to obtain a reference to any of the modules and call IHttpModule's Init or Dispose methods. Alternatively you might manually load an HttpModule assembly and add the module to the list based upon certain custom criteria for your application.

HttpModules and Global.ASAX

      ASP.NET applications may include a global file named GLOBAL.ASAX (known also as the ASP.NET application file). GLOBAL.ASAX lives in the root directory of your ASP.NET application. When the application is loaded at run time, ASP.NET parses the GLOBAL.ASAX file and generates a runtime object derived from HttpApplication. GLOBAL.ASAX is optional, but when it's there, it includes code for responding to application-level events raised by ASP.NET and by HTTP modules.
      GLOBAL.ASAX is useful for handling events exposed by the modules that are handling the incoming request. For example, if you wanted to handle authentication within your GLOBAL.ASAX file, you would do so by handling the Application_OnAuthenticateRequest event, as shown here:

<Script language="C#" runat="server">
     // Inside GLOBAL.ASAX
     void Application_OnAuthenticateRequest(Object Source,
                              EventArgs Details) {
     // Put your authentication code here...
     }
</script>

Conclusion

      Microsoft has done a great job making sure ASP.NET is extensible. One of the easiest ways to add pre- and post-processing to each request is by chaining an HttpModule into your application. In fact, ASP.NET implements such features as forms authentication and output caching through the HttpModule mechanism. They're easy to write—just create a class derived from IHttpModule, write event handlers for the events you want to intercept, and create an entry into your web.config file to let ASP.NET know about the HttpModule. In future columns I'll explore some of ASP.NET's features, such as output caching and forms authentication, which have been implemented through HTTP modules.

Send questions and comments for George to asp-net@microsoft.com.

George Shepherd is a software consultant and an instructor with DevelopMentor. George is the coauthor of MFC Internals (Addison-Wesley, 1996), Programming Visual C++ (Microsoft Press, 1998), and Applied .NET (Addison-Wesley, 2001). George may be reached at 70023.1000@compuserve.com.

From the May 2002 issue of MSDN Magazine