现在我们可以整理一下完整的一个HTTP请求在ASP.NET Framework下是如何被处理的:
HttpRequest—inetinfo.exe—ASPNET_ISAPI.dll—Http Pipeline—ASPNET_WP.exe—HttpRuntime—HttpApplication Factory—HttpApplication—HttpModule—HttpHandler Factory—HttpHandler—HttpHandler.ProcessRequest()
比如如果你想要中途截获一个Http Request并且做些自己的处理,该如何做呢?通过仔细的对上面图示的观察,读者应当知道能够在什么地方截获到这个HTTP请求。对,就是在HTTPRuntime运行时内部来做到这一点的,确切的说,是在HttpModule这个容器中做到这一点的。既然已经进入了神奇的HttpRuntime内部世界,我们当然要多看几眼了,接下来我们会继续我们的历险步伐,向HttpModule容器进军!
我们已经知道HttpModule容器是一个HTTP请求的必经之路,而我们在此小节的任务就是在HttpModule的内部深入看个究竟。
HttpModule在ASP.NET Framework中的位置
我们上回说到,一个来自于客户端的HTTP请求被截获后经过层层转交(怎么都在踢皮球?呵呵)到达了HttpModule这个“请求监听器”。HttpModule就类似于安插在ASPNET_WP.EXE
进程中的一个窃听器,稍微有些常识的人都会很自然的想象得到窃听器是用来做什么的,而我们的HttpModule可以说是作窃听器的绝好人选了,但是需要明确的是,HttpModule绝对不是简单的监听器,它可以做到更多的东西,比如它可以对截获的请求增加一些内容等等。那么HttpModule在整个ASP.NET Framework中的位置处在哪里呢?下面我们通过图示来看看:
另外需要明白的是,当一个HTTP请求到达HttpModule的时候,整个ASP.NET Framework系统还并没有对这个HTTP请求做任何的真正处理,也就是说此时对于HTTP请求来讲,HttpModule只是它路过的一个地方而以。但是正是因为HttpModule是一个HTTP请求的“必经之路”,所以我们可以在这个HTTP请求传递到真正的请求处理中心(HttpHandler)之前附加一些我们需要的信息在这个HTTP请求信息之上,或者针对我们截获的这个HTTP请求信息作一些额外的工作,或者在某些情况下干脆终止满足一些条件的HTTP请求,从而可以起到一个Filter过滤器的作用,而不仅仅是一个窃听器了。 通过查阅MSDN(不要去相信.NET SDK自带的那个QuickStarts Web文档,正式版本中竟然在很多地方没有更新这个文档,很多东西在正式版本是无效的),你会发现系统HttpModule实现了一个叫做IHttpModule的接口,很自然的就应当想到,只要我们自己的类能够实现IHttpModule接口,不就可以完全替代系统的HttpModule了吗?完全正确。
在我们开始自己的HttpModule类之前,我先来告诉你系统中的那个HttpModule是什么样子的,ASP.NET系统中默认的HttpModule有以下几个:
System.Web.Caching.OutputCacheModule
System.Web.SessionState.SessionStateModule
System.Web.Security.WindowsAuthenticationModule
System.Web.Security.FormsAuthenticationModule
System.Web.Security.PassportAuthenticationModule
System.Web.Security.UrlAuthorizationModule
System.Web.Security.FileAuthorizationModule
这些系统默认的HttpModule是在文件machine.config中配置,这个文件位于你安装的.NET框架所在的目录中,比如在你的系统文件目录中的C:/WINNT/Microsoft.NET/Framework/v1.0.3705/CONFIG/machine.config。在我们开发ASP.NET 应用程序的时候会频繁的使用到一个web.config配置文件,那么这个machine.config和我们常见的web.config有什么关系呢?原来在ASP.NET Framework启动处理一个Http Request的时候,她会依次加载machine.config以及你请求页面所在目录的web.config文件,里面的配置是有<remove>标签的,什么意思不说也知道了吧。如果你在machine.config中配置了一个自己的HttpModule,你仍然可以在离你最近web.config文件中“remove”掉这个映射关系。
构建我们自己的HttpModule
在上一小节中,我们谈到了系统默认的各个HttpModule均继承实现了一个叫做IhttpModule的接口。我们先来看看这个接口的真实面目吧:
语法:
public interface IHttpModule
需求:
名称空间: System.Web
平台: Windows 2000, Windows XP Professional, Windows .NET Server family
装配件: System.Web (in System.Web.dll)
公共成员方法:
void Dispose();
参数:无
返回值:void
作用:销毁不再被module所使用的资源。
void Init(HttpApplication context);
参数:HttpApplication类型的实例
返回值:void
作用:初始化一个module,为捕获HTTP请求做出一些准备。
了解了接口IhttpModule的方法,我们也就知道了如何去实现它了。
接下来,我们来开始我们自己的HttpModule构建历程吧。
1)打开VS.NET新建一个“Class Library”项目,将它命名为MyHttpModule。
2)引用System.Web.dll文件
在代码区域敲入:
using System;
using System.Web;
namespace MyHttpModuleTest
{
/// <summary>
/// 说明:用来实现自定义HttpModule的类
/// 作者:uestc95
/// 联系:uestc95@263.net
/// </summary>
public class MyHttpModule:IhttpModule
{
/// <summary>
/// 说明:构造器方法
/// 作者:uestc95
/// 联系:uestc95@263.net
/// </summary>
public MyHttpModule()
{
}
/// <summary>
/// 说明:实现IHttpModule接口的Init方法
///作者:uestc95
///联系:uestc95@263.net
/// </summary>
/// <param name="application">HttpApplication类型的参数</param>
public void Init(HttpApplication application)
{
application.BeginRequest +=new EventHandler(this.Application_BeginRequest);
application.EndRequest +=new EventHandler(this.Application_EndRequest);
}
/// <summary>
/// 说明:自己定义的用来做点事情的私有方法
/// 作者:uestc95
/// 联系:uestc95@263.net
/// </summary>
/// <param name="obj">传递进来的对象参数</param>
/// <param name="e">事件参数</param>
private void Application_BeginRequest(Object obj,EventArgs e)
{
//声明HttpApplication
HttpApplication application=(HttpApplication)obj;
HttpContext context=application.Context;
HttpResponse response=context.Response;
HttpRequest request=context.Request;
response.Write("我来自Application_BeginRequest,:)");
}
/// <summary>
/// 说明:自己定义的用来做点事情的私有方法
/// 作者:uestc95
/// 联系:uestc95@263.net
/// </summary>
/// <param name="obj">传递进来的对象参数</param>
/// <param name="e">事件参数</param>
private void Application_EndRequest(Object obj,EventArgs e)
{
HttpApplication application=(HttpApplication)obj;
HttpContext context=application.Context;
HttpResponse response=context.Response;
HttpRequest request=context.Request;
response.Write("我来自Application_EndRequest,:)");
}
/// <summary>
/// 说明:实现IHttpModule接口的Dispose方法
/// 作者:uestc95
///联系:uestc95@263.net
/// </summary>
public void Dispose(){}
}
}
3)在VS.NET中编译之后,你会得到MyHttpModule.dll这个文件。
4)接下来我们的工作就是如何让ASPNET_WP.exe进程将http request交给我们自己写的这个HttpModule呢?方法就是配置web.config文件。
在web.config文件中增加如下几句话:
<httpModules>
<add name="test" type="MyHttpModuleTest.MyHttpModule,MyHttpModule"/>
</httpModules>
注意要区分大小写,因为web.config作为一个XML文件是大小写敏感的。“type=MyHttpModuleTest.MyHttpModule,MyHttpModule”告诉我们,系统将会将HTTP请求交给位于MyHttpModule.dll文件中的MyHttpModuleTest.MyHttpModule类去处理。而这个DLL文件系统将会自动到/bin子目录或者系统全局程序集缓冲区(GAC)搜寻。我们可以将我们刚才得到的DLL文件放在bin子目录中,至于后者,你可以通过.NET SDK正式版自带的Config工具做到,我们不详细说了。
好了,我们的用来截获HTTP请求的自定义HttpModule就完成并且装配完成了,你可以试着在你的web项目中建立一个新的WebForm,运行看看呢?:)
最后,我们假设一个使用这个HttpModule的场合。A站点提供免费的ASP.NET虚拟空间给大家,但是A站点的管理者并不想提供免费的午餐,他想要在每一个页面被浏览的时候自动弹出自己公司的广告,我总不能时刻监视所有用户的所有页面吧,并且想要在每一个页面手动添加一段JS代码,工作量之大是不可想象的,也是非常不现实的。那么好了,只要我们的HttpModule一旦被挂接完成,这一切都将是轻而易举的事情了,只要我们在每一个HTTP请求被我们捕获的时候,给他在HTTP请求信息上面附加上一些我们自己的写的JavaScript代码就好了!
我们上面提到在Init()方法中使用了两个事件BeginRequest和EndRequest,这两个事件分别是Init()中可以处理的所有事件的最开始事件和最终事件,在他们中间还有一些其它的事件可以被我们利用,可以查阅MSDN,这一点我们会在下面详细的讲解。
在下一节开始之前,我们请各位读者考虑这样一个问题:在HttpModule中可以正常使用Response,Request,Server,Application对象吗?请读者自行试着写一些代码看看行不行。还有一个问题就是,在HttpModule中能操作Session对象吗?如果可以或者不可以,请仔细思考一下原因何在,在下一节我们会详细的探讨这个问题。
在这一小节中,我们会详细的探讨有关HttpModule的运行机制,从而使得各位读者能够透彻的了解HttpModule是如何控制HTTP请求的。
我们在上一节曾经提及当一个HTTP请求被ASP.NET Framework捕获之后会依次交给HttpModule以及HttpHandler来处理,但是需要明确的是,不能理解为HttpModule和HttpHandler是完全独立的。实际上是,在HTTP请求在HttpModule传递的过程中会在某个事件内将控制权交给HttpHandler的,而真正的处理在HttpHandler中执行完成之后,HttpHandler会再次将控制权交还给HttpModule。也就是说HttpModule在某个请求经过她的时候会在恰当时候同HttpHandler进行通信,在何时,如何通信呢?这就是下面提到的了。
我们在上一小节提到在HttpModule容器中最开始的事件是BeginRequest,最终的事件是EndRequest。你如果仔细看上次给出的源程序的话,应当发现在方法Init()中参数我们传递的是一个HttpApplication类型,而我们曾经提及的两个事件正是这个传递进来的HttpApplication的事件之一。
HttpApplication还有其它众多的事件,分别如下:
application.BeginRequest事件
application.EndRequest事件
application.PreRequestHandlerExecute事件
application.PostRequestHandlerExecute 事件
application.ReleaseRequestState事件
application.AcquireRequestState事件
application.AuthenticateRequest事件
application.AuthorizeRequest事件
application.ResolveRequestCache事件
application.UpdateRequestCache事件
application.PreSendRequestHeaders事件
application.PreSendRequestContent事件
接下来我们来看看这些事件的含义解释:
HttpApplication事件名称 什么时间触发
BeginRequest事件 处理HTTP请求开始之前触发
AuthenticateRequest事件 验证客户端时候触发
AuthenticateRequest事件 执行存取的时候触发
ResolveRequestCache事件 从缓存中得到相应时候触发
AcquireRequestState事件 加载初始化Session时候触发
PreRequestHandlerExecute事件 在HTTP请求送入HttpHandler之前触发
PostRequestHandlerExecute事件 在HTTP请求送入HttpHandler之后触发
ReleaseRequestState事件 存储Session状态时候触发
UpdateRequestCache事件 更新缓存信息的时候触发
EndRequest事件 在HTTP请求处理完成时候触发
PreSendRequestHeaders事件 在向客户端发送Header之前触发
PreSendRequestContent事件 在向客户端发送内容之前触发
需要注意的是,在事件EndRequest之后还会继续执行application.PreSendRequestHeaders事件以及application.PreSendRequestContent事件,而这两个事件大家想必应当从名称上面看得出来事做什么用途的了吧。是的,一旦触发了这两个事件,就表明整个对一个HTTP请求的处理已经执行完成了,在这两个事件中是开始向客户端传送处理完成的数据流了。细心的读者看到这里,会有一个疑问:怎么没见到进入HttpHandler容器就处理完成了?不是提到过HttpHandler容器才是真正处理HTTP请求的吗?
其实一开始我们就提到了,在一个HTTP请求在HttpModule容器的传递过程中,会在某一个时刻(确切的说应当是事件)中将这个HTTP请求传递给HttpHandler容器的。这个事件就是ResolveRequestCache,在这个事件之后,HttpModule容器会建立一个HttpHandler的入口实例(做好准备了,:)),但是此时并没有将HTTP请求的控制权交出,而是继续触发AcquireRequestState事件以及PreRequestHandlerExecute事件(如果你实现了的话)。看到了吗,最后一个事件的前缀是Pre,这表明下一步就要进入HttpHandler容器了,事实上的确如此,正如我们猜想的那样,在PreRequestHandlerExecute事件之后,HttpModule容器就会将控制权暂时交给HttpHandler容器,以便进行真正的HTTP请求处理工作。
而在HttpHandler容器内部会执行ProcessRequest方法来处理HTTP请求。在容器HttpHandler处理完毕整个HTTP请求之后,会将控制权交还给HttpModule,HttpModule则会继续对处理完毕的HTTP请求信息流(此时的HTTP请求信息流已经是被处理过后了的信息流了)进行层层的转交动作,直到返回到客户端为止。
怎么样,是不是有些混乱?下面是一个完整的HttpModule的生命周期示意步骤:
Http Request开始
↓
HttpModule
↓
HttpModule.BeginRequest()
↓
HttpModule.AuthenticateRequest()
↓
HttpModule.AuthorizeRequest()
↓
HttpModule.ResolveRequestCache()
↓
建立HttpHandler控制点
↓
接着处理(HttpHandler已经建立,此后Session可用)
↓
HttpModule.AcquireRequestState()
↓
HttpModule.PreRequestHandlerExecute()
↓
进入HttpHandler处理HttpRequest
↓
HttpHandler.ProcessRequest()
↓
返回到HttpModule接着处理(HttpHandler生命周期结束,Session失效)
↓
HttpModule.PostRequestHandlerExecute()
↓
HttpModule.ReleaseRequestState()
↓
HttpModule.UpdateRequestCache()
↓
HttpModule.EndRequest()
↓
HttpModule.PreSendRequestHeaders()
↓
HttpModule.PreSendRequestContent()
↓
将处理后的数据返回客户端
↓
整个Http Request处理结束
为了更加形象的描述整个HttpModule的HTTP请求处理周期,接下来我们来看看
HTTP请求在整个HttpModule中的生命周期图:
想必读者应当可以从上面的HttpModule生命周期图中找到了上一小节我们提出的那个问题:Session对象在何种情况下可以正常的使用!为了验证上面的流程,我们可以用下面的这个自己的HttpModuel来验证一下就知道了。
注意我们下面给出的是类的内容,代码框架还是上一小节我们给出的那个,自己加上就好了:
public void Init(HttpApplication application)
{
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.EndRequest += (new EventHandler(this.Application_EndRequest));
application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));
application.PostRequestHandlerExecute +=(new EventHandler(this.Application_PostRequestHandlerExecute));
application.ReleaseRequestState +=(new EventHandler(this.Application_ReleaseRequestState));
application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));
application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));
application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));
application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));
application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));
application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent));
}
private void Application_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_PreRequestHandlerExecute<br>");
}
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_BeginRequest<br>");
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_EndRequest<br>");
}
private void Application_PostRequestHandlerExecute(Object source,EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_PostRequestHandlerExecute<br>");
}
private void Application_ReleaseRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_ReleaseRequestState<br>");
}
private void Application_UpdateRequestCache(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_UpdateRequestCache<br>");
}
private void Application_AuthenticateRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_AuthenticateRequest<br>");
}
private void Application_AuthorizeRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_AuthorizeRequest<br>");
}
private void Application_ResolveRequestCache(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_ResolveRequestCache<br>");
}
private void Application_AcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_AcquireRequestState<br>");
}
private void Application_PreSendRequestHeaders(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_PreSendRequestHeaders<br>");
}
private void Application_PreSendRequestContent(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("Application_PreSendRequestContent<br>");
}
public void Dispose()
{
}
将上面的代码编译成为一个类库文件,再按照上一小节给出的方法将它挂接到ASP.NET Framework系统,你运行此演示代码就可以看到和我们给出的HttpModule生命周期图是完全吻合的。
现在我们来思考这样一个问题:如果我同时编写了多个自定义的HttpModule,并且也都装配了他们,ASP.NET Framework会怎样加载这多个自定义的HttpModule呢?下面我们就一同通过一个完整的例子来演示看看:我们在下面的代码中会建立两个HttpModule类库DLL文件,并且同时装配他们在web.config中。
1)、在VS.NET中建立一个名为IhttpModule的类库项目
2)、引入名称空间文件System.Web.dll
3)、在代码区域输入如下的代码:
using System;
using System.Web;
using System.Collections;
public class HelloWorldModule : IHttpModule
{
public String ModuleName
{
get { return "HelloWorldModule"; }
}
public void Init(HttpApplication application)
{
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.EndRequest += (new EventHandler(this.Application_EndRequest));
application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));
application.PostRequestHandlerExecute +=(new EventHandler(this.Application_PostRequestHandlerExecute));
application.ReleaseRequestState +=(new EventHandler(this.Application_ReleaseRequestState));
application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));
application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));
application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));
application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));
application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));
application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent));
}
//Your BeginRequest event handler.
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule: Beginning of Request<br>");
}
//Your EndRequest event handler.
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule: End of Request<br>");
}
private void Application_PostRequestHandlerExecute(Object source,EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_PostRequestHandlerExecute:<br>");
}
private void Application_ReleaseRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_ReleaseRequestState :<br>");
}
/// <summary>
///
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void Application_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
HttpResponse Response=context.Response;
HttpRequest Request=context.Request;
context.Response.Write("HelloWorldModule:Application_PreRequestHandlerExecute :<br>");
}
private void Application_UpdateRequestCache(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_UpdateRequestCach :<br>");
}
private void Application_AuthenticateRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_AuthenticateRequest<br>");
}
private void Application_AuthorizeRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_AuthorizeRequest<br>");
}
private void Application_ResolveRequestCache(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_ResolveRequestCache<br>");
}
private void Application_AcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_AcquireRequestState<br>");
}
private void Application_PreSendRequestHeaders(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_PreSendRequestHeaders<br>");
}
private void Application_PreSendRequestContent(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule:Application_PreSendRequestContent<br>");
}
public void Dispose()
{
}
}
3)、编译这个项目之后会得到一个叫做IhttpModule.dll的文件
接下来我们再来共同建立另外一个很类似的自定义HttpModule类库项目
1)、在VS.NET中新建一个名为IhttpModule2的项目
2)、引入System.Web.dll文件
3)、在代码区域输入:
using System;
using System.Web;
using System.Collections;
public class HelloWorldModule2 : IHttpModule
{
public String ModuleName
{
get { return "HelloWorldModule"; }
}
public void Init(HttpApplication application)
{
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.EndRequest += (new EventHandler(this.Application_EndRequest));
application.PreRequestHandlerExecute +=(new EventHandler(this.Application_PreRequestHandlerExecute));
application.PostRequestHandlerExecute +=(new EventHandler(this.Application_PostRequestHandlerExecute));
application.ReleaseRequestState +=(new EventHandler(this.Application_ReleaseRequestState));
application.AcquireRequestState +=(new EventHandler(this.Application_AcquireRequestState));
application.AuthenticateRequest +=(new EventHandler(this.Application_AuthenticateRequest));
application.AuthorizeRequest +=(new EventHandler(this.Application_AuthorizeRequest));
application.ResolveRequestCache +=(new EventHandler(this.Application_ResolveRequestCache));
application.PreSendRequestHeaders +=(new EventHandler(this.Application_PreSendRequestHeaders));
application.PreSendRequestContent +=(new EventHandler(this.Application_PreSendRequestContent));
}
//Your BeginRequest event handler.
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2: Beginning of Reques<br>");
}
//Your EndRequest event handler.
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2: End of Request<br>");
}
private void Application_PostRequestHandlerExecute(Object source,EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_PostRequestHandlerExecute:<br>");
}
private void Application_ReleaseRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_ReleaseRequestState :<br>");
}
/// <summary>
///
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void Application_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
HttpResponse Response=context.Response;
HttpRequest Request=context.Request;
context.Response.Write("HelloWorldModule2:Application_PreRequestHandlerExecute :<br>");
}
private void Application_UpdateRequestCache(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_UpdateRequestCach :<br>");
}
private void Application_AuthenticateRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_AuthenticateRequest<br>");
}
private void Application_AuthorizeRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_AuthorizeRequest<br>");
}
private void Application_ResolveRequestCache(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_ResolveRequestCache<br>");
}
private void Application_AcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_AcquireRequestState<br>");
}
private void Application_PreSendRequestHeaders(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_PreSendRequestHeaders<br>");
}
private void Application_PreSendRequestContent(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("HelloWorldModule2:Application_PreSendRequestContent<br>");
}
public void Dispose()
{
}
}
编译这个自定义的HttpModule类库项目之后,会得到一个名为IhttpModule2.dll的文件。
下面我们需要来建立一个Web应用程序项目来测试这两个自定义的HttpModule文件的效果如何。
1)、在VS.NET中建立一个Web应用程序项目名字叫做: 4.2.3
2)、在文件web.config中增加如下语句:
<httpModules>
<add name="test" type="HelloWorldModule,IHttpModule"/>
<add name="test2" type="HelloWorldModule2,IHttpModule2"/>
</httpModules>
以便通知ASP.NET Framework。
3)、将上面我们生成的两个自定义HttpModule文件拷贝到此Web应用程序的bin/子目录中。
4)、直接运行这个Web
应用程序,我们会得到如下的结果:
从上面的运行结果我们可以看到,我们在web.config文件中引入自定义HttpModule的顺序就决定了多个自定义
HttpModule在处理一个HTTP请求的接管顺序。而系统默认那几个HttpModule是最先被ASP.NET Framework
所加载上去的。
最后,我们来这样一个问题:在前面小节中我们曾经提到过我们可以利用HttpModule来实现当满足某一条件的时候终止此次的HTTP请求,那么如何做到在我们的HttpModule中终止一个HTTP请求呢?方法就是调用HttpApplication.CompleteRequest()方法。并且我们知道在一个HttpModule中首先触发的事件将是BeginRequest,这里也就理所当然的成为了我们终止一个HTTP请求的地方了。
我们可以更改一下BeginRequest事件处理代码如下:
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
application.CompleteRequest();
context.Response.StatusCode=500;
context.Response.StatusDescription=”Internal Server Error!”;
//context.Response.Write("HelloWorldModule1: Beginning of Request<br>");
}
这样,就会终止一个HTTP请求了。但是需要注意的是,即使调用了HttpApplication.CompleteRequest()方法终止了一个
HTTP请求,ASP.NET Framework仍然会触发HttpApplication后面的这三个事件:EndRequest事件、PreSendRequestHeaders
事件、PreSendRequestContent事件。
如果多个自定义的HttpModule存在的话,比如上面我们给出的例子:IhttpModule.dll以及IhttpModule2.dll。如果我们在
IhttpModule.dll中终止了一个HTTP请求的话,这个HTTP请求也不会再触发IhttpModule2.dll中的诸如BeginRequest
等等事件了,但是仍然会触发IhttpModule.dll以及IhttpModule2.dll的EndRequest事件、PreSendRequestHeaders事件、
PreSendRequestContent事件。
我们可以看看下面的示意图:
现在我们已经在HttpModule的神秘世界历险了一番,相信各个读者对于深奥的HttpModule不再陌生了,接下来我们将继续在ASP.NET Framework的世界里历险,下一站的目的地是神秘的IhttpHandler,也就是HTTP请求处理中心了!
4.3、ASP.NET Framework深度历险 --初次接触神秘的IHttpHandler
我们在上一小节在HttpModule的世界历险的时候就提到过,HttpHandler是一个HTTP请求的真正处理中心,也正是在这个HttpHandler容器当中,ASP.NET Framework才真正的对客户端请求的服务器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。在本小节,我们将会一同来初步了解一下神秘的HttpHandler。
4.3.1 IHttpHandler是什么?
我们一直在使用HttpHandler,而不是IHttpHandler,请不要混淆这两者。前者只是我们用来描述使用的一个名称而已,而后者则是ASP.NET Framework中一个重要的接口的名称,而我们历险的重点自然是在IHttpHandler。
IHttpHandler是ASP.NET Framework提供的一个接口,它定义了如果要实现一个HTTP请求的处理所需要必需实现的一些系统约定。也就是说,如果你想要自行处理某种类型的HTTP请求信息流的话,你需要实现这些系统约定才能够做到,这也正是接口在.NET中的作用。
我们先来看看IHttpHandler的真实面目吧:
语法:
public interface IHttpHandler
需求:
名称空间: System.Web
平台: Windows 2000, Windows XP Professional, Windows .NET Server family
装配件: System.Web (in System.Web.dll)
成员字段:
IsReusable
成员方法:
void ProcessRequest(HttpContext context);
HttpHandler同HttpModule类似,系统都默认提供了很多的系统HttpHandler类,用来处理不同的HTTP请求。
同样的,这些默认的系统HttpHandler类也和系统的HttpModule一样在machine.config文件中进行配置的,下面我们就来仔细看看了:
在文件machine.config中可以看到下面的配置段:
<httpHandlers>
<add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/>
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/>
<add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory,
System.Web.Services, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b 03f 5f 7f 11d 50a 3a " validate="false"/>
<add verb="*" path="*.rem" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFacto
ry, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b 77a 5c 561934e089" validate="false"/>
<add verb="*" path="*.soap" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b 77a 5c 561934e089" validate="false"/>
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.ascx" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.csproj" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.vb" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.vbproj" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.webinfo" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.asp" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.licx" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.resx" type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.resources" type="System.Web.HttpForbiddenHandler"/>
<add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/>
<add verb="*" path="*" type="System.Web.HttpMethodNotAllowedHandler"/>
</httpHandlers>
可以看到,ASP.NET Framework为ASP.NET应用程序的正常运行提供了很多的系统默认HttpHandler类,用来适应不同类型的HTTP请求。比如,我们最熟悉的*.aspx文件,用来处理此类型的HTTP请求,ASP.NET Framework将会交给一个名为System.Web.UI.PageHandlerFactory的HttpHandler类来处理。
我们在这里来讲解一下<httpHandlers>配置代码的用法:
主配置标签语法:
<httpHandlers>
<add verb="verb list" path="path/wildcard" type="type,assemblyname" validate="" />
<remove verb="verb list" path="path/wildcard" />
<clear />
</httpHandlers>
子配置标签语法:
<add>:详细指定需要的动词(GET/HEAD等等)以及相应的请求文件路径。其中的路径文件支持通配符*。
<remove>:移除一个到HttpHandler上的映射。并且不支持通配符。
<clear>:移除所有的到HttpHandler上的映射。
而HttpHandler和HttpModule一样,系统会在最初始由ASP.NET Framework首先加载machine.config中的系统HttpHandler
,而后会加载Web应用程序所在目录的web.config中的用户自定义的HttpHandler类。
但是同HttpModule不同的是:一旦定义了自己的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系的,也就是说如果我们自己定义了一个对*.aspx请求的自定义HttpHandler类的话,那么系统将会将对此HTTP请求的处理权限完全交给我们自己定义的这个HttpHandler类来处理,而我们的HttpHandler类则需要完全的解析这个HTTP请求,并做出处理,如果不这样的话,那么,这个HTTP请求将会被空HTTP信息流所代替,或者被我们自定义的HttpHandler类中的信息数据流所代替。这一点我们会在后面的章节中详细探讨。我们在这里仅仅给出一个系统默认HttpHandler和我们的自定义HttpHandler之间的关系图:
在下一小节中,我们将来探讨一下IHttpHandler是如何处理一个HTTP请求的。
4.3.2 IHttpHandler如何处理HTTP请求
在上一小节中我们初步介绍了IHttpHandler的知识,接下来我们会探讨一下当一个HTTP请求被传递到一个HttpHandler
容器之后,HttpHandler容器是如何对这个HTTP请求进行解析和处理的。
我们在上面小节中可以知道,一个HttpHandler容器是实现了一个名为IHttpHandler的接口,而我们也了解了IHttpHandler
接口需要实现的描述。我们能注意到,在IHttpHandler中最为重要的一个成员方法就是:
ProcessRequest。从字面上面我们可以很容易的得知,这个方法就是HttpHandler用来处理一个HTTP请求的,实事的确如此。当一个HTTP请求经由HttpModule容器传递到HttpHandler容器中的时候,ASP.NET Framework会调用HttpHandler的ProcessRequest成员方法来对这个HTTP请求做真正的处理,我们以一个ASPX页面来讲,正是在这里一个ASPX页面才被系统处理解析,并将处理完成的结果继续(注意:这里是继续,也就是说仍在一个HttpModule的生命周期内)经由HttpModule传递下去,直至到达客户端。
我们从 4.3.1 小节中的配置文件中可以了解到对于ASPX页面,ASP.NET Framework在默认情况下是交给System.Web.UI.PageHandlerFactory这个HttpHandlerFactory来处理的,需要注意的这里并不是我们一直在讨论的HttpHandler容器,
而是一个HttpHandler工厂,这一点我们会在下面详细讨论,这里我们仅仅简单的介绍一下什么是HttpHandler工厂。所谓一个HttpHandlerFactory,是指当一个HTTP请求到达这个HttpHandler工厂的时候,HttpHandlerFactory会提供出一个HttpHandler容器,交由这个HttpHandler容器来处理这个HTTP请求,这种处理方式我们在第三章中的Factory设计模式提到过,这样做的好处是大大减轻了系统的压力,提高了系统的适应性和灵活度。
但不论怎样,一个HTTP请求都是最终交给一个HttpHandler容器中的ProcessRequest方法来处理的。因此我们在下面的小节中将会深入的探讨一下IHttpHandler。
4.4、ASP.NET Framework深度历险– IHttpHandler深入
在4.3小节,我们了解到了IHttpHandler是如何来处理一个HTTP请求的,在本小节我们会更加深入讨论有关
IHttpHandler的一些技术细节。
在本小节内,我们先来共同实现一个简单的HttpHandler容器,以便能对HttpHandler容器有一个感性的认识。
通过实现 IHttpHandler 接口我们可创建自定义 HTTP 处理程序,而该接口只包含两个方法。通过调用 IsReusable,IHttpHandlerFactory可查询处理程序以确定是否可使用同一实例为多个请求提供服务。ProcessRequest 方法将 HttpContext 实例用作参数,这使它能够访问 Request 和 Response 内部对象。
我们来看看下面的这个例子:
1)在VS.NET中建立一个新的“类库”项目,命名为:MyHandler
2)在代码区域键入如下代码:
using System;
using System.Web;
namespace MyNamespace
{
///<summary>
///目的:实现简单的自定义HttpHandler容器
///</summary>
public class MyHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
HttpResponse Response=context.Response;
HttpRequest Request=context.Request;
Response.Write("<h1><b>Hello world!</b></h1>");
}
public bool IsReusable
{
get{return true;}
}
}
}
3)编译这个项目,我们得到了MyHandler.dll这个文件。
4)将这个MyHandler.dll文件拷贝到我们的测试Web应用程序目录中的/bin子目录。
5)配置我们的测试Web应用程序配置文件Web.Config如下:
在配置文件Web.Config中增加如下信息:
<httpHandlers>
<add verb="*" path="*" type="MyNamespace.MyHandler,MyHandler"/>
</httpHandlers>
其中的type属性以逗号分隔开的分别是:自定义HttpHandler中的类名称以及最终的程序集文件(也就是我们编译得到的那个DLL文件)。
而其中的path属性则表示我们的自定义HttpHandler将会接管何种类型的文件请求。
这样,经过上面的配置之后,对于所有类型的页面文件HTTP请求都将会被我们的自定义HttpHandler捕获。而我们的自定义HttpHandler则忽略了所有的HTTP请求,替代输出的是一段经典的“Hello world!”语句。
读者可以试着运行这段例子,我们给出的这个简单例子运行结果如下:
Hello word!
这样,一个我们自定义的HttpHandler容器就完全运行的和我们料想的那样一样了,也最终证实了一个HTTP请求的真正处理中心是在一个HttpHandler容器中的。
从上面的例子代码中我们可以看到,ProcessRequest方法传递进来的是一个HttpContext对象,而我们通过这个HttpContext
对象可以方便的使用我们平常在ASP.NET页面中经常直接使用的Response对象以及Request对象,那么我们能够使用Session对象吗?如果你只是简单的类似使用Response对象那样直接在我们的自定义HttpHandler容器中使用一个Session对象的话,编译器将会编译出错。
在一个HttpHandler容器中如果需要访问会话状态对象Session,你必须实现一个IRequiresSessionState接口。 IRequiresSessionState接口指定目标 HTTP 处理程序接口具有对会话状态值的读写访问权限。这只是一个标记接口,没有任何方法。
IRequiresSessionState 接口的语法如下:
命名空间: System.Web.SessionState
平台: Windows 2000, Windows XP Professional, Windows .NET Server family
程序集: System.Web (在 System.Web.dll 中)
也就是说我们只有在我们的程序中实现了这个接口,才有权利访问Session对象。这个接口和其他的普通接口不太一样的是,他没有任何方法,仅仅起到一个标记的作用而已。
我们修改我们的程序如下,以便能够在我们的自定义HttpHandler容器中存取Session对象:
using System;
using System.Web;
using System.Web.SessionState;
namespace MyNamespace
{
///<summary>
///目的:实现简单的自定义HttpHandler容器
///</summary>
public class MyHandler : IHttpHandler ,IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
//声明我们自己的Response对象
HttpResponse Response=context.Response;
//声明我们自己的Request对象
HttpRequest Request=context.Request;
//声明我们自己的Session对象
HttpSessionState Session=context.Session;
//打印一段话
Response.Write("<h1><b>Hello world!</b></h1><br>");
//给Session对象赋值
Session["test"]="this is a session test";
//打印测试Session对象的值
Response.Write("<h1><b>Session[/"test/"]="+Session["test"].ToString()+"</b></h1>");
}
public bool IsReusable
{
get{return true;}
}
}
}
这样,在我们的自定义HttpHandler容器中就可以正常存取Session对象了。
重新编译这个项目,将生成的MyHandler.dll文件覆盖到我们刚才的测试Web应用程序的/bin子目录中,再次运行测试Web应用程序我们会得到如下结果:
在上面我们曾经提到过,ASP.NET Framework实际上并不是直接将相关的页面资源HTTP请求定位到一个其内部默认的IHttpHandler容器之上的,而是定位到了其内部默认的IHttpHandler工厂上了。
这个IHttpHandler工厂的作用就是对很多的系统已经实现了的IHttpHandler容器进行调度和管理的。这样做的优点是大大增强了系统的负荷处理能力,能够很好的提升效率。
接下来,我们就看看IHttpHandler工厂和IHttpHandler容器之间的关系吧。
首先我们来了解一下IHttpHandler工厂的语法定义:
命名空间: System.Web
平台: Windows 2000, Windows XP Professional, Windows .NET Server family
程序集: System.Web (在 System.Web.dll 中)
公共方法:
1)IHttpHandler GetHandler(HttpContext context,string requestType,string url,string pathTranslated);
作用:返回实现 IHttpHandler 接口的类的实例。
参数:
context :HttpContext 类的实例,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如
Request、Response、Session 和 Server)的引用。
requestType :客户端使用的 HTTP 数据传输方法(GET 或 POST)。
url :所请求资源的 RawUrl。
pathTranslated :所请求资源的 PhysicalApplicationPath。
返回值:处理请求的新的 IHttpHandler 对象。
2)ReleaseHandler
作用:使工厂可以重用现有的处理程序实例。
4.5、ASP.NET Framework深度历险 – IHttpModule 以及 IHttpHandler应用实例
4.6、ASP.NET Framework深度历险 – 深入ASP.NET事件模型机制
ASP.NET相对于以前古老的ASP来讲,一个很重要的革命性技术就是ASP.NET是完全基于事件驱动的一种技术,而这点是ASP是不可比拟的,深入的探索ASP.NET的事件模型机制是本章的内容。
4.6.1 A SP.NET事件模型初步认识
ASP.NET之所以对于以前的ASP是一个革命性的巨变,在很大程度上面是由于ASP.NET技术是一种完全基于事件驱动的全新技术,如果你以前曾经接触过Delphi或者VB,你一定就会了解事件驱动技术是怎样的一种技术了。但是需要特别提醒读者注意的事,ASP.NET的事件模型机制以不同于一般的Client/Server时代的事件模型机制。在Client/Server时代,所有的事件捕捉和触发以及处理都是交给客户端的应用程序来实现的,而服务端则用来执行复杂的耗费时间的计算,从而也决定了Client/Server
时代的事件模型机制的相对简单和容易实现。
ASP.NET是完全基于HTTP协议的,而HTTP协议是一种无连接的Web协议,也就是说每当一次客户端的Http请求处理结束之后,服务器端就会自动和客户端断开连接。从而我们应当可以知道,ASP.NET的事件驱动是和Client/Server时代的事件驱动有所不同的一种基于HTTP协议的技术,在ASP.NET中事件的触发和事件的处理是分别在客户端和服务端进行的。一个事件在客户端被触发之后,会通过HTTP协议以POST的方式发送到服务器端,而服务器端则通过ASP.NET 页面架构(ASP.NET page framework)来进行相应的处理和反馈。
说到这里,我们来回忆一下曾经写过的一些ASP.NET程序,如果你仔细留意的话,应当会发现,每一个页面在我们使用
VS.NET生成的时候,会自动的将所有的Web Control以及相应的标签放置到一个默认的<Form>当中,并且默认的传递方式是POST,也可以是GET方式。你可以试着移去或者更改这个<Form>标签,但是当你重新编译并运行这个页面的时候,你应当发现系统仍然自动的将这个<Form>标签更改为自己需要的了,这也就验证了上面我们刚刚提到的ASP.NET需要客户端以POST/GET的方式通过HTTP协议将事件信息发送到服务器端,从而服务器端才能处理这些事件。
ASP.NET页面架构在服务器端接收到这些来自客户端的事件信息之后,会自动的判别并决定调用相应的方法来进行事件处理。我们通过下图可以清晰的了解到这一点:
从上图我们可以看到,ASP.NET Framework负责了从客户端事件捕获、传递、事件信息解释的全部过程,从另外一个方面来说,开发ASP.NET应用程序的时候,你不必亲自去管理这些事件模型的技术细节,而是可以将更大的时间和精力投入到商业逻辑的分析设计当中去,节约了大量的时间。当然由于事件的整个处理过程属于ASP.NET的核心技术,我们也可能去强行的干预到。由于ASP.NET为我们做了所有的事件管理和处理工作,我们就可以像往常的普通应用程序那样来自如的编写各种事件的响应代码了。
4.6.2 A SP.NET的事件模型深入了解
在上一节我们初步的了解了一下ASP.NET事件模型机制的知识,在这一节我们来详细的深入进去看看ASP.NET事件驱动模型的内部到底是怎样的一回事。
从本质上面来讲,ASP.NET的事件模型就是Web Forms的事件模型,我们先来看下面的一个例子代码片断:
(如果没有特殊指明我们的例子代码均采用代码绑定机制)
页面代码
WebForm1.aspx:
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="WebApplication1.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W 3C //DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<asp:Button id="Button1" style="Z-INDEX: 101; LEFT: 243px; POSITION: absolute; TOP: 114px" runat="server" Text="事
件模型测试"></asp:Button>
</form>
</body>
</HTML>
CodeBehind代码
WebForm1.aspx.cs:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace WebApplication1
{
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Button Button1;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Button1.Click += new System.EventHandler(this.Button1_Click);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
private void Button1_Click(object sender, System.EventArgs e)
{
Response.Write("Hello,world!");
}
}
}
上面的一段最简单的代码就是在页面打印一句古老的“Hello,world!”。
上面的最简单的代码运行结果是在页面上面打印一句最简单的“Hello,world!”。仔细的查阅上面的CodeBehind
代码,我们可以简单的了解到如下几点:
1、ASP.NET Framework是通过如下方式来调用事件处理方法的:
this.Button1.Click += new System.EventHandler(this.Button1_Click);
2、事件Button1_Click的参数共有两个:
Button1_Click(object sender, System.EventArgs e)
虽然我们给出的例子相当的简单,但是事件驱动的模型机制却是完全一致的,下面就然我们来由浅入深的逐步的深入到
ASP.NET的事件模型机制的内部看一看。
ASP.NET的所有的事件处理方法都只有相同的两个参数:object类型参数以及System.EventArgs类型的参数。注意,我们提到的所有的事件处理方法都只有这两个相同的参数,唯一会有区别的是,其他的某些事件处理方法的参数2会是System.EventArgs的一个子类,以便适应特殊的处理要求。
在此我们再一次强调一下,ASP.NET技术是完全构建在.NET框架技术之上的,它的所有的技术细节均来源于.NET框架。正如刚才我们看到的事件处理方法的两个参数,他们都是一个类(Class),因而你应当不难理解刚才所讲的“其他的某些事件处理方法的参数2会是System.EventArgs的一个子类”,也就是在某些情况下,参数会是继承于System.EventArgs类的一个子类。
或许有些读者看到这里会有些疑问,为什么所有的事件处理方法都必须要有相同的参数呢?原因还在我们一再强调的那句话“ASP.NET技术是完全构建在.NET框架技术之上的”。
在我们给出的代码中,是通过代表(Delegate)的机制来将事件和事件处理方法绑定在一起的,而代表(Delegate)则是.NET
框架的一个机制。在ASP.NET中需要通过.NET提供的EventHandler来做到这个绑定的:
[Serializable]
public delegate void EventHandler(object sender,EventArgs e);
由此我们可以清楚的了解到为什么所有的ASP.NET事件处理方法都需要有相同的两个参数了,因为它们都是通过EventHandler来实现事件和处理方法的绑定的。
在我们继续前进之前,我们先来透彻的了解一下事件和代表之间的关系。
一个事件(Event)是一个对象发送的一个消息,用来表示一个动作发生了。而一个动作可以被用户操作或者其他程序所触发。触发事件的对象被事件发送者(Event Sender)调用;捕获处理事件的对象被事件接收者(Event Receiver)调用。
在事件通讯当中,事件的发送者并不知道哪个对象或者方法将要去接收/处理发送过去的事件。因而在事件源和事件接收者之间就需要一个中间人存在,这一点类似于“指针”。而.NET架构专门定义了一个特殊的类型用来提供一个指向函数的指针,就是“代表”,也叫做“委托”。
一个代表就是一个类(class),他提供了一个面向某个方法的引用参考。和其他一般普通的类不一样,一个代表类拥有一个签名(Signature),从而使得代表能够提供面向符合自己签名的方法引用参考。所谓“签名”指的是:一个函数方法的参数个数、参数类型的集合。因此,一个代表也可以被认为是一个类型安全的函数指针或者回调函数。当然,代表并不仅仅是为事件本身而设计的,他还有其他更为强大的功用,而在这里我们仅仅探讨事件捕获类型的代表。
下面的例子简单的告诉我们一个事件代表是如何定义的:
// AlarmEventHandler
是一个警报事件的代表
// AlarmEventArgs
是用来捕获来自于警报事件的数据的代表类,他继承于父类
//EventArgs.
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
从上面的代码我们可以看到,一个代表的定义语法和我们一般的函数定义很类似,但是关键字delegate会通知编译器这是一个代表类型。
在.NET框架中,约定事件代表(Event Delegate)拥有两个参数,一个表示事件源,另外一个表示事件的数据。
一个代表的定义提供代表的签名,CLR(common language runtime)会使用这个签名。
一个代表的实例可以被绑定到任何一个符合代表签名的方法上,比如:
public class WakeMeUp
{
// AlarmRang has the same signature as AlarmEventHandler.
public void AlarmRang(object sender, AlarmEventArgs e){...};
...
}
下面是将事件绑定到相应事件处理方法的代码:
WakeMeUp w = new WakeMeUp();
AlarmEventHandler alhandler = new AlarmEventHandler(w.AlarmRang);
这样,在alhandler被触发的时候,它将会依次调用方法w.AlarmRang。
至于更为详细代表机制和技术细节则不属于本书的探讨范围之内了,读者可以自行查阅相关的技术资料来了解。
现在我们应当可以理解这句代码的含义了:
this.Button1.Click += new System.EventHandler(this.Button1_Click);
这句代码表示将事件this.Button1.Click绑定在方法this.Button1_Click上面了,也就是说一旦触发按钮的Click事件,服务器端将会自动的调用方法Button1_Click来处理。
ASP.NET Framework提供给我们的服务器端事件并不是很多,比如我们在ASP时代时常用到的OnMouseOver等等事件均没有提供,这是什么原因呢?其实从上面我们的讲解当中就应当了解到,在ASP.NET Framework下,事件驱动模型机制的实现是在客户端和服务端分别实现的,之间需要通过HTTP协议方式来传递事件信息,因而如果频繁的触发各类事件会对整个Web站点产生很大的流量压力,比如刚刚提到的OnMouseOver事件,每次微小的鼠标移动都将会触发它,这对于服务器端的压力是非常大的,因而系统并没有提供。这些特殊的需要的事件我们需要在客户端自动的实现处理,从而大大减轻服务器端的压力,也带来的更多的灵活性。
接下来我们来谈谈事件的触发顺序,也许读者会有这样的疑问:事件的触发顺序还有必要来讨论吗?我们平常的事件触发不都是自然的有自己的顺序的吗?比如吃饭,我当然要先触发“张开嘴”这个事件,再触发“送入食物”这个事件了。
我们平常生活中的事件触发的确如此,但是在ASP.NET的世界当中却不是这样的,正如我们上面提到的,如果频繁的和服务器端进行事件信息的传递会大大降低服务器的处理效率和性能,因而有些事件比如OnMouseOver在ASP.NET中并没有提供,但是有些事件虽然也会频繁的触发但是必须提供,比如很多情况下要用到的Change事件。对于这种情况,ASP.NET Framework提供了一个折衷的办法,就是对于这类事件在触发的时候,并不是立即将事件信息发送到服务器,而是缓存在客户端,等到再一次的事件信息被发送到服务器端的时候一同发送回去。因此,这些缓存着的事件以及刚刚被触发的事件在服务器端被接收到的时候,ASP.NET Framework并不会按照特定的顺序去解释执行处理这些事件。
下面我们来通过一个实际的例子来验证这一点:
页面文件:
WebForm1.aspx
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="WebApplication1.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W 3C //DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<asp:Button id="Button1" style="Z-INDEX: 101; LEFT: 233px; POSITION: absolute; TOP: 302px" runat="server" Text="
事件模型测试"></asp:Button>
<asp:DropDownList id="DropDownList1" style="Z-INDEX: 102; LEFT: 257px; POSITION: absolute; TOP: 171px" runat="server">
<asp:ListItem>Item 1</asp:ListItem>
<asp:ListItem>Item 2</asp:ListItem>
<asp:ListItem>Item 3</asp:ListItem>
<asp:ListItem>Item 4</asp:ListItem>
<asp:ListItem>Item 5</asp:ListItem>
<asp:ListItem>Item 6</asp:ListItem>
</asp:DropDownList>
<asp:TextBox id="TextBox1" style="Z-INDEX: 103; LEFT: 210px; POSITION: absolute; TOP: 219px" runat="server">我首先改变这个</asp:TextBox>
</form>
</body>
</HTML>
CodeBehind文件:
WebForm1.aspx.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace WebApplication1
{
/// <summary>
/// 测试事件触发处理顺序
/// </summary>
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.DropDownList DropDownList1;
protected System.Web.UI.WebControls.TextBox TextBox1;
protected System.Web.UI.WebControls.Button Button1;
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Button1.Click += new System.EventHandler(this.Button1_Click);
this.DropDownList1.SelectedIndexChanged += new System.EventHandler(this.DropDownList1_SelectedIndexChanged);
this.TextBox1.TextChanged += new System.EventHandler(this.TextBox1_TextChanged);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
private void Button1_Click(object sender, System.EventArgs e)
{
Response.Write("Hello,world!<br>");
}
private void DropDownList1_SelectedIndexChanged(object sender, System.EventArgs e)
{
Response.Write("DropDownList Change事件被触发了!<br>");
}
private void TextBox1_TextChanged(object sender, System.EventArgs e)
{
Response.Write("TextBox1_TextChanged事件被触发了!<br>");
}
}
}
在上面的例子中,我们在页面上面放置了一个下拉框以及一个TextBox文本框,我们首先改变TextBox中的文字,在改变下拉框的选项,我们可以看到这两个Change事件并没有立即触发POST动作,而是被客户端暂时缓存了,当我们点击测试按钮的时候,才刷新整个页面,也就是提交给了服务器端进行消息处理,但是处理的顺序并不是我们刚才进行的那样,可以看到下面的结果:
读者可以试着自己将上面的代码拷贝到VS.NET中运行一下看看结果怎样。
4.7、ASP.NET Framework深度历险 – 深入ASP.NET状态管理模型
4.8、ASP.NET Framework深度历险 – 深入ASP.NET安全管理模型
4.9、ASP.NET Framework深度历险 – 利用MSMQ、SOAP实现分布式应用系统
4.10、ASP.NET Framework深度历险 –ASP.NET中的设计模式(Design Pattern)应用