(翻 译)从底层了解ASP.NET体系结构

(翻 译)从底层了解ASP.NET体系结构

前言

关 于ASP.NET的底层的工作机制,最近园子里讨论的甚是火热。相信很多人都看过Rick Strahl先生的一篇经典之作:A low-level Look at the ASP.NET Architecture ,经Rick Strahl先生同意,我把他的这篇文章翻译成中文,希望能够给想深入了解ASP.NET工作机制的朋友一点帮助。 


特别说 明:翻译此文的目的仅仅是为了给广大的ASP.NET爱好者提供一些帮助,由于本人能力有限,文中不对地方,还请批评指正。如果你需要转载,请你保留该文 以及原英文的链接。多谢!

作者:Rick Strahl  ||  翻译:today  || 下 载例子代码

目录

1.      ASP.NET是什么?

2.      从浏览器到ASP.NET

3.      ISAPI

4.      IIS5和IIS6 的不同之

5.      进入.NET 运行时

6.      加载.NET —稍微有点神秘

7.      回到运行时

8.      HttpRuntime,HttpContext 以及HttpApplication

9.      Web程序的主要部分:HttpApplication

10.  穿过ASP.NET

11.  HttpContext,HttpModules 和HttpHandlers

12.  HttpModules

13.  HttpHandlers

14.  是否已经提供了足够的底层知识?

 

 

 

摘要: ASP.NET 是一个用于构建Web 程 序的强大平台,提供了巨大的弹性和能力以至于它可以构建任意的Web 程序。许多人仅仅对处于ASP.NET 高层次的框架如:WebFormsWebServices 比较熟悉,因此,在这篇文章里,我将会阐述有关ASP.NET 比 较底层的知识,并且将会解释,如何将请求从Web Server 移交给ASP.NET 运行时,然后通过ASP.NET HTTP 管 道处理这些请求。

对于我来说,了解一个平台的内部工作机制总是 会让我感到一些满足和安慰,如同洞察,可以帮助我写出更好的程序。知道了工具有什么用途,以及它们如何组装成复杂框架的一部分,这些将会使你很容易的找到 问题的解决方案,以及在你修改和调试错误时,都显得非常重要。这篇文章的目的就是从底层了解ASP.NET 以 及帮助你理解请求如何流入ASP.NET 处理管道里。同时,你将会了解ASP.NET 引擎的核心,以及一个Web 请求如何在这 里结束。这里讲到的许多知识都是你日常工作中没必要知道的,但是,如果你理解了ASP.NET 如何 把请求路由到应用程序的代码里(通常比较高层次的),这将对你非常有用。

 

注:整个ASP.NET 引 擎完全构建在托管代码里,其所有的扩展性都是通过托管代码去构建

 

使用ASP.NET 的 大多数都比较熟悉WebFormsWebServices 。 这些高层次的实现,使得构建Web 程序变得非常容易。ASP.NET 被 设计为驱动引擎,它把底层的接口提供给Web Server ,为高层次Web 应用程序的前端和末端提供了路由服务。WebFormsWebServices 是建立在ASP.NET 框架之 上,有关HTTP 处理的两种最常用的方式。

 

其实,在较低的层次上,ASP.NET 也 提供了足够多的灵活性。HTTP 运行时和请求管道提供了同样的能力,可以构建类似于WebFormsWebServices 的实现,当 然,这些已经使用.NET 托管代码实现了。如果你需要构建一个自定义HTTP 处理平台,而这个平台要比WebForms 所处 的层次低一点,那么你就会用到所有这些类似的功能。

 

构建大多的Web 界 面,使用WebForms 无疑是最容易的方法,但是,如果你想自定义一个内容处理器,或者需要对流 入和流出的内容做特殊的处理,或者需要为一个应用程序定制一个应用服务器接口,那么使用这些低层次的处理或者模块将会得到更好的性能,以及可以在真正的请 求处理中获得更多的控制权。尽管那些高层次的实现,如:WebFormsWebServices 已提供了类似的功能,但由于它们针对请求添加了太多的控制(导致性能下降)。所以你完全 可以另辟佳境,在较低层次上处理这些请求。

 

 

ASP.NET 是什么?

 

让我们从最简单的定义开始,ASP.NET 是什么?我通常喜欢用如下语句来描述ASP.NET

 

ASP.NET 是完全使用托管代码处理Web 请 求的一个成熟引擎平台。它不仅仅只是WebFormsWebServices

 

ASP.NET 是一个请求处理引擎。它获取客户端请求,然后通过它内置的管 道,把请求传到一个终点,在这个终点,开发者可以添加处理这个请求的逻辑代码。实际上这个引擎和HTTP 或 者Web Server 是完全分开的。事实上,HTTP 运 行时是一个组件,你可以把它宿主在IIS 之外的应用程序上。甚至完全可以和其它的服务组合在一起。 例如,你可以把HTTP 运行时宿主在Windows 桌 面应用程序里(详细的内容请查看:http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.aspx )。

 

通过使用内置的管道路由请求,HTTP 运行时提供了一套复杂的,但却很优雅的机制。在处理请求的每一个层面都牵涉到许多对象,但大多数对象都 可以通过派生或者事件接口来扩展。所以,此框架具有非常高的可扩展性。通过这一套机制,可以进入较低层次的接口如:缓存,身份验证,授权等是有可能的。你 可以在处理请求之前或之后过滤内容,或者仅仅把匹配指定签名的客户端请求直接路由到你的代码里或转向其它的URL 。 针对同一件事情,可以通过不同的处理方法完成,而且实现代码都非常的直观。除此之外,在容易开发和性能之间,HTTP 运 行时还提供了最佳的灵活性。

 

整个ASP.NET 引 擎完全构建在托管代码里,所有的扩展性功能都是通过托管代码的扩展提供。对于功能强大的.NET 框 架而言,使用自己的东西,构建一个成熟的、高性能的引擎体系结构已经成为一个遗嘱。尽管如此,但重要的是,ASP.NET 给 人印象最深的是高瞻远瞩的设计,这使得在其之上的工作变得非常容易,并且提供了几乎可以钩住请求处理当中任意部分的能力。

 

使用ASP.NET 可 以完成一些任务,之前这些任务是使用IIS 上的ISAPI 扩 展和过滤来完成的。尽管还有一些限制,但与ASP 相比,已经有了很大的进步。ISAPI 是底层Win32 样式的API ,仅它的接口就有1 兆,这对于大型的程序开发是非 常困难的。由于ISAPI 是底层的接口,因此它的速度也是非常的快。但对于企业级的程序开发是相当 的难于管理的。所以,在一定的时间内,ISAPI 主要充当其它应用程序或平台的桥接口。但是无论如 何,ISAPI 没有被废弃。事实上,微软平台上的ASP.NETIIS 的接口是通过宿主在.NET 里的ISAPI 扩展来通信的,然后直达ASP.NET 运行 时。ISAPI 提供了与Web Server 通 信的核心接口,然后ASP.NET 使用非托管代码获取请求以及对客户端请求发出响应。ISAPI 提供的内容经由公共对象类似于HttpRequestHttpResponse ,通过一个设计优良的、可访问的接口,以托管对象的方式暴露非托管数据。


从浏 览器到ASP.NET

 

让我们从一个典型的ASP.NET Web 请求的生命周期的起点开始。用户通过在浏览器中键入一个URL ,点击一个超 链接,提交一个HTML 表单(一个post 请 求),或者一个客户端程序调用基于ASP.NETWebService (通 过ASP.NET 提供服务)。在服务器端,IIS5 或 者IIS6 将会收到这个请求。ASP.NET 的 底层通过ISAPI 扩展与IIS 通信,然 后,通过ASP.NET ,这个请求通常被路由到一个带有.aspx 扩 展名的页面。但是,这个处理过程如何工作,则完全依赖于HTTP 处理器(handler )的执行。这个处理器将被安装用于处理指定的扩展。在IIS 中,.aspx 经由“应用程序扩展”被映射到ASP.NET ISAPIdll 文件:aspnet_isapi.dll 。每一 个触发ASP.NET 的请求,都必须经由一个已经注册的,并且指向aspnet_isapi.dll 的扩展名来标识。

 

注:ISAPI 是 自定义Web 请求处理中第一个并且具有最高性能的IIS 入 口点。

 

依靠扩展名,ASP.NET 把 一个请求路由到一个恰当的处理器,该处理器则负责处理这个请求。举个例子,WebServices 的 扩展名.asmx 不会把一个请求路由到磁盘上的某一个页面,而是会路由到在定义中附加了指定特性(WebMethodAttribute )的类,此特性会把它标识成一个Web Services 的实现。许多其它的处理器将随着ASP.NET 一起被安装。当然 也可以定义你自己的处理器。在IIS 里所有的HttpHandler 被 映射并指向ASP.NET ISAPI 扩展,并且这些HttpHandler 也 都在web.config 里配置,用于把请求路由到指定的HTTP 处 理器里执行。每一个处理器都是一个.NET 类,用于处理指定的扩展。而这些处理器可以处理简单到只 有几行代码的Hello World ,也可以处理复杂到类似ASP.NET 的 页面以及执行WebService 。就目前而言,仅仅需要理解扩展就是一种基本的映射机制,ASP.NET 用它可以从ISAPI 里获取一个请求, 然后把请求路由到指定处理该请求的处理器中。

 

ISAPI 连接

 

ISAPI 是底层非托管的Win32 API 。它定义的接口非常的单一并且性能最优。用这些接口处理原始指针(raw pointer ), 而函数指针列表(function pointer tables )则用于回调。ISAPI 提供了最低层的、高性能的接口,开发者和工具厂商可以使用这些接口深入到IIS 里。由于ISAPI 是非常低层的,所以不太适合使 用它构建应用级的程序。ISAPI 趋向于被当作桥接口使用,用于给高层次的工具提供应用服务类型的 功能。例如,ASPASP.NET 都是被 当作冷聚变(cold fusion )构建于ISAPI 之 上。大多PerlPHPJSP 的执行如同许多第三方解决方案一样,可以在IIS 运 行。ISAPI 是个非常好的工具,它给高层次的应用程序提供了高性能垂直访问接口。这使得那些高层 次的应用程序需要的信息可以从ISAPI 提供的信息中提炼。在ASPASP.NET 里,引擎可以提炼ISAPI 接口提供的表单里的对象如:RequestResponse ,这些对象可以从ISAPI 请求的信 息中读取它们各自的内容。

 

作为约定,ISAPI 支 持ISAPI 扩展(extensions ) 和ISAPI 过滤(filters )。扩展 是请求处理接口,提供了跟Web Server 输入和输出相关的逻辑处理。从本质上来说,它是一个 事务接口。ASPASP.NET 都被看作ISAPI 扩展的实现。ISAPI 是钩子接口,它允许 你查看进入IIS 的每一个请求并且可以修改请求的内容(包括输入和输出)或者改变模块(如:身份验 证等)的行为。顺便提一下,ASP.NET 通过两个方面的内容:HTTP 处理器(对应ISAPI 扩展)和HTTP 模块(对应ISAPI 过滤)映射到ISAPI 。这些相关的内容我将会在后面详细描述。

 

ISAPI 是代码的初始点,标识ASP.NET 一 个请求的开始。ASP.NET 映射了不同的扩展到它的ISAPI 扩 展,ISAPI 扩展位于.NET Framework 目 录:

 

<.NET FrameworkDir>/aspnet_isapi.dll

 

你可以在IIS 服 务管理器里看到这些映射,如图1 所示。打开Web 站 点的根目录的属性,选择主目录选项卡,然后查看 配置| 应用程序映射。

maping.jpg
1IIS 把不同的扩展名如.aspx 映射到ASP.NETISAPI 扩展。通过这种机制,在Web Server 里, 请求就可以被路由到ASP.NET 的处理管道里。

 

尽管.NET 需 要很多扩展名,但不必手工设置它们,你可以使用aspnet_regiis.exe 实用工具确保所 有的脚本映射都被注册。


cd <.NetFrameworkDirectory> aspnet_regiis – i


这将会把ASP.NET 运 行时的个别版本,通过脚本映射注册到整个Web 站点,并且安装客户端脚本库,这些脚本库将会被浏览 器上的控件所使用。注意,它是注册了安装在上面目录里的CLR 的那个版本。aspnet_regiis 有一个可选项,可以使你单独配置一个虚拟目录。每一个.NET 框架的版本,都拥有各自的aspnet_regiis , 对于不同版本的.NET 框架,你需要运行适当版本的aspnet_regiis , 注册到整个站点或者虚拟目录。从ASP.NET2.0 开始,在IIS 控制台里,有一个IIS  ASP.NET 配 置页面,在这个页面你可以挑选.NET 的版本。

 

IIS5 IIS6 的 不同之处

 

当一个请求进来的时候,IIS 会 检查脚本映射,然后把请求路由到aspnet_isapi.dll 。接下来这个DLL 文件的操作是什么呢?在IIS5IIS6 里,这个请求又是如何到达ASP.NET 运行时 的呢?它们两者的处理方式有没有重大变化呢?图2 展示了一个大致的流程。

how_aspnet_work.jpg
2 :站在比较高的角度,观看请求从IISASP.NET 运行时的流程,然后直达请求处理管道。IIS5IIS6ASP.NET 的接口采用了不同的方式,但 从请求到达ASP.NET 管道后的整个过程是完全一样的。

 

IIS5 直接把aspnet_isapi.dll 寄 宿在inetinfo.exe 进程里,或者它们中的一个将会与工作进程隔离,如果你拥有隔离权限, 那么可以把Web 站点和虚拟目录隔离的级别设置为中等或者高级。当第一个ASP.NET 请求进来的时候,DLL 将会在另一个EXE-aspnet_wp.exe 里分配一个新的进程,然后把相关信息路由到这个新分配的进程里。接着这个 新的进程会依次加载和寄宿.NET 运行时。每一个进入到ISAPI DLL 的请求,都将通过调用命名管道路由给这个工作者进程。

 

注:IIS6 与 之前的Web Server 不同,已经对ASP.NET 进 行了优化处理。IIS6 中使用了应用程序池。

 

值得注意的是,IIS6 改 变了这个处理模型,IIS 不再直接寄宿像ISAPI 扩 展的任何外部可执行代码。代替的是,IIS 总会保持一个单独的工作进程:应用程序池。所有的处理都 发生在这个进程里,包括ISAPI dll 的执行。对于IIS6 而 言,应用程序池是一个重大的改进,因为它们允许以更小的粒度控制一个指定进程的执行。你可以为每一个虚拟目录或者整个Web 站点配置应用程序池,这可以使你很容易的把每一个应用程序隔离到各自的进程里,这样就可以把它与运行在同 一台机器上其他程序完全隔离。从Web 处理的角度看,如果一个进程死掉,至少它不会影响到其它的进 程。

 

另外,应用程序池是高度可配置的。通过设置应用程序池的执行许 可,可以配置它们的执行安全环境。并且可以为指定的应用程序按照相同的粒度定制这些配置。对于ASP.NET 而 言,IIS6 最大的改进是使用应用程序池代替了machine.config 里 的ProcessModel 实体的大部分功能。在IIS5 里, 这个实体是很难管理的,因为它的设置是全局的,而且不能够在指定Web 程序的web.config 里覆盖这些设置。当IIS6 运行的 时候,ProcessModel 里的大部分配置将被忽略,取而代之的是读取应用程序池的配置。注意 我这里说的是大部分,另外的一些配置,像线程池的大小和IO 线程数目等仍然需要通过这个节点配置, 这是因为,在服务器的应用程序池里没有提供类似功能的配置。

 

由于应用程序池是在外部执行的,所以这些执行可以很容易的被监控 和管理。IIS6 还提供了许多性能计数器,可以对重启和超时选项进行跟踪。在许多情况下,这可以帮 助应用程序纠正问题。最后,IIS6 的应用程序池的实现不依赖COM+ ,正如IIS5 隔离进程一样,这提高了程序的性 能和稳定性,特别是那些需要在内部使用COM 对象的程序。

 

IIS6 应用程序池也包含了ASP.NET 固 有的东西,ASP.NET 可以和新的底层API 通 信,这些API 允许直接访问HTTP 缓冲存 储器的API ,而HTTP 缓冲存储器的API 可以直接进入Web Server 的缓冲存储 器,卸载ASP.NET 级别的缓存。

 

IIS6 里,ISAPI 扩展运行在应用程序池的工作进程里。.NET 运行时也运行在这个进程里,所以ISAPI 扩展 和.NET 运行时的通信是发生在进程内的。这就使得比必须使用命名管道接口的IIS5 具有更高的性能。尽管IIS 的宿主模型不同,但 是真正进入托管代码的接口是类似的,仅仅在获取被路由的请求时有一些变动。

 

进入.NET 运行时

 

进入.NET 运 行时的真正登录点发生在一些没有正式文档的类和接口之间。在微软外面的世界,这些接口鲜为人知。微软的民间也不太热衷于讨论这些细节,可能是因为他们认为 这些,对于使用ASP.NET 构建程序的开发者没有太多的影响。

 

工作进程aspnet_wp.exeIIS5 )和w3wp.exeIIS6 )宿主在.NET 运行时里。ISAPI DLL 通过底层的COM 调用一小撮非托管类 型的接口,其实,最终调用的是ISAPIRuntime 派生类的实例。进入运行时的第一个登录点是 未归档ISAPIRuntime 类,它通过COM 把 接口IISAPIRuntime 暴露给调用者。这些COM

接口是底层的IUnknown , 基于这些接口,就意味着从ISAPI 扩展到ASP.NET 之 间的调用属于内部调用。图3 是使用有名的反射工具Refectorhttp://www.aisto.com/roeder/dotnet/ )看到的IISAPIRuntime 接口的签名。Refector 是 一个可以查看和反编译程序集的工具,使用它可以很容易的查看元数据、反编译代码,就像图3 中看到的 那样。使用它一步一步地探究处理的过程,这是个非常不错的方法。


ildasm.bmp
3 :如果你想深入了解这个底层的接口,你可以打开Refector 工 具,然后指向System.Web.Hosting 命名空间。进入ASP.NET 的登录点以一个托管的COM 接口出现,该 接口将在ISAPI dll 里被调用。该登录点接收一个指向ISAPI ECB 非托管类型的指针。ECB 拥有访问整个ISAPI 接口的权限,它可以获取请求的数据以及把返回的数据发回IIS

 

IISAPIRuntime 接口担当着来自于ISAPI 扩 展(在IIS6 里是直接通信的,在IIS5 里 间接的通过命名管道通信的)的非托管代码和托管代码之间的桥梁。如果你留意一下这个类,你会发现ProcessRequest 方 法的签名像下面的样子:

 

[return: MarshalAs(UnmanagedType.I4)]

int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);

 

ecb 参数是ISAPI 扩 展控制块(extension control block ),它被作为非托管资源传给ProcessRequest 方法。此方法将获取ECB , 然后把它作为基本的输入和输出接口,用于RequestResponse 对 象。ISAPI ECB 包含着所有底层的请求信息,这其中包括服务器变量,用于表单变量(form variables )的输入流,以及用于写数据并把数据发送到客户端的输出流中。一个单独的ECB 引用基本上提供了一个ISAPI 请求可以访问的 所有功能。ProcessRequest 既是登录点也是登出点,在这里非托管资源最先与托管代码相 联系。

 

ISAPI 扩展以异步的方式处理请求。所以,当ISAPI 扩展调用了工作进程或者IIS 的线程后,会立 即返回,但会为当前有效的请求保留ECB 。因此,ECB 需 要包含这样的机制,即当请求结束的时候通知ISAPI (通过ecb.ServerSupportFunction 实 现),然后ISAPI 扩展释放ECB 资源。 接着以异步的方式立即释放ISAPI 工作线程,和卸载由ASP.NET 托 管的那个隔离的处理线程。

 

