页面的编译
特定.aspx资源的程序集的生成分为两个步骤进行。首先,该资源文件的源代码会被解析,根据得到的信息,从Page类(或Page的派生类)派生出相应的类。然后,动态生成的类会被编译为程序集,该程序集之后会被缓存到ASP.Net专用的临时目录下。
只要链接的aspx源文件没有被更改,且整个应用程序没有重启,已编译的页面就一直存在。对已链接aspx文件的任何更改,将使相关程序集变为无效,并在该页面下一次被请求时,强制HTTP运行库创建新的程序集。
编辑web.config和global.asax之类的文件会导致整个应用程序重启。在这种情况下,在某个页面被请求时,所有页面会被重新编译。如果bin文件夹中程序集被改动(新建或替换),所有页面也会被重新编译。
运行机制
IIS的Web服务器所提供的所有资源,会按扩展名进行组织。任何来访的请求会被分配给特定的运行时进程模块进行实际处理。IIS上下文中可处理Web资源的模块是Internet服务器应用程序编程接口(ISAPI)扩展--实际是传统的Win32 dll。IIS和ISAPI扩展会针对专用的通信协议使用这些dll入口方法。当IIS需要ISAPI扩展完成某项任务时,它会加载相应的dll,并通过有效参数调用适当的函数。
当某个资源请求到达时,IIS首先会判断所请求资源类型。静态资源直接由IIS处理,无需调用任何外部模块。IIS在本地Web服务器上访问被请求的文件,并将其内容写入输出控制台返回给浏览器。资源映射信息存储在IIS元库中。ASP.Net在安装时对IIS元库进行修改,使aspnet_isapi.dll能处理某些典型的ASP.NET资源。下图列出了其中的一些:
IIS5进程模型
如果ASP.NET应用程序部署到Windows Server 2003前的系统中,只能选择IIS5.0进程模型。在该模型中,aspnet_isapi.dll不处理aspx文件,而是充当调度程序。它收集有关被调URL和底层资源的所有信息,将其发给另一个特殊进程aspnet_wp.exe。ISAPI扩展与工作进程间的通信是通过命名管道(named pipe)完成。单CPU下,工作进程的一个副本始终运行,承托所有活动的Web应用程序;多个CPU下允许多个工作进程运行,每个进程对应一个可用CPU。
每个Web应用程序是通过其虚拟目录标识,分别从属于独立的应用程序域(AppDomain)。当某个虚拟目录被客户端第一次请求时,ASP.NET工作进程会创建一个新的AppDomain。之后,ASP.NET运行库加载所有所需的程序集,并将控制权交给托管HTTp管道,后者对该请求做实际的处理。
如果客户端请求的是一个已在运行的Web应用程序,ASP.NET运行库只是将该请求转发给与其虚拟目录相关联的现有AppDomain。如果处理当前页面的程序集没被该AppDomain加载,则动态创建它;如在第一次调用时已被创建,则直接使用。
IIS6.0进程模型
如果Web服务器操作系统是Windows Server 2003或更高版本,IIS6.0进程模型是ASP.NET的默认选择。IIS6.0管道以一个名为w3wp.exe的工作进程为中心。该可执行程序副本由分配给同一应用程序池的所有Web应用程序共享。IIS6.0使我们能对应用程序池进行定制,以达到托管于Web服务器的各种应用程序所需的隔离程度。
w3wp.exe工作进程会加载aspnet_isapi.dll,随后,ISAPI扩展加载公共语言运行时(CLR),启动asp.net运行时管道,对请求进行处理。使用IIS6.0进程模型,asp.net内建的工作进程便会被禁用。
IIS6.0以内核级模块形式实现了HTTP监听程序。所有输入请求会首先被一个驱动程序(http.sys)管理。http.sys驱动程序会监听请求,将其追加到相应的应用程序池请求队列中。一个叫“Web管理服务”的模块会读取IIS元库,并指示http.sys驱动程序创建请求队列,队列数量与元库中注册的应用程序池数量一致。
总之,使用IIS6.0进程模型,ASP.NET会运行得更快,因为inetinfo.exe(IIS管理服务)与工作进程不需要进行任何进程间通信。HTTP请求直接投递承托CLR的工作进程。此外,ASP.NET工作进程不是特殊的进程,而仅仅是IIS工作进程的副本。这样,回收进程、缓存页面和监视运行状况的负担会由IIS承受。
被请求页面的表示
每个引用aspx资源的输入请求都会被映射到Page的派生类。Asp.net http运行时环境首先会确定处理该请求的类名。页面URL与类名通过某种命名约定关联在一起。如:请求页面为default.aspx,则可推断出相关联类名为ASP.default_aspx。如果当前加载到AppDomain的程序集不包含这个名称的类,HTTP运行库将发出该类的创建和编译命令。该aspx资源的源代码会被解析,以创建该类的源代码,结果会临时保存在asp.net临时文件夹中。接下来,该类被编译并加载到内存中,以处理该请求。当同一页面的请求再次到达时,由于该类已存在,所以不会再执行编译过程。
ASP.default_aspx类继承于Page类或Page派生类。更确切的讲,ASP.default_aspx的基类(由VS创建)会与代码隐藏类(由ASP.NET HTTP运行库动态组织)合并。
请求的处理
被请求的aspx类创建后,HTTP运行时环境通过公共接口IHttpHandler来调用该类。根类Page实现了该接口,它包含两个成员ProcessRequest方法和布尔类型的IsReusable属性。一旦HTTP运行库获得代表被请求资源类的实例,便调用ProcessRequest方法开始处理,以便向浏览器做出响应后终结。调用并执行ProcessRequest及其所触发事件的整个过程称为“页面的生命周期”。
ASP.NET工作线程会将任何输入请求交给HTTP管道。HTTP管道是一条完全可扩展的托管对象链,其工作方式与一般意义上的“管道”颇为相似。所有这些对象构成了所谓的ASP.NET HTTP运行时环境。
HttpRuntime对象
页面请求会传递给管道中的每一个处理原始HTTP有效负载的对象,在该链路的终端生成要发给浏览器的标记代码。HttpRuntime类就是该管道的入口点。对于输入的每个请求,ASP.NET工作线程通过创建HttpRuntime类的实例,并调用其ProcessRequest来激活HTTP管道。注意:尽管HttpRuntime.ProcessRequest与IHttpHandler接口的名称意思相近,但二者实际并无关系。
HttpRuntime类包含许多私有和内部方法,但只公开了三个静态方法:Close、ProcessRequest和UnloadAppDomain。
HttpRuntime对象会在创建时对许多辅助处理页面请求的内部对象进行初始化。这些辅助对象包括缓存管理器和文件系统监视器(用于检测构成应用程序的文件的变动)。ProcessRequest方法被调用后,HttpRuntime对象即开始处理要发送到浏览器的页面。它会为请求创建一个新的上下文,并初始化一个特殊的文本编写器(writer)对象,该对象用于缓存标记代码。上下文对象是HttpContext类的实例,它封装了所有与请求有关的HTTP特有的信息。
之后,这个HttpRuntime对象使用上下文信息查找或创建能处理该请求的Web应用程序对象。通过包含在URL中虚拟目录信息,便可定位Web应用程序。查找或新建应用程序的对象叫HttpApplicationFactory,这是一个内部使用的对象,负责返回能处理该请求的有效对象。
应用程序工厂
在应用程序的生存期中,HttpApplicationFactory对象维护着许多HttpApplication对象,该对象用于处理输入的HTTP请求。当该程序工厂对象被调用后,它会验证请求的目标虚拟文件夹是否存在。如果应用程序已运行,该工厂从可用的对象池中获取一个HttpApplication对象,然后将它传给请求。如果没有可用的,则创建新的HttpApplication实例。
如果该虚拟目录不曾被调用,则在新的AppDomain中针对该虚拟目录创建一个HttpApplication对象。这样,如果应用程序文件global.asax存在,HttpApplication对象就需对它进行编译,并创建代表实际被请求页面的程序集。该过程相当于启动应用程序。HttpApplication对象用于处理页面请求,每次处理一个(多个对象用于处理并发的请求)。
HttpApplication对象
HttpApplication是一个基类,代表运行中的ASP.NET应用程序。运行中的ASP.NET应用程序由动态创建的继承于HttpApplication的类来表示。如果global.asax存在,那么通过解析其内容,可以创建动态生成的应用程序类的源代码。如果global.asax可用,应用程序类便会被创建,并根据它命名为ASP.global_asax。否则,会使用基类HttpApplication。
HttpApplication派生类的实例负责管理分配给它的请求的整个生命周期。只有在该请求处理完毕后,该实例才会被重用。HttpApplication维护着一系列HTTP模块对象,这些对象可对请求的内容进行筛选,甚至还可进行修改。在请求穿越管道的过程中,可能随时会调用已注册的模块。
HttpApplication对象能判断代表被请求资源的对象类型,随后,HttpApplication使用相应的处理程序工厂获取代表被请求资源的对象。工厂可能使用现有的程序集实例化被请求资源的类实例,也可能动态创建所需程序集,然后再实例化该对象。处理程序工厂对象实现了IHttpHandlerFactory接口,负责返回处理Http请求的托管对象--HTTP处理程序。一个ASP.NET页面只是一个处理程序对象(即实现IHttpHandler接口的类实例)。
页面工厂
一旦HttpApplication对象掌管了请求,就必须选择的个合适的处理程序,并创建该处理程序的实例。对于面向页面的请求,对应的工厂名为PageHandlerFactory。为找到合适的处理程序,HttpApplication会读取配置文件<httpHandlers>区段中的信息。下表包含了几个主要的已注册处理程序:
处理程序工厂不会在每次调用被请求资源时都进行编译,已编译代码被存储在Web服务器的ASP.NET临时目录中。
接到请求时,页面处理工厂会创建代表被请求页面的对象实例。页面对象继承于System.Web.UI.Page类,该类实现了IHttpHandler接口。页面对象会被返回给应用程序工厂,随后被传给HttpRuntime对象。最后的步骤由ASP.NET运行库完成,ASP.NET运行库会调用IHttpHandler的页面对象的ProcessRequest方法。这会使页面执行用户定义的代码,并为浏览器生成标记。
总结
HTTP页面请求处理流程如下图: