一、HttpApplication对象简述
在HttpRuntime创建了HttpContext对象之后,HttpRuntime将随后创建一个用于处理请求的对象,这个对象的类型为HttpApplication。
HttpRuntime管理一个定义在System.Web命名空间下的HttpApplicationFactory类的时候,HttpApplicationFactory通过工厂模式管理HttpApplication对象。在HttpApplicationFactory内部维护了一个HttpApplication对象池,使得被创建的HttpApplication对象可以被重复使用。但是,每一个HttpApplication对象每一次仅仅用于处理一个请求,这样就不需要考虑HttpApplication中多个请求并发的处理问题了。
在HttpApplication中,利用.Net中的事件机制,通过在处理过程中依次发出的多个事件,将这个处理过程分解为多个步骤,这个处理机制通常我们称为处理管道。
处理管道,就是处理复杂问题的时候,将处理的过程分解为多个处理步骤,我们将这种经过多个步骤的处理方式称为处理管道。在.Net中,借助于事件的强大威力,我们可以通过处理管道将复杂的处理步骤封装起来,通过事件将处理过程的多个步骤暴露给程序员,以便于程序员对管理管道进行扩展。
HttpApplication处理管道示意图:
二、HttpApplication暴露的事件
但是,对于一个有着众多事件的类来说,定义大量的事件意味着创建对象的时候需要付出创建事件的成本,定义多个事件,意味着在创建的对象中将会需要更多的存储空间。System.ComponentModel.Component类中,提供了处理多个事件的基础:Events属性,它的类型为System.ComponentModel.EventHandlerList,这是一个线性的字段,当需要事件的时候,就通过key将事件保存到集合中,没有对应的事件,就不会付出创建事件的成本,这样,通过EventHandlerList可以在一个集合中管理多个事件对象,节省了事件对象占用的空间。
1、System.ComponentModel.EventHandlerList
该类的常用成员:
成员定义 | 说明 |
void AddHandler() | 将一个委托加入到集合中,key为委托所对应事件的对象 |
void RemoveHandler() | 删除一个委托 |
Delegate this[Object key] | 通过表示事件的Key取得对应的委托 |
这个类在使用的时候很简单,首先从Component派生一个类,这个类继承Component的Events集合属性。对于这个派生类所需要定义的每一个事件,在类中定义一个对应的作为key的对象,以后,通过这个key对象来访问有Events集合管理的事件。当然,用一个类似字典的集合也能够完成这个任务。
下面试试这个类如何用:
namespace ComponentListTest { class Program { static void Main(string[] args) { //这里是Asp.net程序员写的代码,对于程序员而言,只需要写处理事件就可以了 ProcessPipeline process = new ProcessPipeline(); process.StartProcess += new EventHandler(process_StartProcess); process.EndProcess += new EventHandler(process_EndProcess); process.Process(); Console.ReadKey(); } static void process_StartProcess(object sender, EventArgs e) { Console.WriteLine("开始事件执行"); } static void process_EndProcess(object sender, EventArgs e) { Console.WriteLine("结束事件执行"); } } public class ProcessPipeline : System.ComponentModel.Component { private static readonly object startEvent = new object(); private static readonly object endEvent = new object(); public event EventHandler StartProcess { add { this.Events.AddHandler(startEvent, value); } remove { this.Events.RemoveHandler(startEvent, value); } } public event EventHandler EndProcess { add { this.Events.AddHandler(endEvent, value); } remove { this.Events.RemoveHandler(endEvent, value); } } protected void OnStartProcess(EventArgs e) { if (this.Events[startEvent] != null) { (this.Events[startEvent] as EventHandler)(this, e); } } protected void OnEndProcess(EventArgs e) { if (this.Events[endEvent] != null) { (this.Events[endEvent] as EventHandler)(this, e); } } public void Process() { Console.WriteLine("事件开始顺序执行!"); this.OnStartProcess(EventArgs.Empty); this.OnEndProcess(EventArgs.Empty); } } }
HttpApplication对象是Asp.net中处理请求的重要对象,但是,这种类型的对象实例不是由程序员来创建,而是由Asp.net帮助我们创建。为了便于扩展处理,Asp.net暴露了大量的事件给程序员,这些事件按照固定的处理顺序依次触发,程序员通过编写事件处理方法就可以自定义每一个请求的扩展处理过程。
HttpApplication的19个标准事件如下:
事件名称 | 说明 |
BeginRequest | Asp.net处理的第一个事件,表示处理的开始 |
AuthenticateRequest | 验证请求,一般用来取得请求用户的信息 |
PostAuthenticateRequest | 已经获取请求用户的信息 |
AuthorizeRequest | 授权,一般用来检查用户的请求是否获得权限 |
PostAuthorizeRequest | 用户请求已经得到授权 |
ResolveRequestCache | 获取以前处理缓存的处理结果,如果以前缓存过,那么,不必再进行请求的处理工作,直接返回缓存结果 |
PostResolveRequestCache | 已经完成缓存的获取操作 |
PostMapRequestHandler | 已经根据用户的请求,创建了处理请求的处理器对象 |
AcquireRequestState | 取得请求的状态,一般用于Session |
PostAcquireRequestState | 已经取得了Session |
PreRequestHandlerExecute | 准备执行处理程序 |
PostRequestHandlerExecute | 已经执行了处理程序 |
ReleaseRequestState | 释放请求的状态 |
PostReleaseRequestState | 已经释放了请求的状态 |
UpdateRequestCache | 更新缓存 |
PostUpdateRequestCache | 已经更新了缓存 |
LogRequest | 请求的日志操作 |
PostLogRequest | 已经完成了请求的日志操作 |
EndRequest | 本次请求处理完成 |
2、事件简要介绍
Asp.net服务器对于每一次请求的处理过程都相同,都要经过这个HttpApplication处理管道。管道内部的处理过程是固定的,在服务器处理请求的各个阶段,伴随着处理的进行,依次触发对应的事件,以便于程序员在处理的各个阶段完成自定义的处理工作。
1、最先触发的事件是BeginRequest,这个事件标志着Asp.net服务器处理工作得开始,也是程序员在Asp.net中针对请求所能够处理的第一个事件。
2、开始处理请求后,第一个重要工作就是确定请求用户的身份以实现安全机制,这个工作通过AuthenticateRequest和PostAuthenticateRequest两个事件提供检查当前请求用户身份的机会。检查后的用户可以通过HttpContext的User属性获取到。如:
if (HttpContext.Current.User.Identity.IsAuthenticated) { string UserName = HttpContext.Current.User.Identity.Name; }
3、获取用户身份之后,将开始检查权限的工作。如果没有通过安全检查,一般情况下,都跳过剩下的事件,直接出发EndRequest事件,结束请求的处理过程。
4、当用户通过了权限检查,为了最快获得结果,Asp.net将进行缓存检查,看看是否可以从以前的缓存中直接取得处理的结果。
5、当不能从缓存中获取结果的时候,必须通过一次处理来计算出当前请求的结果。在Asp.net中,用于 处理请求以得到结果的对象称为处理程序Handler,在Asp.net中提供了许多的处理程序,程序员也 可以自定义处理程序。为了处理这个请求,Asp.net必须按照匹配规则找到一个处理当前请求的处理 程序,PostMapRequestHandler事件表示Asp.net已经获取了这个处理程序,HttpContext的Handler 属性就表示这个处理程序对象。从上面的分析可以看到,HttpContext的Handler属性到这里才有实 际的意义。
6、虽然有了处理程序对象,但是还不能立即开始处理,因为会话状态还未获取,还需要先获取Session然后才能获取事件处理程序。
PreRequestHandlerExecute通知程序员,马上开始调用处理程序了,有什么没有准备好的赶紧准备。
7、调用处理程序。
8、在处理程序完成之后,服务器开始进行扫尾工作,PostRequestHandlerExecute事件通知程序员,Asp.net服务器的处理程序已经完成。在处理完成之后,由于在处理程序中,用户可能修改了用户特定的专属数据,那么修改之后的用户状态数据可能需要进行序列化或者进行保存处理。ReleaseRequestState事件通知程序员需要释放这些状态数据,PostReleaseRequestState则表示已经释放完成。
9、在处理完成之后,如果希望将这次处理的结果缓存起来,以便在后继的请求中可以直接使用这个结果,UpdateRequestCache事件提供了处理的机会,PostUpdateRequestCache则表示缓存已经更新完成。
10、在Asp.net
4.0之后,新增加了两个时间完成处理的日志工作:LogRequest表示将这次请求记入日志中,PostLogRequest表示完成了日志工作。
EndRequest是所有请求都要经过的最后一个HttpApplication处理管道的事件,也是程序员处理Asp.net处理请求中的最后一个机会。这个事件之后,处理的结果将被回应道浏览器,完成Asp.net服务器的处理工作。
三、HttpContext状态管理
HttpContext通过属性User和Handler传递了当前请求的用户和处理请求所使用的处理程序信息。如果我们还需要从HttpApplication前面的事件向后面的事件处理程序传递一些参数,那么可以通过HttpContext的Items属性来完成。
HttpContext类中定义了一个Items属性,这是一个字典,定义如下:
public IDictionary Items{ get; }
由于HttpContext对象贯穿整个HttpApplication的处理过程,所以,可以借助于这个属性,从处理过程的前面的步骤中,将数据传递给后面的处理步骤,而不需要通过方法的参数或者对象的成员,这种传递参数的方式称为基于HttpContext的状态管理。
这种状态管理方式是Asp.net中周期最短的状态管理方式,因此有着极高的内存使用效率,但是仅仅能够用在服务器端的一次处理过程中。
四、处理HttpApplication事件
HttpApplication提供了基于事件的扩展机制,允许程序员借助于处理管道中的事件进行处理过程扩展。由于HttpApplication对象是由Asp.net基础架构来创建和维护的,那么,如何才能获取这个对象的引用,以便于注册HttpApplication对象的事件处理,在Asp.net中,提供了两种方式来解决这个问题:IHttpModule方式和golbal.asax方式。这两种方式的核心都是IHttpModule接口。
1、通过IHttpModule创建HttpApplication的事件处理程序
在Asp.net中,创建在System.Web命名空间下的IHttpModule接口专门用来定义HttpApplication对象的事件处理。实现IHttpModule接口的类称为HttpModule(Http模块)。
IHttpModule接口的定义如下,其中仅仅包含两个成员:
public interface IHttpModule { void Dispose(); void Init(HttpApplication context) }
其中,Dispose方法用于回收Module所使用的非托管资源,如果没有的话,直接返回即可。
最重要的是Init方法,可以看到,这个方法接收一个HttpApplication类型的参数,在Asp.net中,每当创建一个HttpApplication对象实例,将遍历注册的HttpModule类型,通过反射,依次每个注册HttpModule类型的一个实例对象,并将这个HttpApplication实例通过Init方法传递给各个HttpModule,这样HttpModule就可以在第一时间完成针对HttpApplication对象的事件注册了。
例如:希望写一个处理PostAuthenticateRequest事件的HttpModule,那么可以完成如下事件的注册:
public void Init(System.Web.HttpApplication application) { application.PostAuthenticateRequest += new EventHandler(Application_PostAuthenticateRequest); }
2、注册HttpModule
在Asp.net中,实现IHttpModule接口只是实现HttpModule的第一步,在Asp.net中所使用的HttpModule还必须在网站配置文件中进行注册才能真正生效,并在Asp.net中使用。
配置文件有3个级别,详细可在配置文件系列中找到:http://www.cnblogs.com/kissdodog/category/468516.html
下面以web.config示例进行说明如何注册HttpModule:
system.web配置元素的子元素httpModules用来配置网站所使用的HttpModule;httpModules的子元素add用来增加一个新的HttpModule;clear将清除前面注册的所有HttpModule。
add元素有两个必选的属性name和type,简介如下:
- name表示这个HttpModule在程序中的名字,在网站应用程序中,可以通过这个名字来找到HttpModule对象的引用。HttpApplication的Modules属性表示这个对象所关联的所有HttpModule对象,通过这个name作为索引器,可以找到对应的HttpModule对象。
- type表示HttpModule对象的类型名,Asp.net网站可以使用这个类型名,通过反射来动态创建HttpModule对象。类型的写法就是反射中要求的类型名称写法,如果这个类定义在网站中,那么,就是一个包含命名空间的类的全名,否则,在全名的后面,使用逗号(,)分隔,还需要跟上类型所在的程序集的名称,这个程序集的名称不需要包含.dll扩展名。
对于IIS7.0来说,需要在配置文件的system.webServer配置节中注册HttpModule。注意此时的配置元素名称变为了modules。在IIS7.0中,可以为MapRequestHandler,LogRequest和PostLogRequest事件添加处理程序。
只要在IIS7.0集成模式下运行并且与.Net Framework3.0或更高版本一起运行的应用程序,才可以支持这些事件。
<system.webServer> <modules> <add name="ModuleExample" type="Samples.ModeleExample"> </modules> </system.webServer>
3、非配置文件的方式注册HttpModule
在Asp.net4.0中,可以不修改配置文件也能达到完成Moudle注册的目的。从.Net 3.5开始,新提供的PreApplicationStartMethodAttribute特征可以应用在程序集上,使得自定义的网站初始化代码可以在Web应用程序的Application_Start初始化环节之前就执行。这个步骤甚至在动态编译和执行Application_Start之前。对于每个程序集,可以定义一次。特征的定义如下:
[AttributeUsageAttribute(AttributeTargets.Assembly,AllowMultiple = false)] public sealed class PreApplicationStartMethodAttribute:Attribute { public Type Type{ get; } public string MethodName{ get; } ... }
Type用来指定定义了初始化方法的类型,MethodName用来指定将要执行的初始化方法。
这样,可以不在配置文件中固定配置HttpModule,而是定义一个方法,这个方法可以返回需要动态注册的HttpModule,将这个方法以委托的形式登记在一个集合中。在网站启动之后,每当HttpApplicationFactory创建一个HttpApplication对象,完成正常注册的HttpModule创建及初始化之后,再来创建我们动态注册的这些HttpModule。
.
.
.
.
.
暂时忽略这些东东,先写下面的
4、常见的HttpModule
在Asp.net中,已经预定义了许多HttpModule,甚至已经在服务器的网站配置文件中进行了注册,在系统文件夹C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config中看到已经注册的HttpModule如下:
<httpModules> <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" /> <add name="Session" type="System.Web.SessionState.SessionStateModule" /> <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" /> <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" /> <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" /> <add name="RoleManager" type="System.Web.Security.RoleManagerModule" /> <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" /> <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" /> <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" /> <add name="Profile" type="System.Web.Profile.ProfileModule" /> <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /> <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </httpModules>
主要的HttpModule的解释如下:
1、OutputCacheModule完成Asp.net的输出缓存管理工作:
OutputCacheModule的配置参数通过system.web配置元素的caching子元素的outputCache元素进行定义。当启用输出缓存之后(启用还是通过配置文件,下同),OutputCacheModule将注册HttpApplication的ResolveRequestCache和UpdateRequestCache两个事件完成输出缓存的管理。
2、SessionStateModule完成Session的管理工作:
这个Module的配置参数通过配置文件中的system.web配置元素的sessionState子元素进行配置。当启用Session状态管理之后,SessionStateModule将注册HttpApplication的AcquireRequestState、ReleaseRequestState、EndRequest三个事件完成Session状态的管理工作。
3、ProfileModule提供个性化数据管理:
这是一个自定义的类似于Session的会话状态管理,但是,个性化数据的读取和保存可以由程序员完全控制,并且提供了强类型的数据访问方式。这个Module的配置参数在system.web的子元素profile中进行说明。当启用了个性化数据管理之后,Module将注册HttpApplication的AcquireRequestState和EndRequest事件处理。
4、AnonymousIdentificationModule提供匿名用户的标志:
是否启用匿名用户标志在配置文件的system.web配置元素的子元素anonymousIdentification中定义,还可以配置匿名标识的管理方式。由于在AuthenticateRequest事件中将验证用户,获取用户名,所以这个Module注册了PostAuthenticateRequest的事件处理,当用户没有经过验证的时候,为用户分配一个唯一的匿名标识。
5、WindowsAuthenticationModule、FormsAuthenticationModule和PassportAuthenticationModule用来完成用户的验证工作。
它们通过配置文件中system.web的子元素authentication子元素定义,mode属性用来指定网站当前使用的验证方式,也就是哪一个Module将被用来完成验证工作。在启用验证的情况下,FormsAuthenticationModule和PassportAuthenticationModule将注册HttpApplication的AuthenticateRequest和EndRequest事件进行用户的验证处理。WindowsAuthenticationModule将注册AuthenticateRequest的事件处理。
6、RoleManagerModule、UrlAuthorizationModule、FileAuthorizationModule用来完成用户的授权管理:
授权管理的配置参数来自system.web的authorization子元素。UrlAuthorizationModule和FileAuthorizationModule注册了HttpApplication的AuthorizeRequest事件处理,用来检查Url和文件的访问授权。RoleManagerModule在Url和文件访问授权检查通过之后,通过用户的标识和角色来完成用户的授权检查,RoleManagerModule注册了HttpApplication的PostAuthenticateRequest和EndRequest事件处理。
5、HttpModule事件
每个HttpModule也可以触发自定义的事件,但是,处理这些HttpModule事件更加麻烦一些,因为这些HttpModule对象实例也不是我们自己创建的。
一般情况下,通过HttpApplication的Modules属性获取特定的HttpModule,这个属性的定义如下:
public HttpModuleCollection Modules{ get; }
可以使用定义HttpModule时候的name作为索引来获取对应的HttpModule。例如,获取前面定义的HttpModule对象的引用:
application.Modules["online"]
然后就可以定义这个HttpModule的事件处理了。
6、通过global.asax创建HttpApplication的事件处理程序
在Visual Studio中创建的Asp.net项目中的Global.asax代码如下:
public class Global : System.Web.HttpApplication { void Application_Start(object sender, EventArgs e) { // 在应用程序启动时运行的代码 } void Application_End(object sender, EventArgs e) { // 在应用程序关闭时运行的代码 } void Application_Error(object sender, EventArgs e) { // 在出现未处理的错误时运行的代码 } void Session_Start(object sender, EventArgs e) { // 在新会话启动时运行的代码 } void Session_End(object sender, EventArgs e) { // 在会话结束时运行的代码。 // 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为 // InProc 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer // 或 SQLServer,则不会引发该事件。 } }