Implementing an IIS Application Filter Using .NET HttpModules and Response Filtering (cont'd)
HttpModule—the Filter for all Requests
The HttpModule does not replace the target of a request, however the HttpModule receives notification at various processing points during the lifespan of a request. Since (as is the case with HttpHandlers) we can map an HttpModule to all application request pages we can use an HttpModule as the foundation for our web application controller. Let’s start off by taking a look at the IHttpModule interface.

advertisement

The first thing to notice about this interface is the lack of an IsPoolable property. There is a very good reason for this. Unlike HttpHandlers, which are instantiated for each concurrent request, only one instance of any given HttpModule is instantiated for any given application. Naturally, this makes the issue of pooling irrelevant.

Here’s a snippet from the Web.config file on the FPSNow! site:


<httpModules>
     <add type="FPSNow.WebFilter, FPSNow" name="WebFilter" />
</httpModules>

Here’s what this does. After this web site is started, an instance of FPSNow.WebFilter (an HttpModule class) will be instantiated on the first call to a .aspx or .asmx  page (or any extension mapped to aspnet_isapi.dll). For the entire duration of the application, this single class instance will filter all requests to all .aspx and .asmx pages on my site.

Immediately, we can see the implications of this fundamental difference between HttpHandlers, which provide a class instance per request, and HttpModules which provide a single class instance for all requests per application. While HttpHandlers are free to accumulate request specific data in class member variables during the course of a request, HttpModules should definitely not do this! This is because, at any moment the single class instance which is handling an event for particular request, may receive event notification from another request. While private method level variables are safe in this respect, class member variables will be written to and perhaps overwritten by concurrent requests.

Here’s another difference which has positive ramifications when using HttpModules. It is very easy to initialize and maintain consistent application state since a single class instance serves the entire application. Simply load up your complex XML structures and UI templates in the Init method and use this for the remainder of the application’s lifespan. This is perfectly safe for application-wide data which is read-only across the entire scope of the operation. (If this is not the case, then you’ll need to work out some sort of synchronization for modifications to data which is shared between requests. But this would be true in any case, regardless of whether you are using an HttpModule filter or not. For the purposes of our discussion, let’s assume that, once it is initialized, application-wide state is read-only for the remainder of the lifespan of the application and that any change to the state of this data would require an application restart.)

At this point, developers who come from a VB6 background may be somewhat concerned. As I have just described, a single instance of the HttpModule class serves every request to the application. In the VB6 environment, this simply wouldn't scale since a VB6 class instance is forever bound to the thread on which it was created. This thread affinity would present a very real bottleneck since every request would serialized in order to access class methods (or in our case event handlers) via the thread to which it is bound. Fortunately, thread-affinity is no longer a barrier in the .NET environment. The HttpModule class is accessible via any thread which happens to be serving the current request. Since access to the HttpModule doesn't require a specific thread, the single class instance doesn't represent a bottleneck.

To recap, a single class instance of my HttpModule controller class (FPSNow.WebFilter) will filter every single request to my application pages (HttpHandlers). Let’s continue to explore the HttpModule processing model in order to see how we’ll be able to get any meaningful work done under these circumstances.

When this HttpModule class is initially instantiated, the Init method is immediately called:


C#
public void Init(System.Web.HttpApplication context)

VB.NET
Public Sub Init (context As System.Web.HttpApplication) 

As you can see, the application context of the current application is passed in as a single parameter. Let’s take a look at the HttpApplication object in the object browser.

As you can see, the HttpApplication object exposes some very interesting events which we can immediately see are key points in the lifespan of any HTTP request. In order for our HttpModule class to filter (i.e., get involved with) request processing, all we need to do is to provide event handlers for those events which we wish to monitor, and then wire-up our event handlers to the HttpApplication provided events.

The FPSNow! web site monitors three such events that occur during the process of every request. (Well, at least two of them do. We hope the third doesn’t, but we’re prepared in case it does.): 

  • BeginRequest

  • PostRequestHandlerExecute

  • Error

Handlers for these events are defined in the WebFilter class as follows:


C#
private void OnBeginRequest(object o, EventArgs ea) 
private void OnPostRequestHandlerExecute(object o, EventArgs ea) 
private void OnError(object o, EventArgs ea) 

VB.NET
Private Sub OnBeginRequest(o As Object, ea As EventArgs)
Private Sub OnPostRequestHandlerExecute(o As Object, ea As EventArgs) 
Private Sub OnError(o As Object, ea As EventArgs)

In the Init method, which is called when the HttpModule is first instantiated, these event handlers are 'wired up’ to the Application events. The code statements below demonstrate how to associate an event handler with an event. Since the context parameter to the Init method represents the Application object, the following statements associate the HttpModule's event handler to the event provided by the Application object:

C#
context.BeginRequest += new EventHandler(this.OnBeginRequest);

VB.NET
AddHandler context.BeginRequest, AddressOf Me.OnBeginRequest

Basically, every page on the FPSNow! web site is comprised of four sections; Header, NavBar, Content and Footer. When a request occurs, the first handler (which I’ve established) to receive notification is OnBeginRequest. At this point, the header and dynamic NavBar are assembled for the current request and written into the Response buffer. Then control passes to the .aspx HttpHandler target of the current request. The output from the request handler is added into the same Response buffer. Finally, when the HttpHandler is finished, the next handler, OnPostRequestHandlerExecute is notified. At this point, the footer for the current request is assembled and written into the Response stream. Finally, the entire Response stream for the current request is sent back to the browser.

If an error should occur, the OnError handler will be notified. In this event handler, a formatted error message will be assembled and written into the Response buffer. The error will be cleared (Server.ClearError) so that processing will continue. Since the HttpHandler is finished processing, the next event handler to be notified is OnPostRequestHandlerExecute. As with any request, at this point, the footer is assembled and written into the Response stream. Finally, the entire Response stream for the current request is sent back to the browser. So even in the event of an error, the user receives a fully formatted page back to the browser.

I use OnBeginRequest to assemble the header, rather than OnPreRequestHandlerExecute, because OnPreRequestHandlerExecute won’t execute if the HttpHandler doesn’t exist as would be the case with an HTTP request for a non-existent page. On the other hand, OnBeginRequest will always execute even if the page does not exist. By formatting the page header in OnBeginRequest, we can ensure that even if a malformed URL for a non-existent page is requested, the user always gets back a fully formatted page in response. You are invited to check this out with the following link to a non existing page on the FPSNow! site.
Previous Page: Introduction Next Page: HttpModule--the Filter for all Requests (cont'd)
Page 1: IntroductionPage 3: HttpModule--the Filter for all Requests (cont'd)
Page 2: HttpModule--the Filter for All Requests