So we’ve learned that any
incoming requests for ASP.NET resources are handed over to the worker
process for the actual processing within the context of the CLR. In IIS
6.0, the worker process is a distinct process from IIS, so if one
ASP.NET application crashes, it doesn’t bring down the whole server.
ASP.NET manages a pool of HttpApplication
objects for each running application and picks up one of the pooled
instances to serve a particular request. These objects are based on the
class defined in your global.asax file, or on the base HttpApplication class if global.asax is missing. The ultimate goal of the HttpApplication object in charge of the request is getting an HTTP handler.
On the way to the final HTTP handler, the HttpApplication object makes the request pass through a pipeline of HTTP modules. An HTTP module is a .NET Framework class that implements the IHttpModule interface. The HTTP modules that filter the raw data within the request are configured on a per-application basis within the web.config file. All ASP.NET applications, though, inherit a bunch of system HTTP modules configured in the global web.config file.
Generally speaking, an HTTP
module can pre-process and post-process a request, and it intercepts
and handles system events as well as events raised by other modules. The
highly-configurable nature of ASP.NET makes it possible for you to also
write and register your own HTTP modules and make them plug into the
ASP.NET runtime pipeline, handle system events, and fire their own
events.
The IHttpModule Interface
The IHttpModule interface defines only two methods—Init and Dispose. The Init
method initializes a module and prepares it to handle requests. At this
time, you subscribe to receive notifications for the events of
interest. The Dispose method disposes of the resources (all but memory!) used by the module. Typical tasks you perform within the Dispose method are closing database connections or file handles.
The IHttpModule methods have the following signatures:
void Init(HttpApplication app);
void Dispose();
The Init method receives a reference to the HttpApplication object that is serving the request. You can use this reference to wire up to system events. The HttpApplication object also features a property named Context that provides access to the intrinsic properties of the ASP.NET application. In this way, you gain access to Response, Request, Session, and the like.
Table 1 lists the events that HTTP modules can listen to and handle.
Table 1. HttpApplication Events
Event | Description |
---|
AcquireRequestState, PostAcquireRequestState | Occurs when the handler that will actually serve the request acquires the state information associated with the request. The post event is not available in ASP.NET 1.x. |
AuthenticateRequest, PostAuthenticateRequest | Occurs when a security module has established the identity of the user. The post event is not available in ASP.NET 1.x. |
AuthorizeRequest, PostAuthorizeRequest | Occurs when a security module has verified user authorization. The post event is not available in ASP.NET 1.x. |
BeginRequest | Occurs as soon as the HTTP pipeline begins to process the request. |
Disposed | Occurs when the HttpApplication object is disposed of as a result of a call to Dispose. |
EndRequest | Occurs as the last event in the HTTP pipeline chain of execution. |
Error | Occurs when an unhandled exception is thrown. |
PostMapRequestHandler | Occurs when the HTTP handler to serve the request has been found. The event is not available in ASP.NET 1.x. |
PostRequestHandlerExecute | Occurs when the HTTP handler of choice finishes execution. The response text has been generated at this point. |
PreRequestHandlerExecute | Occurs just before the HTTP handler of choice begins to work. |
PreSendRequestContent | Occurs just before the ASP.NET runtime sends the response text to the client. |
PreSendRequestHeaders | Occurs just before the ASP.NET runtime sends HTTP headers to the client. |
ReleaseRequestState, PostReleaseRequestState | Occurs when the handler releases the state information associated with the current request. The post event is not available in ASP.NET 1.x. |
ResolveRequestCache, PostResolveRequestCache | Occurs when the ASP.NET runtime resolves the request through the output cache. The post event is not available in ASP.NET 1.x. |
UpdateRequestCache, PostUpdateRequestCache | Occurs
when the ASP.NET runtime stores the response of the current request in
the output cache to be used to serve subsequent requests. The post event is not available in ASP.NET 1.x. |
All these events are exposed by the HttpApplication object that an HTTP module receives as an argument to the Init method.
A Custom HTTP Module
Let’s begin coming to grips with HTTP modules by writing a relatively simple custom module named Marker
that adds a signature at the beginning and end of each page served by
the application. The following code outlines the class we need to write:
using System;
using System.Web;
namespace Core35.Components
{
public class MarkerModule : IHttpModule
{
public void Init(HttpApplication app)
{
// Register for pipeline events
}
public void Dispose()
{
// Nothing to do here
}
}
}
The Init method is invoked by the HttpApplication class to load the module. In the Init method, you normally don’t need to do more than simply register your own event handlers. The Dispose method is, more often than not, empty. The heart of the HTTP module is really in the event handlers you define.
Wiring Up Events
The sample Marker module registers a couple of pipeline events. They are BeginRequest and EndRequest. BeginRequest is the first event that hits the HTTP application object when the request begins processing. EndRequest
is the event that signals the request is going to be terminated, and
it’s your last chance to intervene. By handling these two events, you
can write custom text to the output stream before and after the regular
HTTP handler—the Page-derived class.
The following listing shows the implementation of the Init and Dispose methods for the sample module:
public void Init(HttpApplication app)
{
// Register for pipeline events
app.BeginRequest += new EventHandler(OnBeginRequest);
app.EndRequest += new EventHandler(OnEndRequest);
}
public void Dispose()
{
}
The BeginRequest and EndRequest event handlers have a similar structure. They obtain a reference to the current HttpApplication object from the sender and get the HTTP context from there. Next, they work with the Response object to append text or a custom header:
public void OnBeginRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication) sender;
HttpContext ctx = app.Context;
// More code here
...
// Add custom header to the HTTP response
ctx.Response.AppendHeader("Author", "DinoE");
// PageHeaderText is a constant string defined elsewhere
ctx.Response.Write(PageHeaderText);
}
public void OnEndRequest(object sender, EventArgs e)
{
// Get access to the HTTP context
HttpApplication app = (HttpApplication) sender;
HttpContext ctx = app.Context;
// More code here
...
// Append some custom text
// PageFooterText is a constant string defined elsewhere
ctx.Response.Write(PageFooterText);
}
OnBeginRequest writes standard page header text and also adds a custom HTTP header. OnEndRequest simply appends the page footer. The effect of this HTTP module is visible in Figure 1.
Registering with the Configuration File
You register a new HTTP module by adding an entry to the <httpModules> section of the configuration file. The overall syntax of the <httpModules> section closely resembles that of HTTP handlers. To add a new module, you use the <add> node and specify the name and type attributes. The name attribute contains the public name of the module. This name is used to select the module within the HttpApplication’s Modules
collection. If the module fires custom events, this name is also used
as the prefix for building automatic event handlers in the global.asax file:
<system.web>
<httpModules>
<add name="Marker"
type="Core35.Components.MarkerModule,Core35Lib" />
</httpModules>
</system.web>
The type
attribute is the usual comma-separated string that contains the name of
the class and the related assembly. The configuration settings can be
entered into the application’s configuration file as well as into the
global web.config
file. In the former case, only pages within the application are
affected; in the latter case, all pages within all applications are
processed by the specified module.
The order in which
modules are applied depends on the physical order of the modules in the
configuration list. You can remove a system module and replace it with
your own that provides a similar functionality. In this case, in the
application’s web.config file you use the <remove> node to drop the default module and then use <add>
to insert your own. If you want to completely redefine the order of
HTTP modules for your application, you can clear all the default modules
by using the <clear> node and then re-register them all in the order you prefer.
Note
HTTP
modules are loaded and initialized only once, at the startup of the
application. Unlike HTTP handlers, they apply to just any requests. So
when you plan to create a new HTTP module, you should first wonder
whether its functionality should span all possible requests in the
application. Is it possible to choose which requests an HTTP module
should process? The Init
method is called only once in the application’s lifetime; but the
handlers you register are called once for each request. So to operate
only on certain pages, you can do as follows: public void OnBeginRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication) sender;
HttpContext ctx = app.Context;
if (!ShouldHook(ctx))
return;
...
}
OnBeginRequest is your handler for the BeginRequest event. The ShouldHook
helper function returns a Boolean value. It is passed the context of
the request—that is, any information that is available on the request.
You can code it to check the URL as well as any HTTP content type and
headers. |
Accessing Other HTTP Modules
The sample just discussed demonstrates how to wire up pipeline events—that is, events fired by the HttpApplication object. But what about events fired by other modules? The HttpApplication object provides a property named Modules that gets the collection of modules for the current application.
The Modules property is of type HttpModuleCollection and contains the names of the modules for the application. The collection class inherits from the abstract class NameObjectCollectionBase,
which is a collection of pairs made of a string and an object. The
string indicates the public name of the module; the object is the actual
instance of the module. To access the module that handles the session
state, you need code like this:
SessionStateModule sess = app.Modules["Session"];
sess.Start += new EventHandler(OnSessionStart);
As mentioned, you can also handle events raised by HTTP modules within the global.asax file and use the ModuleName_EventName
convention to name the event handlers. The name of the module is just
one of the settings you need to define when registering an HTTP module.