Extend ASP.NET With HTTP Modules

Add power above and beyond traditional ASP.NET apps by inserting new functionality to request handlers.

asp:cover story

LANGUAGES: VB .NET | C#

TECHNOLOGIES: HTTP Modules | ASP.NET Configuration | .NET Event Handling | File I/O

 

Extend ASP.NET With HTTP Modules

Add power above and beyond traditional ASP.NET apps by inserting new functionality to request handlers.

 

By Brian Noyes

 

ASP.NET is a powerful environment for developing and running Web applications. But sometimes you need to insert functionality in the middle of that environment in ways that simply are not possible with code on or behind an ASP.NET page. You might need to perform processing at the earliest stages in the HTTP processing pipeline, before the request ever reaches any pages, or at the very end, after the response has been generated. Or you might need to intercept processing at the moment certain events occur, such as when the user is being authenticated. In the days before ASP.NET, the only real way to do this was through Internet Server Application Program Interface (ISAPI), which was fairly painful to program, and you needed a lot of expertise in C++, DLL programming, and Internet Information Server (IIS). Fortunately, as it has for many Web-development tasks, ASP.NET has made this kind of thing much easier. The ASP.NET architecture gives you the opportunity to add functionality at several different levels in the HTTP processing pipeline.

 

In this article, I'll discuss where those extension points are. I'll briefly cover how the processing pipeline works in ASP.NET and which classes are involved for which parts of the processing. Then I'll go into a little more detail about the best way to add functionality in the processing pipeline: creating HTTP modules.

 

This article's sample application uses a module to keep track, in a log file, of a user and each page he or she visits on a site. You could do this at the page level, but it is much easier by inserting an HTTP module in the pipeline to do the work. That way, you can add this functionality to any site simply by changing the web.config file and deploying an assembly to the Web application's bin directory. The code shown in this article is in VB .NET, but the download code contains the same thing in C#. Finally, I'll mention some other examples of what you could do with an HTTP module.

 

Get Tubed in the HTTP Pipeline

Ultimately, any processing that occurs in your Web application starts with an HTTP request and ends with an HTTP response. If your Web application maintains state (through cookies, session state, or some other means), any number of successive round trips of requests and responses could make up the processing of a single user experience on your site. But IIS usually hides these details and only lets you worry about implementing Web pages and their associated code to do what you need to do when a request comes in for a particular page.

 

When a request comes in for an address, IIS needs to locate an associated Web application for the request and load that application if it is not running already. Then, IIS takes the request variables (QueryString and Post parameters) located in the request headers and body and passes them along to the application or page to process. In classic ASP, IIS handled all this internally. IIS created an application instance on the first call to the server for a resource in a virtual directory, and the application passed on calls to its individual pages. If you wanted to intercept the calls anywhere in that process, you had to implement an ISAPI extension that implemented an application, or you had to implement an ISAPI filter that could do some processing on the responses as they came in and on the requests as they went out, basically sitting between IIS and the application (see Figure 1).

 


Figure 1. In traditional ASP development, your options were limited either to handling things in your page or implementing ISAPI filters or extensions to override the normal processing pipeline.

 

ASP.NET runs as an ISAPI extension, so any ASP.NET application is handled by the ASP.NET runtime instead of IIS. ASP.NET manages a pool of HttpApplication instances for each application and uses one of the instances from the pool for each request that comes in. These instances are based on the class declared in your global.aspx file or on the base HttpApplication class if no global.aspx file is present. Unlike classic ASP, ASP.NET also runs these instances in their own process, so if one app crashes, it doesn't bring down the whole server.

 

The next layer in the ASP.NET pipeline is made of HTTP modules, which are simply .NET classes that implement the IHttpModule interface and are configured for the application through the web.config file. ASP.NET also provides many HTTP modules configured in the machine.config file for such things as authentication and session-state services. At the end of the chain are HTTP handlers, which are simply classes that implement the IHttpHandler interface. The System.Web.UI.Page class that Web forms are derived from implements the IHttpHandler interface for you, so your typical HTTP handler is simply a page in your application (see Figure 2). Basically, you can add or modify functionality in an ASP.NET application at several levels: the application, module, and handler levels.

 


Figure 2. ASP.NET gives you several points at which you can intercept the request handling: the application, module, and handler levels.

 

In extending ASP.NET, it's important to understand the lifecycle of a request and the events the HttpApplication raises during that lifecycle. You can wire event handlers in your HTTP module to react to the lifecycle events that interest you. You also need to keep in mind that the state of the HttpApplication instance changes throughout the lifecycle. For example, if you try to access the User object in the BeginRequest handler, the User object will be null. If you try to access the Session object after the ReleaseRequestState event, the Session object will be null. Figure 3 shows the events available.

 

Event Name

Description

BeginRequest

Occurs for every request. This is the first chance to hook into a request.

AuthenticateRequest

Occurs when authentication is attempted. This is your chance to provide custom authentication.

AuthorizeRequest

Occurs when the requested resource is checked for access rights.

ResolveRequestCache

Used to handle caching of output responses (such as cached controls and pages).

AcquireRequestState

Obtains a state object for the request.

PreRequestHandlerExecute

Occurs just prior to control being passed to the HTTP handler (page).

PostRequestHandlerExecute

Happens just after the HTTP handler (page) has completed processing and has returned a response.

ReleaseRequestState

This is where the request's state goes out of scope.

UpdateRequestCache

Cache can be updated here if content has changed.

EndRequest

The last chance to do something with the request or response.

Error

Raised if some error occurs in the processing pipeline.

PreSendRequestHeaders

Signals that headers are about to be sent to the client. This is your chance to modify those headers.

PreSendRequestContent

Signals that the content of the response is about to be sent to the client. This is a good place to make modifications to content.

Figure 3. This is the HttpApplication's lifecycle. As an HTTP request goes through the processing lifecycle, events are raised at key milestones to let you perform custom processing. The available events are shown in order. The last three events occur non-deterministically any time throughout the processing lifecycle.

 

Insert Yourself

HTTP modules provide the perfect insertion point for adding functionality or overriding behavior in Web applications because they reside between the application and the handler for every request that comes through. All you need to do to create one is create a class that implements the IHttpModule interface. This interface defines two methods: Init and Dispose. Init is called when your module is loaded and it's where you can wire up handlers for the HttpApplication events you're interested in. The Dispose method is called when the module is being unloaded. This method provides an opportunity to clean up any resources you might have held open. Then, you simply need to implement event handlers for the events you are interested in.

 

If you want your module to be used for all applications on the machine, add it to the machine.config file. If you want it to apply to one Web application only, add it to the web.config file for that app. The machine.config file contains already, which adds the modules ASP.NET provides for authentication, caching, and state management. You can add modules at the application level simply by adding an section to the web.config file:

 

  

  type="MonitoredSite.UserTracker,

  MonitoredSite"/>

 

You use an element for each module you want to add to the processing pipeline. They are processed in the order they are presented in the list (from top to bottom). You provide a name for the module, the fully qualified name of the class that implements the IHttpModule interface, and the name of the assembly that contains that class. The assembly is expected to live in the bin folder for the Web application. You also could remove a module that was added in the machine.config file if you want to provide your own implementation. For example, if you had your own module for handling forms authentication, you could add this code to your web.config file:

 

  

  

  type="MyModules.MyFormsAuthenticationModule, Module1"/>

 

Now I'll take you through building an HTTP module that tracks user activity on a site. This article's downloadable code includes an ASP.NET Web application project named MonitoredSiteVB. In it is an HTTP module class named UserTracker. The module creates a log of user activity on the site, including the time, session ID, username, and pages visited. The module also tracks whether the user encountered any errors. I kept the logging implementation simple in this sample, but you could implement a more scalable approach in a similar fashion to write the user activity to a database or other store. For a production module, you probably would want to implement it in a separate class-library assembly project so it could be reused easily for other sites.

 

The first step in building the module is to create the module's class. This is a simple class that implements the IHttpModule interface and its two methods, Init and Dispose. Then, you need to decide which HttpApplication events you want to subscribe to. In this application, I subscribed to the PostRequestHandlerExecute and Error events and added handlers for them. I chose the PostRequestHandlerExecute event because I wanted to log a session ID as well as the user and page they requested. If I had tried to do this in the EndRequest event, the session would be out of scope already and thus unavailable. Each HttpApplication event can be handled by an instance of the EventHandler class, and the handlers are all passed the same parameters: an object that is a reference to the current HttpApplication instance servicing this request, and an EventArgs object that is empty.

 

After creating an ASP.NET project in Visual Studio and adding some simple pages to demonstrate navigation on the site, I added the class in Figure 4 to the project as a new item.

 

Imports System.Web

Imports System.IO

Imports System.Xml

Public Class UserTracker

  Implements IHttpModule

 

  Public Sub Init(ByVal app As HttpApplication) _

    Implements IHttpModule.Init

      AddHandler app.PostRequestHandlerExecute, _

        AddressOf Me.OnPostRequestHandlerExecute

      AddHandler app.Error, AddressOf Me.OnError

  End Sub

 

  Public Sub Dispose() Implements IHttpModule.Dispose

      ' Nothing needed here

  End Sub

 

  Public Sub OnPostRequestHandlerExecute(ByVal appObj As _

    Object, ByVal eArgs As EventArgs)

        ' Details omitted

  End Sub

 

  Public Sub OnError(ByVal appObj As Object, ByVal eArgs _

    As EventArgs)

        ' Details omitted

  End Sub

 