ASP.NET 得到ecb 引 用后,会在内部使用它来获取当前请求的相关信息,如服务器变量,POST 的数据以及返回输出到客户 端的数据。Ecb 将继续存活直到这个请求结束或者IIS 超 时, 在这之前,ASP.NET 将会与ecb 继续保持通信。当请求结束的时候,输出的内容会写进ISAPI 的 输出流里(通过ecb.WriteClient() 实现)。然后ISAPI 扩展会被通知请求已经结束,让它知道ECB 可 以被释放了。这个执行过程是非常高效的,这是因为,.NET 类本质上只是担当着一个相当瘦小的包装 器,而它包装的内容就是具有高性能的非托管ISAPI ECB

 

加载.NET— 稍微有点神秘

 

让我们回到之前略过的一个话题:当请求到达时,.NET 运行时是如何被加载的。具体在哪里加载的,这是比较模糊的。关于这个处理过程,我没有找到相关的文档, 由于我们现在讨论的是本地代码,所以通过反编译ISAPI DLL 文件并把它描述出来显得不太容 易。

 

我的最佳猜测是,在ISAPI 扩 展里,当第一个请求命中一个ASP.NET 的映射扩展时,工作线程就会引导.NET 运行时启动。一旦运行时存在了,非托管代码就可以为指定的虚拟目录请求一个ISAPIRuntime 对象的实例,当然前提条件是,这个实例还不存在。每一个虚拟目录都会拥有一个AppDomain ,在ISAPIRuntime 存在 的AppDomain 里,它将引导一个单独的程序启动。由于接口被作为COM 可调用的方法暴露,所以实例化操作将发生在COM 之 上。

 

为了创建ISAPIRuntime 的 实例,当指定虚拟目录的第一个请求到达时,System.Web.Hosting.AppDomainFactory.Create() 方 法将被调用。这将会启动程序的引导过程。这个方法接收的参数为:类型,模块名以及应用程序的虚拟路径,这些都将被ASP.NET 用 于创建AppDomain ,接着会启动指定虚拟目录的ASP.NET 程 序。HttpRuntime 的根对象将会在一个新的AppDomain 里 创建。每一个虚拟目录或者ASP.NET 程序将寄宿在属于自己的AppDomain 里。它们仅仅在有请求到达时启动。ISAPI 扩 展管理这些HttpRuntime 对象的实例,然后基于请求的虚拟路径,把请求路由到正确的应用程 序里。

 

回到运行时

 

这个时候,你已经拥有了一个ISAPIRuntime 的活动实例,并且可以在ISAPI 扩 展里调用。一旦运行时启动并运行起来,ISAPI 扩展就可以调用ISAPIRuntime.ProcessRequest() 方法了,而这个方法就是进入ASP.NET 通道真正的登录点。图4 展示了这里的流 程。


isapi_to_handler.jpg
4 :把ISAPI 的请求转到ASP.NET 通道需要调用很多没有正式文档的类和接口,以及几个工厂方法。每一个Web 程序/ 虚拟目录都运行在属于自己的AppDomain 里。调用者将维护一个IISAPIRuntime 接 口的代理引用,负责触发ASP.NET 的请求处理。

 

记住,ISAPI 是 多线程的,因此请求可以以多线程的方式穿过AppDomainFactory.Create() 返 回的对象引用。列表1 展现了从IsapiRuntime.ProcessRequest 方 法反编译得到的代码。这个方法接收一个ISAPI ecb 对象和一个服务器类型参数(这个参数用于 指定创建何种版本的 ISAPIWorkerRequest ),这个方法是线程安全的,因此多个ISAPI 线程可以同时 安全的调用单个返回对象的实例。

 

列表 1: ProcessRequest 请求进入 .NET 的登录点

public int  ProcessRequest(IntPtr ecb,  int  iWRType)
{
  
  // ISAPIWorkerRequest HttpWorkerRequest  继承,这里创建的是             

//  ISAPIWorkerRequest 派生类的一个实例

HttpWorkerRequest request1 =
        ISAPIWorkerRequest.CreateWorkerRequest(ecb,iWRType);
  
// 得到请求的物理路径
   
string  text1 = request1.GetAppPathTranslated();

// 得到AppDomain 的 物理路径
   
string  text2 = HttpRuntime.AppDomainAppPathInternal;
   
if  (((text2 ==  null ) || text1.Equals(".")) || 
         (
string .Compare(text1, text2,  true
          CultureInfo.InvariantCulture) == 0))
   {
      HttpRuntime.ProcessRequest(request1);
      
return  0;
   }
  // 如果外部请求的AppDomain 物理路径和原来AppDomain 的 路径不同,说明ISAPI 维持

// AppDomain 的 引用已经失效了,所以,需要把原来的程序关闭,当有新的请求时,会

// 再次启动程序。
   HttpRuntime.ShutdownAppDomain("Physical path changed from " + 
                                 text2 + " to " + text1);
   
return  1;
}

 

 

这里实际的代码并不重要,需要提醒的是,这里的代码是通过反编译.NET 框架内的代码得到的,你永远也不会和这些代码打交道,而且这些代码以后可能会有所变动。这里的用意是 揭示ASP.NET 在底层发生了什么。ProcessRequest 接 收了非托管参数ecb 的引用,然后把它传给了ISAPIWorkerRequest 对 象,这个对象负责创建当前请求的内容。如列表2 所示。

 

列表 2: 一个ISAPIWorkerRequest 的方法

// ***  ISAPIWorkerRequest 里的实现代码
public override byte [] GetQueryStringRawBytes()
{
   
byte [] buffer1 =  new byte [ this ._queryStringLength];
   
if  ( this ._queryStringLength  0)
   {
      
int  num1 =  this .GetQueryStringRawBytesCore(buffer1,
                 
this ._queryStringLength);
      
if  (num1 != 1)
      {
         
throw new  HttpException( "Cannot_get_query_string_bytes");
      }
   }
   
return  buffer1;
}

// ***  再派生于ISAPIWorkerRequest 的类ISAPIWorkerRequestInProcIIS6 的 实现// *** 代码
// *** ISAPIWorkerRequestInProcIIS6
internal override int  GetQueryStringCore( int  encode, StringBuilder 
buffer, 
int  size)
{
   
if  ( this ._ecb == IntPtr.Zero)
   {
      
return  0;
   }
   
return  UnsafeNativeMethods.EcbGetQueryString( this ._ecb, encode, 
buffer, size);
}

 

