对于每个运行中的应用程序,ASP.NET都维护着一个HttpApplication对象的池,特定的请求会逐一由池中的实例处理。这些对象基于定义在global.asax的类创建,如果未定义global.asax,则基于HttpApplication类创建。负责处理请求的HttpApplication对象的最终目标是获取HTTP处理程序。
在获取目标HTTP处理程序前,HttpApplication对象要使请求流经HTTP模块的管道。HTTP模块是实现IHttpModule接口的.NET Framework类,用于筛选请求的中原始数据。HTTP模块会基于每个应用程序的web.config文件进行配置,但所有ASP.NET应用程序都会继承全局的web.config文件中配置的许多系统HTTP模块。
一般来讲,HTTP模块可以对请求进行预处理和后处理,它会监听和处理系统事件和其他模块引发的事件。ASP.NET固有的可配置特性使我们能够编写和注册自定义的HTTP模块,并将其插入到ASP.NET运行时管道中来处理系统和自定义事件。
IHttpModule接口
IHttpModule接口定义了两个方法:Init和Dispose。Init方法用于对模块进行初始化,为处理请求做好准备。同时,我们还可以通过它来订阅所需的事件,以便得到通知。Dispose方法用于释放当前模块占用的资源(除内存外的所有资源)。我们一般在Dispose方法中关闭数据库连接或文件句柄。这两个方法的签名如下:
void Dispose();
下表列出了HTTP模块能够监听和处理的事件:
自定义的HTTP模块
让我们编写一个相对简单的自定义模块Marker,它对当前应用程序处理的所有页面添加页眉和页脚。该类的代码如下:
using System.Web;
using System.IO;
namespace Core35.Components
{
public class MarkerModule : IHttpModule
{
#region Constants
const string PageHeaderText = " <table width=100% style='font-family:verdana;font-size:8pt;' bgcolor=cyan><tr><td align=center>You must register to continue enjoy this Web site! Click <b><a href=http://www.contoso.com>here</a>.</b></td></tr></table> " ;
const string PageFooterText = " <table width=100% style='font-family:verdana;font-size:8pt;' bgcolor=cyan><tr><td align=center>Courtesy of <b>Programming Microsoft ASP.NET 3.5</b></td></tr></table> " ;
#endregion
#region IHttpModule
public void Init(HttpApplication app)
{
// Register for pipeline events
app.BeginRequest += new EventHandler(OnBeginRequest);
app.EndRequest += new EventHandler(OnEndRequest);
}
public void Dispose()
{
// Nothing to do here
}
#endregion
#region Helpers
public void OnBeginRequest( object sender, EventArgs e)
{
HttpApplication app = (HttpApplication) sender;
HttpContext ctx = app.Context;
// More code here
if ( ! ShouldHook(ctx))
return ;
// Add custom header to the HTTP response
ctx.Response.AppendHeader( " Author " , " DinoE " );
ctx.Response.Write(PageHeaderText);
}
public void OnEndRequest( object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpContext ctx = app.Context;
// More code here
if ( ! ShouldHook(ctx))
return ;
// Append some custom text
ctx.Response.Write(PageFooterText);
}
public bool ShouldHook(HttpContext ctx)
{
return ctx.Request.Path.EndsWith( " testmarker.aspx " , StringComparison.OrdinalIgnoreCase);
}
#endregion
}
}
OnBeginRequest添加了一个普通的页面眉和一个自定义的HTTP标头。OnEndRequest只添加页脚。
使用配置文件来注册该模块
我们需要在web.config注册HTTP模块,以使模块生效:
< httpModules >
< add name = " Marker " type = " Core35.Components.MarkerModule,Core35Lib " />
</ httpModules >
</ system.web >
模块生效的顺序取决于它们在配置列表中的物理顺序。我们可以移除某个系统模块,并替换成具有类似功能的自定义模块。在这种情况下,可以在应用程序的web.config文件中使用<remove>节点来删除默认的模块,通过<add>来插入自定义的模块。如果希望为应用程序重新定义所有HTTP模块的顺序,可使用<clear>节点,然后按意愿重新注册。
HTTP模块只在应用程序启动时加载和初始化一次。与HTTP处理程序不同,模块会作用于所有请求。
其他HTTP模块的访问
上例演示了如何订阅管道的事件(即由HttpApplication对象引发的事件)。那么,如果想订阅其他模块引发的事件,如何捕获呢?HttpApplication对象提供了一个名为Modubles的属性,用于获取当前应用程序中模块的集合。
Modules的类型为HttpModuleCollection,包含应用程序中各模块的名称。该集合类继承于抽象类NameObjectCollectionBase,这个抽象类代表由字符串/对象对组成的集合。字符串指示模块的公共名称,对象是模块的实例。如要访问处理会话状态的模块:
sess.Start += new EventHandler(OnSessionStart);
我们还可在global.asac文件中使用ModuleName_EventNamer的事件处理程序命名范式来处理HTTP模块引发的事件。