技术开发 频道

ASP.NET触发管道事件


【IT168专稿】

穿过ASP.NET管道

    HttpApplication负责请求的传输,通过触发事件,通知应用程序正在发生的事情。这个是作为HttpApplication.Init()方法的一部分实现的(使用Refector查看System.Web.HttpApplication.InitInternal和HttpApplication.ResumeSteps()的代码可以看到这一点)。在这个方法里,创建和启动了一系列事件,包括绑定事件处理器。Global.asax里的事件处理器会自动映射到对应的事件,它们也可以映射到额外添加的HTTPModules,这些HTTPModules本质上是HttpApplication已发布事件的一种扩展。

    通过在web.config里注册,HTTPModules和HttpHandlers可以被动态的加载,并且可以添加到事件链条上。HTTPModules实际上就是事件处理器,它可以钩住指定HttpApplication的事件。而HttpHandlers就是一个端点,它可以被调用处理“应用程序级的请求处理”。

    加载HTTPModules和HttpHandlers,然后添加到调用链上作为HttpApplication.Init()方法调用的一部分。图6展示了不同的事件,这些事件何时被触发以及通道上的哪些部分会受到它们的影响。



    图1事件在ASP.NET HTTP管道里传输。HttpApplication对象的事件驱动请求在管道里传输。HTTPModules可以截获这些事件,可以覆盖或增强已存在的功能。 


HttpContext,HttpModules和HttpHandlers


   HttpApplication本身并不知晓发送给Web程序的数据。它仅仅是个消息邮递者,只负责事件之间的通信。它触发事件,然后通过传递HttpContext对象,把信息发送给被调用的方法。在之前我们提到,当前请求的数据是在HttpContext对象里保存。它提供了请求从开始到结束需要的所有数据。图7展示了ASP.NET的管道之间的传输流程。注意,从请求的开始至结束,上下文对象(Context)都是可以在一个事件方法里使用它保存数据,然后在之后的事件方法里获取这些数据。



   图2ASP.NET管道在一系列事件接口之间传输请求,这提供了足够的灵活性。HttpApplication担当主容器,负责加载Web程序,当请求到来时触发事件以及在管道之间传输请求。经过已配置的HTTP过滤和模块时,每一个请求都将遵循一个公有的路径。过滤器可以检查穿梭在管道里的每一个请求,而处理器允许实现应用程序的逻辑或者应用程序级的接口像WebForms和WebServices一样。为了给程序提供输入和输出,上下文对象(Context)给请求提供了所需的信息,它贯穿了请求生命周期的始终。

   ASP.NET管道一旦启动,HttpApplication将逐一触发事件,如图6展示的那样。每一个事件都将被触发,如果事件绑定了事件处理器,那么这些事件处理器将被调用,执行它们的任务。这个过程的主要目的是通过调用HttpHandler处理指定的请求。对于ASP.NET请求而言,HttpHandler是处理请求机制的核心,在这里任意的应用程序级的代码被执行。记住,ASP.NET的页面和Web Service都是HttpHandler的具体实现,在这里,所有请求处理的核心功能被实现。HTTPModule则倾向于在分发给事件处理器之前或者之后对内容进行处理。在ASP.NET里典型的默认操作有:鉴定(Authentication),处理前的缓存操作以及各种处理后的编码操作机制。

   关于HTTPModule和HttpHandler,这里有很多有用的信息,但为了保持这篇文章合理的尺度,我将仅仅讲述关于它们一些简短的、整体的看法。

HttpModules

   伴随着HttpApplication触发的一系列事件,请求将会在管道之间穿梭。我们已经看到了这些发布的事件,在Global.asax里都有对应事件的处理方法。这个方法(步骤)是程序(ASP.NET应用程序)携带的,尽管这个并不总是你需要的。如果你想构建一套通用的HttpApplication事件处理程序,并以插件的形式添加到任意的Web程序里。那么你可以使用HTTPModule,它是可重复使用的,不需要添加任何实现代码就可以在其它程序里使用,而你所做的仅仅在web.config里注册。

   模块本质上是过滤器,在功能上类似于ASP.NET请求级别的ISAPI过滤。对于每一个穿过ASP.NET的HttpApplication对象的请求,模块都允许在HttpApplication对象触发的事件处理方法里截获这些请求。这些模块以类的形式存储在外部程序集里,可以在web.config里配置,当程序启动的时候加载。通过实现指定的接口和方法,模块就可以被添加到HttpApplication的事件链上。多个HttpModules可以钩住相同的事件,事件发生的顺序是它们在web.config里声明(配置)的顺序。如下就是在web.config里,一个模块的声明。