System.Web.Hosting.ISAPIWorkerRequest 继承于抽象类HttpWorkerRequest , 它的职责是创建一个抽象的输入和输出视图,为Web 程序的输入提供服务。注意这里的另外一个工厂方 法CreateWorkerRequest ,它的第二个参数用于指定创建什么样的工作请求对象(即ISAPIWorkerRequest 的派生类)。这里有3 个 不同的版本:ISAPIWorkerRequestInProcISAPIWorkerRequestInProcForIIS6ISAPIWorkerRequestOutOfProc 。 当请求到来时,这个对象(指ISAPIWorkerRequest 对象)将被创建,用于给RequestResponse 对象提供基础服务, 而这两个对象将从数据的提供者WorkerRequest 接收数据流。

 

抽象类HttpWorkerRequest 围 绕着底层的接口提供了高层的抽象(译注:抽象的目的是要把数据的处理与数据的来源解藕)。这样,就不用考虑数据的来源,无论它是一个CGI Web ServerWeb 浏览器控件还是你 自定义的机制(用于把数据流入HTTP 运行时),ASP.NET 都 可以以同样的方式从中获取数据。

 

有关IIS 的 抽象主要集中在ISAPI ECB 块。在我们的请求处理当中,ISAPIWorkerRequest 依赖于ISAPI ECB , 当有需要的时候,会从中读取数据。列表2 展示了如何从ECB 里 获取查询字符串的值的例子。

 

ISAPIWorkerRequest 实现了一个高层次包装器方法(wrapper method ),它调用了低层次的核心方法,而这些方法负责实际调用非托管API 或者说是“服务层的实现”。核心的方法在ISAPIWorkerRequest 的 派生类里得以实现。这样可以针对它宿主的环境提供特定的实现。为以后增加一个额外环境的实现类作为新的Web Server 接口提供了便利。同样使ASP.NET 运行在其它平台上成为可能。另外 这里还有一个帮助类:System.Web.UnsafeNativeMethods 。它的许多方 法是对ISAPI ECB 进行操作,用于执行关于ISAPI 扩 展的非托管操作。

 

HttpRuntime HttpContext 以 及HttpApplication

 

当一个请求到来时,它将被路由到ISAPIRuntime.ProcessRequest() 方法里。这个方法会接着调用HttpRuntime.ProcessRequest ,在这个方法里,做了几件重要的事情(使用Refector 反编译System.Web.HttpRuntime.ProcessRequestInternal 可 以看到)。

l        为请 求创建了一个新的HttpContext 实例

l        获取 一个HttpApplication 实例

l        调用HttpApplication.Init() 初始化管道事件

l        Init() 触发HttpApplication.ResumeProcessing() ,启动ASP.NET 管道处理

 

首先,一个新的HttpContext 对 象被创建,并且给它传递一个封装了ISAPI ECB ISAPIWorkerRequest 。 在请求的生命周期里,这个上下文(context )一直是有效的。并且可以通过静态的HttpContext.Current 属性访问。正如它的名字暗示的那样,HttpContext 对象表示当前活动请求的上下文,因为它包含了在请求生命周期里你会用到的所有必需对象的 引用,如:RequestResponseApplicationServerCache 。在请求处理过程的任何时候,你都可以使用HttpContext.Current 访 问这些对象。

 

HttpContext 对象还包含了一个非常有用的列表集合,你可以使用它存储有关特 定的请求需要的数据。上下文(context )对象创建于一个请求生命周期的开始,在请求结束时被 释放。因此,保存在列表集合里的数据仅仅对当前的请求有效。一个很好的例子,就是记录请求的日志机制,在这里,通过使用Global.asax 里的Application_BeginRequestApplication_EndRequest 方法,你可以从请求的开始时间至结束时间段内,对请求进行跟 踪。如列表3 所示。记住HttpContext 是 你的朋友,在请求或者页面处理的不同阶段,如果你需要相关数据都可以使用它获取。

 

列表 3: 通过在通道事件里使用HttpContext.Items 集合保存数据

protected void  Application_BeginRequest(Object sender, EventArgs e)
{
   
//*** Request Logging
   
if  (App.Configuration.LogWebRequests)
      Context.Items.Add("WebLog_StartTime",
                        DateTime.Now);
}

protected void  Application_EndRequest(Object sender, EventArgs e)
{
   
// *** Request Logging
   
if  (App.Configuration.LogWebRequests) 
   {
      
try 
      {   
         TimeSpan Span = DateTime.Now.Subtract( 
          (DateTime)Context.Items["WebLog_StartTime"]);
         
int  MiliSecs = Span.TotalMilliseconds;

         // do your logging
         WebRequestLog.Log(
                App.Configuration.ConnectionString,
                
true ,MilliSecs);
   }
}

 

一旦请求的上下文对象被搭建起来,ASP.NET 就需要通过一个HttpApplication 对 象,把你的请求路由到合适的程序/ 虚拟目录里。每一个ASP.NET 程 序都拥有各自的虚拟目录(Web 根目录),并且它们都是独立处理请求的。

 

Web 程序的主要部分:HttpApplication

 

每一个请求都将被路由到一个HttpApplication 对象。HttpApplicationFactory 类 会为你的ASP.NET 程序创建一个HttpApplication 对 象池,它负责加载程序和给每一个到来的请求分发HttpApplication 的引用。这个HttpApplication 对象池的大小可以通过machine.config 里 的ProcessModel 节点中的MaxWorkerThreads 选 项配置,默认值是20

 

HttpApplication 对象池尽管以比较少的数目开始启动,通常是一个。但是当同时有 多个请求需要处理时,池中的对象将会随之增加。而HttpApplication 对象池,也将会被 监控,目的是保持池中对象的数目不超过设置的最大值。当请求的数量减小时,池中的数目就会跌回一个较小的值。

 

对于你的Web 程 序而言,HttpApplication 是一个外部容器,它对应到Global.asax 文件里定义的类。基于标准的Web 程 序,它是你实际可以看到的进入HTTP 运行时的第一个登录点。如果你查看Global.asax (后台代码),你就会看到这个类直接派生于HttpApplication

 

public class  Global : System.Web.HttpApplication

 

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

l        BeginRequest

l        AuthenticateRequest

l        AuthorizeRequest

l        ResolveRequestCache

l        [ 此处创 建处理程序(即与请求 URL 对应的页)。]

l        AcquireRequestState

l        PreRequestHandlerExecute

l        [ 执行处 理程序。]

l        PostRequestHandlerExecute

l        ReleaseRequestState

l        [ 响应筛 选器(如果有的话),筛选输出。]

l        UpdateRequestCache

l        EndRequest

 

这里的每一个事件都在Global.asax 文 件中以Application_ 为前缀,无实现代码的方法出现。举个例子,如Application_BeginRequest()Application_AuthorizeRequest() 。 由于它们在程序中会经常用到,所以出于方便的考虑,这些事件的处理器都已经被提供了,这样你就不必再显式的创建这些事件处理器的委托了。

 

每一个ASP.NET Web 程序运行在各自的AppDomain 里,在AppDomain 里同时运行着多个HttpApplication 的 实例,这些实例存放在ASP.NET 管理的一个HttpApplication 对 象池里,认识到这一点,是非常重要的。这就是为什么可以同时处理多个请求,而这些请求不会互相干扰的原因。

 

使用列表4 的 代码,可以进一步了解AppDomain ,线程,HttpApplication 之 间的关系。

 

列表 4: AppDomain, Threads and HttpApplication instances 之间的关系

private void  Page_Load( object  sender, 
                       System.EventArgs e)
{
   
// Put user code to initialize the page here
   
this .ApplicationId = ((HowAspNetWorks.Global)
   HttpContext.Current.ApplicationInstance).ApplicationId; 

   
this .ThreadId = AppDomain.GetCurrentThreadId();

   
this .DomainId = 
              AppDomain.CurrentDomain.FriendlyName;

   
this .ThreadInfo = "ThreadPool Thread: " + 
            Thread.CurrentThread.IsThreadPoolThread.ToString() +
             "
< br > Thread Apartment: " + 
            Thread.CurrentThread.ApartmentState.ToString();

   
// ***  为了可以同时看到多个请求一起到 达,故意放慢速度
   Thread.Sleep(3000);
}

 

这是样例程序的一部分,运行的结果如图5 所示。为了检验结果,你应该打开两个浏览器,输入相同的地址,观察那些不同的ID 的值。

 

application_pools.jpg
5 :同时运行几个浏览器,你会很容易的看到AppDomainsapplication 对象以及处理请求的线程之间内在的关系。当多个请求触发时,你会看到线程和applicationID 在改变,而AppDomainID 却没有发生变化。

 

你将会观察到AppDomain ID 一直保持不变,而线程和HttpApplicationID 在请求多的时候会发生改变,尽管它们会出现重复。这是因为HttpApplications 是 在一个集合里面运行,下一个请求可能会再次使用同一个HttpApplication 实例,所以有 时候HttpApplicationID 会 重复。注意,一个HttpApplication 实例对象并不依赖于一个特定的线程,它们仅仅是被 分配给处理当前请求的线程而已。

 

线程由.NETThreadPool 提供服务,默认情况下,线程模型为多线程单元(MTA )。 你可以通过在ASP.NET 的页面的@Page 指 令里设置属性ASPCOMPAT="true" 覆盖线程单元的状态。ASPCOMPAT 意味着COM 组件将在一个安全的环境 下运行。ASPCOMPAT 使用了单线程单元(STA ) 的线程为请求提供服务。STA 线程在线程池里是单独设置的,这是因为它们需要特殊的处理方式。

 

实际上,这些HttpApplication 对 象运行在同一个AppDomain 里是很重要的。这就是ASP.NET 如 何保证web.config 的改变或者单独的ASP.NET 页 面得到验证可以贯穿整个AppDomain 。改变web.config 里 的一个值,将导致AppDomain 关闭并重新启动。这确保了所有的HttpApplication 实例可以看到这些改变,这是因为当AppDomain 重 新加载的时候,来自ASP.NET 的那些改变将会在AppDomain 启 动的时候重新读取。当AppDomain 重新启动的时候任何静态的引用都将重新加载。这样,如果程 序是从程序的配置文件读取的值,这些值将会被刷新。

 

在示例程序里可以看到这些,打开一个ApplicationPoolsAndThreads.aspx 页面,注意观察AppDomainID 。然后在web.config 里做一些改动(增加一个空格,然后保存),重新加载这个页面(译注:由于缓存的影响可能会 在原来的页面上刷新无效,需要先删除缓存再刷新即可),你就会看到一个新的AppDomain 被创 建了。

 

本质上,这些改变将引起Web 程 序重新启动。对于已经存在于处理管道的请求,将继续通过原来的管道处理。而对于那些新的请求,将被路由到新的AppDomain 里。 为了处理这些“挂起的请求”,在这些请求超时结束之后,ASP.NET 将强制关闭AppDomain 甚至某些请求还没有被处理。因此,在一个特定的时间点上,同一个HttpApplication 实例在两个AppDomain 里 存在是有可能的,这个时间点就是旧的AppDomain 正在关闭,而新的AppDomain 正在启动。这两个AppDomain 将 继续为客户端请求提供服务,直到旧的AppDomain 处理完所有的未处理的请求,然后关闭,这时 候才会仅剩下新的AppDomain 在运行。

 

穿过ASP.NET 管道

 

HttpApplication 负责请求的传输,通过触发事件,通知应用程序正在发生的事情。 这个是作为HttpApplication.Init() 方法的一部分实现的(使用Refector 查看System.Web.HttpApplication.InitInternalHttpApplication.ResumeSteps() 的代码可以看到这一点)。在这个方法里,创建和 启动了一系列事件,包括绑定事件处理器。Global.asax 里的事件处理器会自动映射到对应的 事件,它们也可以映射到额外添加的HTTPModules ,这些HTTPModules 本质上是HttpApplication 已 发布事件的一种扩展。

 

通过在web.config 里 注册,HTTPModulesHttpHandlers 可 以被动态的加载,并且可以添加到事件链条上。HTTPModules 实际上就是事件处理器,它可以 钩住指定HttpApplication 的事件。而HttpHandlers 就 是一个端点,它可以被调用处理“应用程序级的请求处理”。

 

HTTPModules HttpHandlers 将 被加载,然后添加到调用链上作为HttpApplication.Init() 方法调用的一部分。 图6 展示了不同的事件,这些事件何时被触发以及通道上的哪些部分会受到它们的影响。


event_flow.jpg
6 :事件在ASP.NET HTTP 管道里传输。HttpApplication 对象的事件驱动请求在管道里传输。HTTPModules 可 以截获这些事件,可以覆盖或增强已存在的功能。

 

HttpContext HttpModulesHttpHandlers

HttpApplication 本身并不知晓发送给Web 程 序的数据。它仅仅是个消息邮递者,只负责事件之间的通信。它触发事件,然后通过传递HttpContext 对 象,把信息发送给被调用的方法。在之前我们提到,当前请求的数据是在HttpContext 对象里 保存。它提供了请求从开始到结束需要的所有数据。图7 展示了ASP.NET 的 管道之间的传输流程。注意,从请求的开始至结束,上下文对象(Context )都是你的伙伴,你可 以在一个事件方法里使用它保存数据,然后在之后的事件方法里获取这些数据。


request_flow.jpg
7ASP.NET 管道在一系列事件接口之间传输请求, 这提供了足够的灵活性。HttpApplication 担当主容器,负责加载Web 程序,当请求到来时触发事件以及在管道之间传输请求。经过已配置的HTTP 过 滤和模块时,每一个请求都将遵循一个公有的路径。过滤器可以检查穿梭在管道里的每一个请求,而处理器允许实现应用程序的逻辑或者应用程序级的接口像WebFormsWebServices 一样。为了 给程序提供输入和输出,上下文对象(Context )给请求提供了所需的信息,它贯穿了请求生命周 期的始终。

 

ASP.NET 管道一旦启动,HttpApplication 将 逐一触发事件,如图6 展示的那样。每一个事件都将被触发,如果事件绑定了事件处理器,那么这些事件 处理器将被调用,执行它们的任务。这个过程的主要目的是通过调用HttpHandler 处理指定的 请求。对于ASP.NET 请求而言,HttpHandler 是 处理请求机制的核心,在这里任意的应用程序级的代码被执行。记住,ASP.NET 的页面和Web Service 都是HttpHandler 的 具体实现,在这里,所有请求处理的核心功能被实现。HTTPModule 则倾向于在分发给事件处理 器之前或者之后对内容进行处理。在ASP.NET 里典型的默认操作有:鉴定(Authentication ),处理前的缓存操作以及各种处理后的编码操作机制。

 

关于HTTPModuleHttpHandler ,这里有很多有用的信息,但为了保持这篇文章合理的尺度,我将仅仅讲述关于它们一些简 短的、整体的看法。

 

HttpModules

 

伴随着HttpApplication 触 发的一系列事件,请求将会在管道之间穿梭。你已经看到了这些发布的事件,在Global.asax 里 都有对应事件的处理方法。这个方法(步骤)是程序(ASP.NET 应用程序)携带的,尽管这个并不 总是你需要的。如果你想构建一套通用的HttpApplication 事件处理程序,并以插件的形 式添加到任意的Web 程序里。那么你可以使用HTTPModule , 它是可重复使用的,不需要添加任何实现代码就可以在其它程序里使用,而你所做的仅仅在web.config 里 注册。

 

模块本质上是过滤器,在功能上类似于ASP.NET 请求级别的ISAPI 过滤。对于每一个穿 过ASP.NETHttpApplication 对 象的请求,模块都允许在HttpApplication 对象触发的事件处理方法里截获这些请求。这 些模块以类的形式存储在外部程序集里,可以在web.config 里配置,当程序启动的时候加载。 通过实现指定的接口和方法,模块就可以被添加到HttpApplication 的事件链上。多个HttpModules 可以钩住相同的事件,事件发生的顺序是它们在web.config 里 声明(配置)的顺序。如下就是在web.config 里,一个模块的声明。

 

< configuration >
  
< system.web >
    
< httpModules >
     
< add  name = "BasicAuthModule" 
      
type ="HttpHandlers.BasicAuth,WebStore" />
    
</ httpModules >
  
</ system.web >
</ configuration >

 

注意,在这里你需要指定一个完整的类型名和一个不带扩展名的程序 集的名字。

 

模块允许你查看每一个传入的Web 请求,基于触发的事件基础上执行操作。模块是非常有用的,它可以修改请求,输出响应的内容以及提供自定义 的身份验证,另外还可以在特定的程序里,针对ASP.NET 的每一个请求提供响应前处理和响应后处 理。许多ASP.NET 的特征像身份验证,会话引擎都是作为HTTP 模 块实现的。

 

HttpModules 感觉有点类似于ISAPI 过 滤器,是由于它们查看进入ASP.NET 程序的每一个请求,但它们局限性于仅可以查看映射到某一个ASP.NET 程序或者虚拟目录的请求,和映射到ASP.NET 的 请求。因此,你仅可以查看所有的ASPX 页面或者任意其它自定义的已经映射到这个程序的扩展名(译 注:作者可能漏掉了ASMXASHX 等, 这里的意思应该是所有的ASP.NET 默认的扩展名)。但是,你不能查看标准的.HTM 或者图像文件,除非你通过添加这些扩展名,明确的把它们映射到ASP.NETISAPI DLL ,如图一中那样。对于模块,一个常见的用处是过滤一个指定的文件夹的JPG 图片内容,然后使用GDI+ 在每一张返回的图片上 方添加“样图”字样。

 

实现一个HTTP 模 块是非常简单的:你必须实现一个IHttpModule 接口,它包含两个方法:Init()Dispose() 。传递的事件参数中包 含着一个HttpApplication 对象的引用,接着,它会给你访问HttpContext 对象的权限。在这两个方法里,你可以钩住HttpApplication 的 事件。举个例子,如果你想用一个模块钩住AuthenticateRequest 事件,那么你需要 做的会像列表5 中展示的那样。

 

列表 5: 一个HTTP 模块实现 起来非常的简单

public class  BasicAuthCustomModule : IHttpModule
{

   
public void  Init(HttpApplication application)
   {
      
// *** Hook up any HttpApplication events
      application.AuthenticateRequest += 
          
new  EventHandler( this .OnAuthenticateRequest);
   }
   
public void  Dispose() { }

   
public void  OnAuthenticateRequest( object  source, 
                                   EventArgs eventArgs)
   {
      HttpApplication app = (HttpApplication) source;
      HttpContext Context = HttpContext.Current;
      … 
do  what you have to  do …                     } 
}

 

记住,你的模块已经有访问HttpContext 对 象的权限了。从这里到所有其它内置的ASP.NET 管道对象如ResponseRequest ,因此你可以获取输入 的数据等等。但紧记,某些对象现在可能不能使用,只有到这条链的后面的环节才会有效,

 

Init() 方 法里,你可以钩住多个事件,因此在一个模块里,可以管理多个不同功能的操作。但是,应该尽可能的把不同的逻辑代码放到不同的类里,这样可以确保这个模块是 标准的组件。在许多情况下,你实现的功能可能需要钩住多个事件。举个例子,一个日志过滤器可能需要在BeginRequest 里 记录请求的开始时间,在EndRequest 里记录请求完成的时间。

 

HttpModules 里 使用HttpApplication 的事件时,有一点是需要注意的,Response.End()HttpApplication.CompleteRequest() 方 法会使ASP.NET 跳过HttpApplication 和 模块的事件链。可以查看这两个方法的帮助文档获取更多的信息。

 

HttpHandlers

 

模块是相当低层次的,它针对每一个传入ASP.NET 程序的请求触发。HTTP 处理器则着重于 处理一个指定的请求映射。通常一个页面扩展已经被映射到处理器了。

 

实现一个HTTP 处 理器所需要做的是非常基础的,但是通过访问HttpContext 对象,就会有很多有用的功能。HTTP 处理器通过一个简单IHttpHandler 接 口实现(或者它的异步版本IHttpAsyncHandler )。它仅仅有一个方法ProcessRequest() 和一个属性IsReusable 。 这里的关键是ProcessRequest() 会得到一个HttpContext 对 象的实例。这个单独的方法将从开始到结束负责处理一个Web 请求。

 

单一的,简单的方法?可能太简单了,是吗?可是,一个简单的接 口,但它的实现可能并不简单。记住,WebFormsWebServices 都 是作为HTTP 处理器实现的。因此,在这个看似简单的接口里,过多的实现过程被隐藏了。关键是,事 实上到现在,一个HTTP 处理器可以访问所有为了开始处理请求而被组建和配置起来的ASP.NET 的内置对象。关键是,HttpContext 对 象提供了所有与请求相关的功能,可以获取流入的数据和输出数据到Web 服务器。

对于HTTP 处 理器而言,所有的操作都通过简单地调用ProcessRequest() 方法执行。可以简单到如下 的样子:

public void  ProcessRequest(HttpContext context)
{
   context.Response.Write("Hello World");
}

一个完整的实现像WebForms 页 面引擎那样,可以根据HTML 模版展现复杂的表单。所以,这里的关键点是你想用这个简单但功能强大 的接口做什么。

 

因为对你而言,HttpContext 对 象是可以使用的,这样你就可以访问RequestResponseSessionCache 对象,因此你已经拥有了ASP.NET 请求的所有特征,可以自己做主如何处理用户提交的信息,然后给客户端返回处理后产生的内容。记 住,在一个ASP.NET 请求的生命期内,上下文对象始终都是你的朋友。

 

处理器的关键操作通常是往Response 对 象里写输出数据,或者更确切的说,是往Response 对象的OutputStream 里写。这个就是真正返回到客户端的输出数据。在底层,由ISAPIWorkerRequest 负责把OutputStream 发 回给ISAPIecb.WriteClient 方 法,因为ecb.WriteClient 方法才是真正执行IIS 产 生输出数据的。

 

通过使用大量的处于高层的、基础的框架接口,WebForms 实现了一个HTTP 处理器。但最后,WebFormRender() 方法却简单的使用了 一个HtmlTextWriter 对象,把最终的输出发送给context.Response.OutputStream 对 象而结束。因此,相当的奇妙,最终甚至一个高层次的工具像WebForm ,也仅仅是在ResponseRequest 对象之上的一个高层次 的抽象。

 

在这个时候,你可能想知道,是否需要从头实现一个完整的HTTP 处理器?毕竟WebForms 已经提供了一个 易用的HTTP 处理器的实现,因此为什么还要为这些大量底层的东西而烦恼,放弃这些已经提供的灵活 性呢?

 

WebForms 是非常棒的,使用它可以生成复杂的HTML 页面和处理业务层的逻辑而这些都需要图形化的设计和模版化的页面。除此以外,WebForms 引擎还可以完成更多的,需要丰富的表现层的任务。如果所有你想做的是:从系统读取文件,由执行 的代码把它返回。这样的任务,如果避开使用Web Forms 页面框架,代替的是直接处理文件然后 返回,相信后者才是更高效的方法。如果你做的事情仅仅是像从数据库里读取图片一样,那么完全没有必要使用页面框架来处理,因为这里的确没有Web UI 需要你俘获离开图片的事件。

 

在这里找不到,组建一个页面对象和会话对象以及俘获页面层上事件 的理由。对于你的任务而言,这里需要做的仅仅是执行代码,而这些与你手头上的任务毫不相干。

 

因此,这种情况下使用处理器会更加高效。处理器可以完成使用WebForms 不可能完成的事情。比如:需要处理这样的请求,它们没有必要在磁盘上存在对应的物理文件,这 些被请求的路径通常称为虚拟的URL 。为了使这样的请求正常的工作,你需要确保已经在应用程序的扩 展对话框(如图一所示)里关闭了“确认文件是否存在”的复选框。

 

对于内容提供者来说,这是通用的,如:动态的图像处理,XML 服务,提供虚拟的URL 使原来的URL 重定向,下载管理等等,这些均不能使用WebForms 实 现。

 

是否已经 提供了足够的底层知识?

 

哎呀!我们终于绕着请求的处理周期回到了原地。尽管我在这里没有 探讨有关HTTP 模块和HTTP 处理器如何 工作的更多细节,但仍旧提供了许多对你有帮助的底层信息。挖掘这些信息花费了我很多时间,通过了解ASP.NET 在 底层的工作模式,使我感到非常地满足,希望它也可以给你带来同样的感受。

 

在结束之前,还是让我们来回顾一下,我在这篇文章中讨论的从IISHTTP 处理器的事件序列:

l        IIS 得 到一个请求

l        查询 脚本映射扩展,然后把请求映射到aspnet_isapi.dll 文件

l        代码 进入工作者进程(IIS5 里是aspnet_wp.exeIIS6 里是w3wp.exe

l        .NET 运 行时被加载

l        非托 管代码调用IsapiRuntime.ProcessRequest() 方法

l        每一 个请求调用一个IsapiWorkerRequest

l        使用WorkerRequest 调用HttpRuntime.ProcessRequest() 方 法

l        通过 传递进来的WorkerRequest 创建一个HttpContext 对 象

l         通过把上下文对象作为参数传递给 HttpApplication.GetApplicationInstance() ,然后调用该方法,从应用程序池中获取一个 HttpApplication 实例。

l        调用HttpApplication.Init() ,启动管道事件序列,钩住模块和处理器

l        调用HttpApplicaton.ProcessRequest ,开始处理请求

l        触发 管道事件

l        调用HTTP 处理器和ProcessRequest 方法

l        把返 回的数据输出到管道,触发处理请求后的事件

 

使用手边的例子将会更容易记住这些零碎的片断是如何组合起来的。 为了记住它,我会不时地看一下它。现在应该回到工作中了,去做一些不太抽象的事情吧。

 

尽管讨论的这些是基于ASP.NET 1.1 的。但这里描述的底层处理在ASP.NET 2.0 好像并没有多大改变。

 

最后,非常感谢来自微软的Mike Volodarsky ,是他校验了这篇文章,并且提出了一些宝贵的意见。还有Michele Leroux Bustamante ,他为ASP.NET 管道请求流程的幻灯片提供 了依据。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值