利用 IHttpModule 自定义 HTTP 处理模块
本文内容
- 引入
- IHttpModule 概述
- 创建自定义 HTTP 模块的步骤
- 演示创建自定义 HTTP 模块
- HTTP 模块的工作方式
- HTTP 模块与 Global.asax 文件
- 参考资料
引入
本文在 VS 2008 和 IIS 6 环境下概述如何利用 IHttpModule 自定义 HTTP 模块。
当我们在 VS 2008 里新建一个 Web 应用程序项目后,会在 Web.config 文件看到如下一个配置:
<httpModules>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>
我们知道 VS 2008 已经可以开发 Ajax 程序,异步刷新界面,如 ScriptManager 控件、UpdatePanel 控件,那么 System.Web.Handlers.ScriptModule 类就是管理用于 ASP.NET 中 AJAX 功能的 HTTP 模块。
更进一步,看它 C# 声明:
[AspNetHostingPermissionAttribute(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermissionAttribute(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class ScriptModule : IHttpModule
该类继承了 IHttpModule 接口。代码如下所示。看看它的事件,你多少体会出如若让 Web 应用程序支持 Ajax 功能,发出异步请求,需要做什么,至少能够检查传入和传出的请求。
//
// ScriptModule.cs
//
// Author:
// Igor Zelmanovich <igorz@mainsoft.com>
//
// (C) 2007 Mainsoft, Inc. http://www.mainsoft.com
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.Script.Services;
namespace System.Web.Handlers
{
public class ScriptModule : IHttpModule
{
protected virtual void Init (HttpApplication context) {
context.PreSendRequestHeaders += new EventHandler (PreSendRequestHeaders);
context.PostAcquireRequestState += new EventHandler (PostAcquireRequestState);
context.AuthenticateRequest += new EventHandler (AuthenticateRequest);
}
void AuthenticateRequest (object sender, EventArgs e) {
// The AuthenticateRequest event is raised after the identity of the current user has been
// established. The handler for this event sets the SkipAuthorization property of the HttpContext
// for the current request. This property is checked in the authorization module to see
// if it has to omit authorization checking for the requested url. Usually an HttpModule
// use this property to allow anonymous access to some resources (for example,
// the Login Page if we’re using forms authentication). In our scenario,
// the ScriptModule sets the SkipAuthorization to true if the requested url is
// scriptresource.axd or if the authorization module is enabled and the request is a rest
// request to the authorization web service.
}
void PostAcquireRequestState (object sender, EventArgs e) {
// The PostAcquireRequestState event is raised after the session data has been obtained.
// If the request is for a class that implements System.Web.UI.Page and it is a rest
// method call, the WebServiceData class (that was explained in a previous post) is used
// to call the requested method from the Page. After the method has been called,
// the CompleteRequest method is called, bypassing all pipeline events and executing
// the EndRequest method. This allows MS AJAX to be able to call a method on a page
// instead of having to create a web service to call a method.
HttpApplication app = (HttpApplication) sender;
HttpContext context = app.Context;
if (context == null)
return;
HttpRequest request = context.Request;
string contentType = request.ContentType;
IHttpHandler currentHandler = context.CurrentHandler;
if (currentHandler == null)
return;
#if TARGET_J2EE
if (!(currentHandler is Page) && currentHandler is IServiceProvider) {
pageType = (Type) ((IServiceProvider) currentHandler).GetService (typeof (Type));
if (pageType == null)
return;
}
#endif
Type pageType = currentHandler.GetType ();
if (typeof (Page).IsAssignableFrom (pageType) && !String.IsNullOrEmpty (contentType) && contentType.StartsWith ("application/json", StringComparison.OrdinalIgnoreCase)) {
IHttpHandler h = RestHandler.GetHandler (context, pageType, request.FilePath);
h.ProcessRequest (context);
app.CompleteRequest ();
}
}
void PreSendRequestHeaders (object sender, EventArgs e) {
HttpApplication app = (HttpApplication) sender;
HttpContext context = app.Context;
if (context.Request.Headers ["X-MicrosoftAjax"] == "Delta=true") {
Page p = context.CurrentHandler as Page;
#if TARGET_J2EE
if (p == null && context.CurrentHandler is IServiceProvider)
p = (Page) ((IServiceProvider) context.CurrentHandler).GetService (typeof (Page));
#endif
if (p == null)
return;
ScriptManager sm = ScriptManager.GetCurrent (p);
if (sm == null)
return;
if (context.Response.StatusCode == 302) {
context.Response.StatusCode = 200;
context.Response.ClearContent ();
if (context.Error == null || sm.AllowCustomErrorsRedirect)
ScriptManager.WriteCallbackRedirect (context.Response.Output, context.Response.RedirectLocation);
else
sm.WriteCallbackException (context.Response.Output, context.Error, false);
}
else if (context.Error != null) {
context.Response.StatusCode = 200;
context.Response.ClearContent ();
sm.WriteCallbackException (context.Response.Output, context.Error, true);
}
}
}
protected virtual void Dispose () {
}
#region IHttpModule Members
void IHttpModule.Dispose () {
Dispose ();
}
void IHttpModule.Init (HttpApplication context) {
Init (context);
}
#endregion
}
}
IHttpModule 概述
HTTP 模块是一个在每次针对应用程序发出请求时调用的程序集。HTTP 模块作为 ASP.NET 请求管道的一部分调用,它们能够在整个请求过程中访问生命周期事件。HTTP 模块使您可以检查传入和传出的请求并根据该请求进行操作。
HTTP 模块通常具有以下用途:
- 安全 因为您可以检查传入的请求,所以 HTTP 模块可以在调用请求页、XML Web services 或处理程序之前执行自定义的身份验证或其他安全检查。
- 统计信息和日志记录 因为 HTTP 模块是在每次请求时调用的,所以,您可以将请求统计信息和日志信息收集到一个集中的模块中,而不是收集到各页中。
- 自定义的页眉或页脚 因为您可以修改传出响应,所以可以在每一个页面或 XML Web services 响应中插入内容,如自定义的标头信息。
创建自定义 HTTP 模块的步骤
编写 HTTP 模块的一般过程如下:
- 创建一个实现 IHttpModule 接口的类。
- 实现 Init 方法。该方法初始化模块,并创建所需的任何应用程序事件。例如,如果希望为响应附加一些内容,可以创建 EndRequest 事件。如果希望执行自定义身份验证逻辑,可以创建 AuthenticateRequest 事件。
- 为已创建的事件编写代码。
- 若模块需要释放,还可以实现 Dispose 方法。
- 在 Web.config 文件中注册模块。
演示创建自定义 HTTP 模块
将一个字符串追加到响应的开头和末尾。在对其扩展名已分配给 ASP.NET 的文件进行任何请求的过程中,该模块都将自动运行。
- 新建 Web 项目。
- 新建名为 HelloWorldModule 的类,该类继承 IHttpModule,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace HttpModuleDemo
{
public class HelloWorldModule : IHttpModule
{
public HelloWorldModule()
{
}
public String ModuleName
{
get { return "HelloWorldModule"; }
}
// In the Init function, register for HttpApplication
// events by adding your handlers.
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
application.EndRequest +=
(new EventHandler(this.Application_EndRequest));
}
private void Application_BeginRequest(Object source,
EventArgs e)
{
// Create HttpApplication and HttpContext objects to access
// request and response properties.
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
string filePath = context.Request.FilePath;
string fileExtension =
VirtualPathUtility.GetExtension(filePath);
if (fileExtension.Equals(".aspx"))
{
context.Response.Write("<h1><font color=red>" +
"HelloWorldModule: Beginning of Request" +
"</font></h1><hr>");
}
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
string filePath = context.Request.FilePath;
string fileExtension =
VirtualPathUtility.GetExtension(filePath);
if (fileExtension.Equals(".aspx"))
{
context.Response.Write("<hr><h1><font color=red>" +
"HelloWorldModule: End of Request</font></h1>");
}
}
public void Dispose() { }
}
}
- 在 Web.config 文件注册该模块,如下所示:
<httpModules>
<add name="HelloWorldModule" type="HttpModuleDemo.HelloWorldModule"/>
</httpModules>
- 新建一个页面,并用浏览器查看该页面,页面会显示如下信息。
HTTP 模块的工作方式
模块必须注册才能从请求管道接收通知。注册 HTTP 模块的最常用方法是在应用程序的 Web.config 文件中进行注册。
当 ASP.NET 创建表示您的应用程序的 HttpApplication 类的实例时,将创建已注册的任何模块的实例。在创建模块时,将调用它的 Init 方法,并且模块会自行初始化。
当这些应用程序事件被引发时,将调用模块中的相应方法。该方法可以执行所需的任何逻辑。如检查身份验证或记录请求信息。在事件处理过程中,模块能访问当前请求的 Context 属性。这使得可以将请求重定向到其他页、修改请求或者执行任何其他请求操作。例如,若模块检查身份验证,则在凭据不正确的情况下,模块可能重定向到登录页或错误页。否则,当模块的事件处理程序完成运行时,ASP.NET 会调用管道中的下一个进程。这可能是另一个模块,也可能是用于该请求的 HTTP 处理程序(如 .aspx 文件)。
HTTP 模块与 Global.asax 文件
之所以把它们放在一起,是因为在某种程度上,它们功能有相似之处。
可以在应用程序的 Global.asax 文件中实现模块的许多功能,这使你可以响应应用程序事件。但是,
- 模块相对于 Global.asax 文件具有如下优点:模块可以进行封装。创建一次后在其他应用程序中使用。通过将它们添加到全局程序集缓存,并将其注册到 Machine.config 文件中,可以跨应用程序重新使用它们。
- 使用 Global.asax 文件的好处在于可以处理其他已注册事件,如 Session_Start 和 Session_End。此外,Global.asax 文件还允许您实例化可在整个应用程序中使用的全局对象。
当您必须创建依赖应用程序事件的代码,并且符合以下条件时,都应该使用模块:
- 希望在其他应用程序中重用该模块。
- 希望避免将复杂代码放在 Global.asax 文件中。
- 该模块应用于管道中的所有请求(仅限 IIS 7.0 集成模式)。
当您必须创建依赖应用程序事件的代码并且不需要跨应用程序重用代码时,应该将代码添加到 Global.asax 文件中。当必须订阅对于模块不可用的事件(如 Session_Start)时,也可以使用 Global.asax 文件。
参考资料
MSDN HTTP 处理程序和 HTTP 模块概述 http://msdn.microsoft.com/zh-cn/library/bb398986(v=VS.90).aspx
MSDN IHttpModule http://msdn.microsoft.com/zh-cn/library/system.web.ihttpmodule(v=VS.90).aspx