<configuration> 
<system.web>
<httpModules>
<add name= "BasicAuthModule"
type="HttpHandlers.BasicAuth,WebStore" />
</httpModules>
</system.web>
</configuration>
注意:在这里需要指定一个完整的类型名和一个不带扩展名的程序集的名字。
模块允许你查看每一个传入的Web请求,基于触发的事件基础上执行操作。模块是非常有用的,它可以修改请求,输出响应的内容以及提供自定义的身份验证,另外还可以在特定的程序里,针对ASP.NET的每一个请求提供响应前处理和响应后处理。许多ASP.NET的特征像身份验证,会话引擎都是作为HTTP模块实现的。
HttpModules感觉有点类似于ISAPI过滤器,是由于它们查看进入ASP.NET程序的每一个请求,但它们局限性于仅可以查看映射到某一个ASP.NET程序或者虚拟目录的请求,和映射到ASP.NET的请求。因此,仅可以查看所有的ASPX页面或者任意其它自定义的已经映射到这个程序的扩展名(译注:作者可能漏掉了ASMX,ASHX等,这里的意思应该是所有的ASP.NET默认的扩展名)。但是不能查看标准的.HTM或者图像文件,除非通过添加这些扩展名,明确的把它们映射到ASP.NET的ISAPI DLL,如图一中那样。对于模块,一个常见的用处是过滤一个指定的文件夹的JPG图片内容,然后使用GDI+在每一张返回的图片上方添加“样图”字样。
实现一个HTTP模块是非常简单的:必须实现一个IHttpModule接口,它包含两个方法:Init()和Dispose()。传递的事件参数中包含着一个HttpApplication对象的引用,接着,它会给访问HttpContext对象的权限。在这两个方法里,可以勾选HttpApplication的事件。举个例子,如果想用一个模块勾选AuthenticateRequest事件,那么需要做的会像列表5中展示的那样。

列表 5: 一个HTTP模块实现起来非常的简单

public class BasicAuthCustomModule : IHttpModule 
{

public void Init(HttpApplication application)
{
// *** Hook up any HttpApplication events
application.AuthenticateRequest +=
new EventHandler(this.OnAuthenticateRequest);
}
public void Dispose() { }

public void OnAuthenticateRequest(object source,
EventArgs eventArgs)
{
HttpApplication app = (HttpApplication) source;
HttpContext Context = HttpContext.Current;
… do what you have to do… }
}


   记住,模块已经有访问HttpContext对象的权限了。从这里到所有其它内置的ASP.NET管道对象如Response和Request,因此可以获取输入的数据等等。但紧记,某些对象现在可能不能使用,只有到这条链的后面的环节才会有效, 在Init()方法里,可以勾选多个事件,因此在一个模块里,可以管理多个不同功能的操作。但是,应该尽可能的把不同的逻辑代码放到不同的类里,这样可以确保这个模块是标准的组件。在许多情况下实现的功能可能需要钩住多个事件。举个例子,一个日志过滤器可能需要在BeginRequest里记录请求的开始时间,在EndRequest里记录请求完成的时间。

在HttpModules里使用HttpApplication的事件时,有一点是需要注意的,Response.End()和HttpApplication.CompleteRequest()方法会使ASP.NET跳过HttpApplication和模块的事件链。可以查看这两个方法的帮助文档获取更多的信息。

HttpHandlers

模块是相当低层次的,它针对每一个传入ASP.NET程序的请求触发。HTTP处理器则着重于处理一个指定的请求映射。通常一个页面扩展已经被映射到处理器了。

