ASP.NET请求处理全过程
一个ASP.NET请求过程中,从浏览器中发出一个Web请求 到 这个请求被响应并显示在浏览器中的过程中究竟会发生哪些不同的事件,当我们进入这个事件之旅时,我们也会试着明白在请求处理的每个事件当中我们可以做什么业务逻辑处理操作。
首先把整个过程大致分成两步:
- ASP.NET会创建一个能够处理请求的环境。换句话说,它会创建一个包含请求、响应以及上下文对象的应用程序对象来处理这个请求。
- 一旦ASP.NET环境被创建,用户请求就会通过由modules(管道)、handlers(处理程序)和page objects(页面对象)触发的一系列事件进行处理。简而言之,我们暂且将此步称为MHPM(Module、Handler、Page和Module Event)。
ASP.NET环境的创建
- 第一步:用户请求到达IIS后,发现处理不了这个后缀的文件,就去查找映射表。IIS首先会检查哪一个ISAPI扩展能够处理这个请求,这会取决于文件的后缀名。例如:如果请求的是一个'.aspx'的页面,那么就会被传递到'aspnet_isapi.dll'来进行处理。
- 第二步:如果这是该网站的首次请求,那么一个称为'ApplicationManager'的类会首先创建一个该网站可以运行的应用程序域(App Domain)。正如我们所知,应用程序域隔离部署在同一台IIS服务器上的两个不同的Web应用程序。因此,即使其中一个应用程序域出现了错误,也不会影响其他应用程序域的正常运作。
应用程序域
.NET平台下,程序集并没有直接加载进 进程 中(传统的Win32程序是直接承载的)。.NET可执行程序承载在进程的一个逻辑分区中,术语称应用程序域(简称AppDomain)。应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个 轻量级的进程。
- 第三步:在新创建的应用程序域中,会创建ASP.NET的宿主环境,也就是HttpRuntime对象。一旦宿主环境被创建完成,网站调用HttpRuntime类的静态方法处理请求,ASP.NET最核心的对象如HttpContext、HttpRequest和HttpResponse对象都会被创建好。HttpApplication对象将会被分配给一系列的ASP.NET核心对象来处理请求的页面。
这里面主要包括:
- 分析请求报文,并将报文数据封装到一个叫HttpWorkRequest类对象应用的属性中
- 通过调用HttpApplicationFactory类的一个静态方法创建HttpApplication对象(这里每次HttpApplicationFactory都会到HttpApplication池中去查找,看看有没用空闲的HttpApplication对象,如果有,就直接拿来使用,否则才创建新的使用,网站针对此次请求的所有运行过程都在这个对象中完成)
如果你的系统中存在一个global.asax文件,那么这个global.asax文件的对象也会被创建。但是,需要注意的是你的global.asax需要继承自HttpApplication类。
global.asax
Global.asax 文件(也称作 ASP.NET 应用程序文件)是可选文件,包含用于响应 ASP.NET 或 HttpModule 引发的应用程序级别事件的代码。(换句话说,我们可以自定义后面我们所要介绍的一些事件,因为请求处理流程会经历后面的10多个事件,我们可以写代码来自定义其中的一些事件,加一些我们想做的业务逻辑操作,比如:URL重写、身份验证、图片水印等等。)
-
创建HttpContext对象,这个对象是当前请求的上下文环境,里面包含处理请求的所有参数数据,其中最重要的就是HttpRequest和HttpResponse对象。
HttpRequest
对象主要包含了所有的请求信息,这些信息来源于HttpWorkRequest对象中包含的属性:Form(客户端表单数据)、QueryString(客户端url参数)
HttpResponse
主要包含了TestWriter对象,用来保存页面类型执行过程中要输入给浏览器的数据
- 因为HttpApplication里面运行IHttpHandler handler=(通过反射方式创建的被请求页面类对象) 被请求页面对象里的ProcessRequest方法,所以,需要将HttpContext对象传入到HttpApplication中来,即HttpApplication对象将会被分配给一系列的ASP.NET核心对象来处理请求的页面
- 第四步:这时,HttpApplication开始通过HTTP管道事件、处理程序(Handlers)和页面事件来处理请求了。也就是说:它会触发 MHPM 中的事件来处理请求。
下图则形象地展示了在一个ASP.NET请求过程中的重要内部对象模型。最高层是ASP.NET运行时,它创建了一个应用程序域(AppDoamin),下层则创建了一个包含request、response以及context对象的HttpRuntime。
.NET平台处理HTTP请求的过程大致如下:
- 1、IIS得到一个请求;
- 2、查询脚本映射扩展,然后把请求映射到aspnet_isapi.dll文件
- 3、代码进入工作者进程(IIS5里是aspnet_wp.exe;IIS6里是w3wp.exe),工作者进程也叫辅助进程;
- 4、.NET运行时被加载;
- 5、非托管代码调用IsapiRuntime.ProcessRequest()方法;
- 6、每一个请求调用一个IsapiWorkerRequest;
- 7、使用WorkerRequest调用HttpRuntime.ProcessRequest()方法;
- 8、通过传递进来的WorkerRequest创建一个HttpContext对象
- 9、通过把上下文对象作为参数传递给HttpApplication.GetApplicationInstance(),然后调用该方法,从应用程序池中获取一个HttpApplication实例;
- 10、调用HttpApplication.Init(),启动管道事件序列,钩住模块和处理器;
- 11、调用HttpApplicaton.ProcessRequest,开始处理请求;
- 12、触发管道事件;
- 13、调用HTTP处理器和ProcessRequest方法;
- 14、把返回的数据输出到管道,触发处理请求后的事件。
- 当客户端向Web服务器请求一个页面文件时,这个HTTP请求会被inetinfo.exe进程截获(WWW服务),它判断文件后缀,如果是*.aspx、*.asmx等,就把这个请求转交给aspnet_isapi.dll,而aspnet_isapi.dll则会通过一个Http PipeLine的管道,将这个HTTP请求发送给w3wq.exe进程,当这个HTTP请求进入w3wq.exe进程之后,Asp.Net framework就会通过HttpRuntime来处理这个HTTP请求,处理完毕后将结果返回给客户端。当一个HTTP请求被送入到HttpRuntime之后,这个HTTP请求通过HTTP管道(HttpRuntime是HTTP管道的入口)被送入到一个被称之为HttpApplication Factory的一个容器当中,而这个容器会给出一个HttpApplication实例来处理传递进来的HTTP请求,同时HttpApplication实例会创建一个HttpContext对象来记录HTTP请求的上下文,而后这个HTTP请求会依次进入到如下几个容器中:HttpModule --> HttpHandler Factory --> HttpHandler.当系统内部的HttpHandler的ProcessRequest方法处理完毕之后,整个Http Request就被处理完成了.如果想在中途截获一个HttpRequest并做些自己的处理,就应该在HttpRuntime运行时内部来做到这一点,确切的说时在HttpModule这个容器中做到这个的。
通过MHPM触发的事件处理请求
一旦HttpApplication创建好,它就开始处理请求了。它经历了三个不同的部分:HttpModule、Page和HttpHandler。当它经过这些部分时,它将调用不同的事件,而这些事件的逻辑处理还可以由开发者来进行扩展和增加自定义处理。
先来了解一下什么是HttpModule和HttpHandlers。他们帮助我们在ASP.NET页面处理过程的前后注入自定义的逻辑处理
他们之间主要的差别
如果你想要注入的逻辑是基于像'.aspx','.html'这样的扩展名,那么你可以使用HttpHandler。换句话说,HttpHandler是一个基于处理器的扩展。
如果你想要在ASP.NET管道事件中注入逻辑,那么你可以使用HttpModule。也可以说,HttpModule是一个基于处理器的事件。
下面是请求处理过程的逻辑流程,其中有4个重要的步骤
- 第一步(M:HttpModule):客户端请求开始被处理。在ASP.NET引擎执行和创建HttpModule触发事件(在此过程中,你也可以注入自定义逻辑)之前,有6个事件你可以在页面对象创建之前来使用,它们分别是:BeginRequest、AuthenticateRequest、AuthorizeRequest、ResolveRequestCache、AcquireRequestState 以及 PreRequestHandlerExecute。
- 第二步(H:HttpHandler):一旦以上6个事件被触发后,ASP.NET引擎就将会调用 ProcessRequest 事件,即使你已经在项目中实现了HttpHandler。
- 第三步(P:ASP.NET Page):一旦HttpHandler逻辑执行,ASP.NET页面对象就被创建了。而ASP.NET页面被创建,一系列的事件也会随之被触发,它们可以帮助我们自定义逻辑注入到这些事件里边。在此过程中,有6个重要事件给我们提供了占位符,以便我们在ASP.NET页面中写入逻辑,它们分别是:Init、Load、Validate、Render 和 Unload。你可以通过记住单词SILVER来记忆这几个事件,S—Start(没有任何意义,仅仅是为了形成一个单词),I(Init)、L(Load)、V(Validate)、E(Event)、R(Render)。
- 第四步(M:HttpModule):一旦页面对象执行结束并从内存中被卸载,HttpModule提供了提交返回页面的执行事件,同样,在这些事件中也可以被注入自定义的返回处理逻辑。这里有4个重要的提交处理事件:PostRequestHandlerExecute、ReleaserequestState、UpdateRequestCache以及EndRequest
下图形象地展示了上面的四个步骤:
对于执行HttpApplication的ProcessRequest方法这个过程可以看成一个管道,要先后按照顺序执行19个委托事件,其中第八个时,创建 被请求的页面对象,第11到12事件之间,执行了被创建的页面类对象的ProcessRequest方法
什么是请求管道?
请求管道就是把Application的一系列事件串联成一条线,这些事件按照排列的先后顺序依次执行,事件处理的对象包括HttpModule、HttpHandler、ASP.NET Page
熟悉请求管道实现程序运行的全过程:
- BeginRequest:开始处理请求
- AuthenticateRequest:授权验证请求,获取用户授权信息
- PostAuthenticateRequest:获取成功
- AunthorizeRequest:授权,一般来检查用户是否获得权限
- PostAuthorizeRequest:获得授权
- ResolveRequestCache:获取页面缓存结果
- PostResolveRequestCache:已获取缓存
- PostMapRequestHandler:创建页面对象
- AcquireRequestState:获取Session-----先判断当前页面对象是否实现了IRequiresSessionState接口,如果实现了,则从浏览器发来的请求报文体中获得SessionID,并到服务器的Session池中获得对应的Session对象,最后赋值给HttpContext的Session属性
- PostAcquireRequestState:获得Session
- PreRequestHandlerExecute:准备执行页面对象,执行页面对象的ProcessRequest方法
- PostRequestHandlerExecute:执行完页面对象了
- ReleaseRequestState:释放请求状态
- PostReleaseRequestState:已释放请求状态
- UpdateRequestCache:更新缓存
- PostUpdateRequestCache:已更新缓存
- LogRequest:日志记录
- PostLogRequest:已完成日志
- EndRequest完成
详解ASP.NET页面事件
在上面的部分中,我们已经了解了一个ASP.NET页面请求事件的整体流程。那么,在其中一个最重要的部分就是ASP.NET页面,但是我们并没有对其进行详细讨论
每一个ASP.NET页都有2个部分:一个是在浏览器中进行显示的部分,它包含了HTML标签、viewstate形式的隐藏域 以及 在HTML input中的数据。当这个页面被提交到服务器时,这些HTML标签会被创建到ASP.NET控件,并且viewstate还会和表单数据绑定在一起。一旦你在后置代码中得到所有的服务器控件,你可以执行和写入你自己的逻辑并呈现给客户浏览器。
当页面进行回发时,如点击按钮,以上事件都会重新执行一次,这时的执行顺序为:
- OnPreInit
- OnInit
- OnInitComplete
- OnPreLoad
- Page_Load
- OnLoad
- Button_Click
- OnLoadComplete
- OnPreRender