——本文是《项目驱动学习——DotText源码学习》系列的第一篇文章,在这之后会持续发表相关的文章。

概论

在阅读DotText源码之前,让我们首先了解一下ASP.NET的工作机制,可以使我们更好的理解。ASP.NET是Web服务器(IIS)的ISAPI(Internet Server API)扩展。当IIS接收到客户端浏览器发来的请求后,它根据请求的文件类型确定由哪个ISAPI扩展来处理该请求,并将请求转发给ASP.NET(如果是ASP.NET处理的相应文件类型的话,如*.aspx、*.asmx、*.ashx)。ASP.NET应用首先进行初始化,并装载配置模块,然后经过一系列步骤来完成对客户端请求的响应。

下面按以下步骤来解析ASP.NET的工作机制:

  1. Step1:用户从浏览器中请求网页(.aspx)
  2. Step2:ASP.NET接收到对应用程序的第一个请求
  3. Step3:为每个请求创建ASP.NET核心对象
  4. Step4:将HttpApplication对象分配给请求
  5. Step5:由HttpApplication管线处理请求

Step1、用户从浏览器中请求网页(.aspx)

当IIS收到请求后,会对所请求文件的扩展名进行检查,确定应该由哪个ISAPI扩展来处理该请求,然后将该请求传递给合适的ISAPI扩展,也就是说IIS将该请求传给ASP.NET。

说明:

1、ASP.NET即是IIS的ISAPI扩展,并且ASP.NET处理已映射到它的文件扩展名,如.aspx、.ascx、.ashx和.asmx

2、ISAPI(Internet Server API)服务器扩展是可以被HTTP服务器加载和调用的DLL。Internet服务器扩展也称为Internet服务器应用程序(ISA),用于增强符合Internet服务器API(ISAPI)的服务器的功能。ISA 通过浏览器应用程序调用,并且将相似的功能提供给通用网关接口 (CGI) 应用程序。想了解更多

IIS是如何将请求传给ASP.NET的呢,或者说他们是怎么通信的呢?其实,IIS捕获的任何请求经过检查分析,然后映射到一个外部模块进行真正的处理。而负责处理输入的ASP.NET请求的外部模块,是一个名为aspnet_isapi的动态链接库(DLL)——aspnet_isapi.dll。该模块并不是一个普通的DLL,而是一个ISAPI模块。ISAPI模块是实现了一种特殊协议的DLL,能够与IIS可执行文件通信。

下面以IIS6的进程模型为例,说明这个过程(注意:不同的IIS版本这个过程是有差异的)。

IIS 6.0进程模型,也称为工作进程隔离模式(worker process isolation mode),是以应用程序池的概念为中心的。IIS6.0总会保持一个单独的工作进程——应用程序池。所有的处理都发生在这个进程里,包括ISAPI DLL的执行。应用程序池是一组共享相同的工作进程副本的Web应用程序,从而将Web服务器的核心部分与可能无法正常工作的应用程序相分离。我们可以用一组不同的属性来配置每个应用程序池及其工作进程副本。

在默认的IIS 6.0进程模型下运行时,ASP.NET应用程序使用一个通用的、与ASP.NET无关的(ASP.NET-agnostic)工作进程——这也是服务Web服务器托管的所有应用程序的工作进程,该程序名为w3wp.exe。在IIS6.0里,ISAPI扩展运行在应用程序池的工作进程里。而.NET运行时也运行在这个进程里,所有ISAPI扩展和.NET运行时的通信时发生在进程内的,这就使得性能更高。

分配给同一个应用程序池的所有Web应用程序共享该可执行程序的一个副本。IIS 6.0构架的另一个关键组件是名为http.sys的内核模式设备驱动程序。该驱动程序是负责捕获并服务任何输入请求的HTTP监听程序。

当一个请求到达时,http.sys把它传递到被调用应用程序所属的应用程序池管理的队列。每个应用程序池有一个队列。w3wp.exe工作进程装入aspnet_isapi.dll;接着该ISAPI扩展装入公共语言运行库(common language runtime,简称CLR),启动ASP.NET运行库管道来处理请求。如果IIS 6.0进程模型正在使用,内置的ASP.NET工作进程将被禁用。工作进程使用http.sys获取请求,并把响应发送给客户端。

就这样一个请求通过IIS6.0被转到ASP.NET进行处理。下面用一个图来形象描述。

ASP.NET的工作机制

Step2、ASP.NET接收到对应用程序的第一个请求

当ASP.NET接收到对应用程序中任何资源的第一个请求时,应用程序管理器(ApplicactionManager)就会创建一个应用程序域;在应用程序域中,将创建宿主环境(HostingEnviroment类的实例),它提供对有关应用程序的信息的访问。

说明:

1、应用程序域(AppDomain)为全局变量提供应用程序隔离,在其中可以加载和执行托管代码程序集,并允许单独卸载每个应用程序,但不允许卸载单个程序集只能通过卸载应用程序域来卸载程序集。

2、HostingEnvironment类,在托管应用程序的应用程序域(AppDomain)内向托管应用程序提供应用程序管理功能应用程序服务

如下图所示:

ASP.NET的工作机制

应用程序域(AppDomain)是怎么创建的呢?其实是这么回事:从Step1知道接收到请求后IIS6.0的w3wp.exe工作进程将装入aspnet_isapi.dll,接着该ISAPI扩展装入CLR,启动ASP.NET运行库管道来处理请求。在aspnet_isapi.dll理是通过调用ISAPIRuntime派生类的实例进入.NET运行时的,这是因为IISAPIRuntime接口担当着来自于ISAPI扩展的非托管代码和托管代码之间的桥梁。

一旦运行时存在了,非托管代码就可以为指定的虚拟目录请求一个ISAPIRuntime对象的实例(如果这个实例还不存在)。每一个虚拟目录都会拥有一个AppDomain,在ISAPIRuntime存在的AppDomain里,它将引导一个单独的程序启动。为了创建ISAPIRuntime的实例,当指定虚拟目录的第一个请求到达时,System.Web.Hosting.AppDomainFactory.Create()方法将被调用(点此查看在MSDN中关于System.Web.Hosting.AppDomainFactory.Create()方法的描述)。这将会启动程序的引导过程。这个方法接收的参数为:类型,模块名以及应用程序的虚拟路径,这些都将被ASP.NET用于创建AppDomain,接着会启动指定虚拟目录的ASP.NET程序。HttpRuntime的根对象将会在一个新的AppDomain里创建。每一个虚拟目录或者ASP.NET程序将寄宿在属于自己的AppDomain里。它们仅仅在有请求到达时启动。ISAPI扩展管理这些HttpRuntime对象的实例,然后基于请求的虚拟路径,把请求路由到正确的应用程序里。

Step3、为每个请求创建ASP.NET核心对象

创建应用程序域并实例化了宿主环境之后,ASP.NET将创建并初始化核心对象(如HttpContext、HttpRequest和HttpRespone)。HttpContext类包含特定于当前应用程序请求的对象,如HttpRequest和HttpRespone对象。HttpRequest对象包含有关当前请求的信息,包括Cookie和浏览器信息。HttpRespone对象包含发送到客户端的响应,包括所有呈现的输出和Cookie。这些工作都是在ISAPIRuntime.Proce***equest()方法里执行的。详细的了解HttpContext点此查看MSDN。

其实,一旦运行时启动并运行起来,ISAPI扩展就可以调用ISAPIRuntime.Proce***equest()方法了,而这个方法就是进入ASP.NET通道真正的登录点。经过Step2后,请求它将被路由到ISAPIRuntime.Proce***equest()方法里。这个方法会接着调用HttpRuntime.Proce***equest,在这个方法里,做了几件重要的事情:

  1. 为请求创建了一个新的HttpContext实例
  2. 获取一个HttpApplication实例
  3. 调用HttpApplication.Init()初始化管道事件
  4. Init()触发HttpApplication.ResumeProcessing(),启动ASP.NET管道处理。

Step4、将HttpApplication对象分配给请求

初始化所有核心应用程序对象之后,将通过创建HtppApplication类的实例启动应用程序。如果应用程序具有Global.asax文件,则ASP.NET会创建Global.asax类(从HttpApplication类派生)的一个实例,并使用该派生类表示应用程序。同时,ASP.NET将创建所有已配置的模块(如状态管理模块、安全管理模块),在创建完所有已配置的模块后,将调用HttpApplication类的Init方法。

Global.asax里的事件处理器会自动映射到对应的事件,也可以映射到额外添加的HttPModules,这些HttPModules本质上是HttpApplication已发布事件的一种扩展。我们可以自己定一些HttpModules和HttpHandlers,然后通过在web.config里注册,HttPModules和HttpHandlers可以被动态的加载,并且可以添加到事件链条上。HttPModules实际上就是事件处理器,它可以钩住指定HttpApplication的事件。而HttpHandlers就是一个端点,它可以被调用处理“应用程序级的请求处理”。HttPModules和HttpHandlers将被加载,然后添加到调用链上作为HttpApplication.Init()方法调用的一部分。

如下图所示:

ASP.NET的工作机制

说明:每一个请求都将被路由到一个HttpApplication对象。HttpApplicationFactory类会为你的ASP.NET程序创建一个HttpApplication对象池,它负责加载程序和给每一个到来的请求分发HttpApplication的引用(由Step2知道这些是在ISAPIRuntime.Proce***equest()方法中调用HttpRuntime.Proce***equest()完成的)。

Step5、由HttpApplication管线处理请求

在该阶段由HttpApplication类执行一系列事件;并根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现了IHttpHandler的类来对请求处理(即由一些HttpHandlers处理)。

HttpApplication主要用作HTTP管道的事件控制器,因此,它的接口主要有事件组成,这些事件包括:

  1. BeginRequest
  2. AuthenticateRequest
  3. AuthorizeRequest
  4. ResolveRequestCache
  5. AquireRequestState
  6. PreRequestHandlerExecute
  7. …Handler Execution…
  8. PostRequestHandlerExecute
  9. ReleaseRequestState
  10. UpdateRequestCache
  11. EndRequest

ASP.NET管道一旦启动,HttpApplication将逐一触发事件,如上所述的事件。每一个事件都将被触发,如果事件绑定了事件处理器,那么这些事件处理器将被调用,执行它们的任务。这个过程的主要目的是通过调用HttpHandler处理指定的请求。对于ASP.NET请求而言,HttpHandler是处理请求机制的核心,在这里任意的应用程序级的代码被执行。

如果该请求针对从Page类派生的对象(页),并且需要对该页进行编译,则ASP.NET会在创建该页的实例之前对其进行编译,在装载后用该实例来处理这个请求,处理完后通过HttpRespone输出,最后释放该实例。

参考文献&扩展阅读

【1】微软公司著,《Web应用开发——ASP.NET2.0》29~32页,高等教出版社

【2】Esposito, D.著,《ASP.NET 2.0高级编程》1.1.1 节ASP.NET进程模型,清华大学出版社

【3】By Rick Strahl,A low-level Look at the ASP.NET Architecture强烈推荐

【4】园子里的today从底层了解ASP.NET体系结构(上文的翻译)(强烈推荐

【5】Simone BusoliASP.NET Internals - The bridge between ISAPI and Application Domains

如果想知道得更清楚请阅读上述博文(推荐,因为书籍不一定有)或书籍。