实现一个HTTP处理器所需要做的是非常基础的,但是通过访问HttpContext对象,就会有很多有用的功能。HTTP处理器通过一个简单IHttpHandler接口实现(或者它的异步版本IHttpAsyncHandler)。它仅仅有一个方法ProcessRequest()和一个属性IsReusable。这里的关键是ProcessRequest()会得到一个HttpContext对象的实例。这个单独的方法将从开始到结束负责处理一个Web请求。

单一的,简单的方法?可能太简单了,是吗?可是,一个简单的接口,但它的实现可能并不简单。记住,WebForms和WebServices都是作为HTTP处理器实现的。因此,在这个看似简单的接口里,过多的实现过程被隐藏了。关键是,事实上到现在,一个HTTP处理器可以访问所有为了处理请求而被组建和配置起来的ASP.NET的内置对象。关键是HttpContext对象提供了所有与请求相关的功能,可以获取流入的数据和输出数据到Web服务器。

对于HTTP处理器而言,所有的操作都通过简单地调用ProcessRequest()方法执行。可以简单到如下的样子:

public void ProcessRequest(HttpContext context) 
{
context.Response.Write("Hello World");
}



    一个完整的实现像WebForms页面引擎那样,可以根据HTML模版展现复杂的表单。所以,这里的关键点是你想用这个简单但功能强大的接口做什么。

    HttpContext对象是可以使用的,这样就可以访问Request,Response,Session和Cache对象,因此已经拥有了ASP.NET请求的所有特征,可以自己做主如何处理用户提交的信息,然后给客户端返回处理后产生的内容。记住,在一个ASP.NET请求的生命期内,上下文对象始终都是朋友。

   处理器的关键操作通常是往Response对象里写输出数据,或者更确切的说,是往Response对象的OutputStream里写。这个就是真正返回到客户端的输出数据。在底层,由ISAPIWorkerRequest负责把OutputStream发回给ISAPI的ecb.WriteClient方法,因为ecb.WriteClient方法才是真正执行IIS产生输出数据的。

   通过使用大量的处于高层的、基础的框架接口,WebForms实现了一个HTTP处理器。但最后,WebForm的Render()方法却简单的使用了一个HtmlTextWriter对象,把最终的输出发送给context.Response.OutputStream对象而结束。因此,相当的奇妙,最终甚至一个高层次的工具像WebForm,也仅仅是在Response和Request对象之上的一个高层次的抽象。

    我们可能想知道,是否需要从头实现一个完整的HTTP处理器?毕竟WebForms已经提供了一个易用的HTTP处理器的实现,因此为什么还要为这些大量底层的东西而烦恼,放弃这些已经提供的灵活性呢?

   WebForms是非常棒的,使用它可以生成复杂的HTML页面和处理业务层的逻辑而这些都需要图形化的设计和模版化的页面。除此以外,WebForms引擎还可以完成更多的,需要丰富的表现层的任务。如果所有你想做的是:从系统读取文件,由执行的代码把它返回。这样的任务,如果避开使用Web Forms页面框架,代替的是直接处理文件然后返回,相信后者才是更高效的方法。如果你做的事情仅仅是像从数据库里读取图片一样,那么完全没有必要使用页面框架来处理,因为这里的确没有Web UI需要你俘获离开图片的事件。

   在这里找不到,组建一个页面对象和会话对象以及俘获页面层上事件的理由。对于你的任务而言,这里需要做的仅仅是执行代码,而这些与手头上的任务毫不相干。

   因此,这种情况下使用处理器会更加高效。处理器可以完成使用WebForms不可能完成的事情。比如:需要处理这样的请求,它们没有必要在磁盘上存在对应的物理文件,这些被请求的路径通常称为虚拟的URL。为了使这样的请求正常的工作,你需要确保已经在应用程序的扩展对话框(如图一所示)里关闭了“确认文件是否存在”的复选框。

   对于内容提供者来说,这是通用的,如动态的图像处理,XML服务,提供虚拟的URL使原来的URL重定向,下载管理等等,这些均不能使用WebForms实现。
0
相关文章