End Class

Figure 4. The UserTracker HTTP module class provides the extension of the HTTP processing pipeline for any applications that include it in the HttpModules section of their web.config file. The class implements the IHttpModule interface, which allows it to plug in to any ASP.NET Web application.

 

Figure 5 details how to wire up the events and handle them. Basically, each handler begins by casting the appObj parameter to an HttpApplication, then uses that object to access the Request.Url, User, and Session properties of the application object. Then, the handler writes out the date and time, session ID, username, and URL to a log file.

 

Public Sub Init(ByVal app As HttpApplication) _

    Implements IHttpModule.Init

    ' Add handlers for the events we want

      AddHandler app.PostRequestHandlerExecute, _

         AddressOf Me.OnPostRequestHandlerExecute

      AddHandler app.Error, AddressOf Me.OnError

      fname = "C:\logfile.txt"

End Sub

 

Public Sub OnPostRequestHandlerExecute( _

     ByVal appObj As Object, _

     ByVal eArgs As EventArgs)

  Try

    Dim app As HttpApplication

    Dim dttm As String

    Dim sesid As String

    Dim username As String

    Dim url As String

    Dim fname As String

 

    ' Get the appObj as a typed HttpApplication instance

    app = CType(appObj, HttpApplication)

    ' Get the current Date and Time

    dttm = DateTime.Now.ToString

    ' Get the session ID

    sesid = app.Session.SessionID

    ' Get the username

    username = app.User.Identity.Name

    ' Get the URL requested

    url = app.Request.Url.ToString

    ' Write to the logfile

    fname = HttpContext.Current.Server.MapPath( _

      "./MonitoredSiteVB.log")

    Dim sw As New StreamWriter(fname, True)

    sw.WriteLine(dttm + ", " + sesid + +

            ", " + username + ", " + url)

    sw.Close()

  Catch e As Exception

     ' Do nothing

  End Try

End Sub

Figure 5. Connect and handle the ASP.NET application events in your HTTP module. You can wire up event handlers in the Init method called when the module loads, then you can perform any custom processing you want to insert into the HTTP request-processing pipeline in your event handlers.

 

The next thing you need to do to ensure your HTTP module is called in the processing of the application is to add it to the web.config file as I showed you before. For the VB project, this is how it looks:

 

  

    type="MonitoredSiteVB.UserTracker,

    MonitoredSiteVB"/>

 

Because the app writes out a log file to the Web application directory using the ASPNET account through which ASP.NET runs, you also need to grant that account write permission to the application folder, then add this code to your web.config file to ensure people cannot view your logs:

 

  

    type="System.Web.HttpForbiddenHandler" />

 

Now you should be ready to run. Your module will be loaded when the application starts up and will intercept the events for each request when it comes in. Keep in mind that if the user clicks on the Back button in the browser (or presses the backspace key), a request will not be issued, so you won't see a log entry. Note also that embedded images on the page are associated with the same request as the page itself, so they will not result in a separate request even if they are configured to be handled by ASP.NET instead of IIS directly.

 

What else could you use HTTP modules for? Obviously a lot more than I could mention or even imagine. But some common scenarios would be to implement your own authentication service, pre-process destination URLs to redirect or perform some custom processing on each request, or modify the return response headers, such as to mask actual paths on your server with aliased paths. You could process outbound responses to screen out "dirty" words or proprietary phrases that might creep into documents published to the Web or that come from an HTML-based discussion forum. Or maybe you want to implement your very own Web-based protocol that does something similar to SOAP. The possibilities are endless, but the beauty of it is how easy HTTP modules are to write and configure as you have seen.

 

HTTP modules are an easy way to add or modify the behavior of a site without modifying the code for the pages of the site. You also can handle events at the application level through your global.aspx file, or at the handler level with classes or pages that implement the IHttpHandler interface. Whichever way you go, ASP.NET provides you with much more flexibility for customizing the behavior of your Web application than was possible with ASP - and in a way that is a great deal easier to implement than ISAPI filters or extensions.

 

The sample code in this article is available for download.

 

Brian Noyes is an independent software consultant and president of Software Insight (http://www.softinsight.com). He's a Microsoft Certified Solution Developer with more than 11 years of programming, design, and engineering experience. Brian specializes in architecture, design, and coding of cutting-edge software systems, and he is a contributing editor for asp.netPRO and other publications. E-mail him at mailto:[email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish