從更低的角度
這篇文章在一個(gè)底層的角度來關(guān)注一個(gè)web請求怎樣到達(dá)asp.net框架,從web服務(wù)器,通過ISAPI??纯催@些后面發(fā)生了什么,讓我們停止對asp.net的黑箱猜想。
ASP.NET是一個(gè)非常強(qiáng)大用來創(chuàng)建web應(yīng)用程序的平臺,它為創(chuàng)建web應(yīng)用程序提供了大量的靈活強(qiáng)大的支持。大多數(shù)人僅僅熟悉表層的WebForm和webservice,他們位于整個(gè)ASP.NET架構(gòu)的最表層。在這篇文章里,我將會描述非常底層的ASP.NET并且解釋一個(gè)web請求是如何從web服務(wù)器到達(dá)ASP.NET運(yùn)行時(shí),并且通過ASP.NET管道(pipeline)處理請求的。
對我來說理解一個(gè)平臺的內(nèi)部機(jī)制,能夠然我自己得到相應(yīng)的滿意和舒適,同時(shí)也可以幫助我們寫出更好的應(yīng)用程序。了解這些工具是怎樣作為整個(gè)框架的某一部分而互相配合,并且更加容易的找到問題的解決方案,更加重要的,在發(fā)生錯(cuò)誤的時(shí)候,能夠幫助你定位以及調(diào)試這個(gè)錯(cuò)誤。這篇文章的目標(biāo)就是從系統(tǒng)的角度來看ASP.NET,并且?guī)椭斫猓埱笫侨绾蔚竭_(dá)ASP.NET處理管道的。就這點(diǎn)而言,我們將會看到核心引擎,以及一個(gè)web請求是如何終結(jié)的。很多東西都是你在日常的工作中不需要知道的,但是它有利于理解ASP.NET架構(gòu)怎樣路由到你經(jīng)常編寫的應(yīng)用程序的高層代碼的。
大多數(shù)使用ASP.NET的人都是對Wenforms和WebService熟悉。這些高層的實(shí)現(xiàn)能夠簡化創(chuàng)建以web為基礎(chǔ)的應(yīng)用程序,并且,ASP.NET是一個(gè)驅(qū)動(dòng)引擎,提供了對web服務(wù)器的底層接口,也為你在你的程序中用到的典型的高層前端服務(wù)的路由機(jī)制提供接口。WebForm和WebService僅僅是兩個(gè)在ASP.NET框架核心上構(gòu)建的非常經(jīng)久耐用的HttpHandlers。
然而,ASP.NET在低層提供了更多的靈活性。HTTP Runtime和請求管道提供了所有的相同創(chuàng)建WebForm和WebService的能力,它們實(shí)際上也是由.NET托管代碼實(shí)現(xiàn)。而且,你如果決定自己定制、創(chuàng)建一個(gè)比WebForm稍低層的平臺,所有的ASP.NET的低層的這些功能、機(jī)制,你也同樣可以使用。
WebForm顯然是創(chuàng)建大多數(shù)web應(yīng)用程序的最容易的方式,但是你在創(chuàng)建自定義內(nèi)容的handlers或者對于進(jìn)、出內(nèi)容需要特殊的處理,或者你需要為另外一個(gè)應(yīng)用程序創(chuàng)建一個(gè)定制的應(yīng)用程序接口,使用低層的handlers或者modules能夠給你更好的性能以及對一個(gè)web請求的更好的控制。你也可以繞過WebForm和WebService的這些高層實(shí)現(xiàn)提供的這些功能、機(jī)制直接在底層進(jìn)行操作。
什么是ASP.NET
讓我們以一個(gè)簡單的定義開始:什么事ASP.NET?我喜歡把ASP.NET定義如下:
ASP.NET是一個(gè)使用托管代碼完成的,從前到后處理web請求的,久經(jīng)考驗(yàn)的框架。它并不僅僅是WebForm和WebService…
ASP.NET是一個(gè)請求處理引擎,它通過它的內(nèi)部的管道將一個(gè)請求傳送到一個(gè)開發(fā)者的代碼上。實(shí)際上這個(gè)引擎完全獨(dú)立于HTTP或者web服務(wù)器。事實(shí)上,HTTP Runtime是一個(gè)在IIS或者其他任何服務(wù)器之外的,您的應(yīng)用程序的宿主環(huán)境。舉一個(gè)例子,您可以將ASP.NET runtime放到一個(gè)Windows窗口中(點(diǎn)擊獲得更多詳情http://www./presentations/ASP.NETruntime/ASP.NETruntime.asp)
運(yùn)行時(shí)為請求通過這個(gè)管道提供了一個(gè)復(fù)雜而又優(yōu)雅的機(jī)制。有一系列的相關(guān)對象,大多數(shù)都是可以在請求的每一個(gè)層次,通過實(shí)現(xiàn)其子類或者實(shí)現(xiàn)事件接口來進(jìn)行擴(kuò)展。通過這個(gè)機(jī)制能夠接觸到非常低層的接口,例如緩存,權(quán)限驗(yàn)證等。你甚至能在接受請求的前后過濾內(nèi)容,或者將滿足特定要求的請求轉(zhuǎn)到你的代碼或者其他的URL地址。有很多不同的方法來完成相同的事情,而且所有的這些方法實(shí)現(xiàn)的都非常直接,這樣,就可以靈活的根據(jù)性能以及開發(fā)難度來選擇最好的方法.
整個(gè)的ASP.NET引擎都是托管代碼完成的,并且,可以支持通過托管代碼進(jìn)行拓展
整個(gè)的ASP.NET引擎都是托管代碼完成的,并且,可以支持通過托管代碼進(jìn)行拓展.這是一個(gè)對.NET框架是否能夠開發(fā)出久經(jīng)考驗(yàn)的、性能良好的框架的有力的證明。然而,給人印象最深刻的部分是ASP.NET的深思熟慮的架構(gòu),能夠使得這個(gè)結(jié)構(gòu)非常易用,提供了處理請求的任何一個(gè)部分的能力。
使用ASP.NET你能夠完成 以前是ISAPI擴(kuò)展和ISAPI篩選器領(lǐng)域的工作,雖然帶有一些局限性,但是比ASP要好很多。ISAPI是一個(gè)非常底層的Win32形式的API,它僅有非常貧乏的接口,非常難創(chuàng)建經(jīng)久耐用的應(yīng)用程序。由于ISAPI非常的底層而且非??焖?,它處在非托管開發(fā)層。這樣ISAPI有些時(shí)候主要用做連接其他的應(yīng)用程序平臺的橋。這并不意味著ISAPI已經(jīng)死了。事實(shí)上ASP.NET在微軟的平臺上,正是通過ISAPI的一個(gè)擴(kuò)展和ASP.NET的運(yùn)行時(shí)和IIS進(jìn)行交互的。ISAPI提供了Web服務(wù)器的核心接口,并且ASP.NET使用非托管的ISAPI代碼來向客戶端接收,發(fā)送數(shù)據(jù)。ISAPI提供的數(shù)據(jù),是通過一些通用的對象暴露出去的,像HttpRequest和HtttpReponse,他們通過托管代碼對象,以一個(gè)非常好的,易接觸的接口形式,對外暴露非托管代碼的內(nèi)容。
從瀏覽器到ASP.NET
讓我們從一個(gè)典型的ASP.NET Web Request的生命周期的最初開始。一個(gè)請求,在瀏覽器里,在一個(gè)用戶輸入一個(gè)URL地址或者點(diǎn)擊一個(gè)超鏈接,或者提交一個(gè)HTML表單(一個(gè)post類型的請求)?;蛘咭粋€(gè)客戶端的程序會調(diào)用ASP.NET的WebService,這個(gè)WebService也使用過ASP.NET進(jìn)行服務(wù)的。在服務(wù)器端,IIS5或者6接收到請求。在最底層,ASP.NET通過一個(gè)ISAPI擴(kuò)展和IIS進(jìn)行交互。這樣的一個(gè)請求通常會被路由到一個(gè)以aspx為擴(kuò)展名的頁面文件,但是如何處理這個(gè)請求,完全取決與HTTP handler的實(shí)現(xiàn),這個(gè)handler為了處理指定的擴(kuò)展名而創(chuàng)立起來。在IIS里,.aspx 被‘應(yīng)用程序擴(kuò)展’(也可以成為腳本映射) 映射到ASP.NET ISAPI dll - ASP.NET_isapi.dll.每一個(gè)觸發(fā)ASP.NET的請求都是必須通過在ASP.NET_isapi.dll指明和注冊的擴(kuò)展名。
根據(jù)擴(kuò)展名,ASP.NET將請求路由到相應(yīng)的,負(fù)責(zé)響應(yīng)請求的handler。舉一個(gè)例子,asmx這是一個(gè)WebService的擴(kuò)展名,它不會被路由到磁盤上面的一個(gè)頁面文件,而是路由到一個(gè)WebService類里面。其他很多的映射已經(jīng)被ASP.NET安裝了,而且你也可以定義你自己的。所有的這些HttpHandlers都是在ASP.NET ISAPI里面指出,從而在IIS里面被映射,或者在web.config文件里面設(shè)置,路由到指定的HTTP Handler的實(shí)現(xiàn)。每一個(gè)handler,是處理指定的擴(kuò)展名的一個(gè).NET類,這個(gè)類可以簡單到一個(gè)HelloWorld程序,也可以非常復(fù)雜,像一個(gè)ASP.NET page類或者 WebService 的實(shí)現(xiàn)。現(xiàn)在,理解擴(kuò)展名是這種映射機(jī)制的基礎(chǔ),這種機(jī)制是ASP.NET用來從IIS獲得一個(gè)用戶請求然后將其路由到指定的處理請求的handler。
ISAPI是第一個(gè)也是性能最高的定制web請求處理的切入點(diǎn)。
ISAPI 連接
ISAPI是一個(gè)非常低層的非托管的win32API。這個(gè)接口根據(jù)ISAPI定義規(guī)范,非常的簡單,還有優(yōu)化過的性能。他們非常的底層-處理指針,用函數(shù)指針來進(jìn)行回調(diào)-它們?yōu)殚_發(fā)者和工具提供最底層的,最好性能的,來處理IIS的接口。由于ISAPI非常的底層,他并適合創(chuàng)建應(yīng)用程序級別的代碼,而且,ISAPI趨向主要被用作 為高層工具提供應(yīng)用程序服務(wù)器功能的 橋接口。舉個(gè)例子,ASP和ASP.NET都是建立在ISAPI上面,還有Cold Fusion,運(yùn)行在IIS上面的,大多數(shù)的Perl,PHP以及JSP的實(shí)現(xiàn)還有很多的第三方的解決方案,比如我的Web Connection framework for Visual FoxPro都是建立在ISAPI上面的。ISAPI是一個(gè)為高層應(yīng)用程序提供接口的非常優(yōu)秀的工具,這些接口抽象了ISAPI提供的信息。在ASP.NET和ASP中,這些引擎將ISAPI提供的信息抽象為像Request和Response這樣的對象,使他們讀取到ISAPI的Request信息。把ISAPI想象成鉛錘。對于ASP.NET來說,ISAPI dll非常瘦小,僅僅是作為一個(gè)路由機(jī)制,以管道形式傳送請求到ASP.NET運(yùn)行時(shí),所有的重型的處理甚至請求的線程管理,都在ASP.NET引擎和你的代碼中。
依照協(xié)議,ISAPI支持ISAPI擴(kuò)展和ISAPI篩選器。擴(kuò)展是一個(gè)請求處理接口,并且提供處理web服務(wù)器的傳入傳出的邏輯,它本質(zhì)上是一個(gè)事務(wù)接口。ASP.NET和ASP都是做為ISAPI擴(kuò)展被實(shí)現(xiàn)的。ISAPI過濾器是一組接口,他們能 查看每一個(gè)進(jìn)入IIS的請求,修改內(nèi)容,或者改變類似于驗(yàn)證功能的行為。順便提一句,在ASP.NET中,通過兩個(gè)概念映射了類似ISAPI的功能:HTTPHandlers(擴(kuò)展)和HttpModules(篩選器)。一會,我們看詳細(xì)的內(nèi)容。
ISAPI是標(biāo)記著ASP.NET的請求的初始代碼。ASP.NET映射了各種擴(kuò)展名到ISAPI的擴(kuò)展里,這些映射都在.NET Framework的目錄下:
<.NET FrameworkDir>\ASP.NET_isapi.dll
你可以交互式的在IIS服務(wù)管理器里面看到這些映射,如圖一.選擇你的網(wǎng)站,然后“主目錄”,“配置”,“映射”。

圖一: IIS 映射各種擴(kuò)展名到ASP.NET ISAPI,像 .ASPX 。通過這個(gè)機(jī)制,請求在web服務(wù)器層被路由到ASP.NET的處理管道。
你不應(yīng)該手動(dòng)的設(shè)置它們,因?yàn)?NET需要他們。另外你也可以使用ASP.NET_regiis.exe 工具來使得各種腳本映射得到正確的注冊:
cd <.NetFrameworkDirectory>
ASP.NET_regiis - i
這就將會為整個(gè)的站點(diǎn)注冊特定版本的ASP.NET運(yùn)行時(shí),創(chuàng)建各種客戶端腳本庫。注意,這里是注冊在上面的文件夾安裝的特定版本的CLR 。ASP.NET_regiis命令的選項(xiàng)允許你可以單獨(dú)的設(shè)置一個(gè)虛擬目錄。每一個(gè)版本的.NET框架都有他自己版本的ASP.NET_regiis,你需要運(yùn)行一個(gè)恰當(dāng)版本的來注冊一個(gè)網(wǎng)站或者一個(gè)虛擬目錄。以ASP.NET2.0為例,你可以在IIS配置頁面里面的ASP.NET選項(xiàng)選擇.NET的版本。
IIS5和IIS6工作方式不同
當(dāng)一個(gè)請求進(jìn)入,IIS檢查腳本映射,然后將請求路由到ASP.NET_isapi.dll。這個(gè)DLL進(jìn)行的操作在IIS6和IIS5中明顯不同,圖2大概的展示了這個(gè)流程。
IIS5中直接宿主ASP.NET_isapi.dll在inetInfo.exe進(jìn)程中,或者一個(gè)獨(dú)立的進(jìn)程中。當(dāng)?shù)谝粋€(gè)請求來到這個(gè)DLL文件的時(shí)候,將會產(chǎn)生另外的一個(gè)新的進(jìn)程– ASP.NET_wp.exe –并且路由請求到這個(gè)新生成的進(jìn)程中。這個(gè)進(jìn)程一次的加載,宿主.NET運(yùn)行時(shí)。每一個(gè)請求都是先來到ISAPI然后通過命名管道路由到工作進(jìn)程

圖二– 從IIS到ASP.NET運(yùn)行時(shí)的. IIS 5 and IIS 6以不同的方式處理 ASP.NET,但是總的來說,一旦到了ASP.NET的管道,就相同了。
IIS6,不像以前的服務(wù)器,它是完全為ASP.NET做過優(yōu)化的
IIS 6 –應(yīng)用程序池萬歲
IIS6明顯的改變了處理模型,IIS不再像ISAPI的擴(kuò)展一樣直接的處理任何不相關(guān)的可執(zhí)行代碼。取而代之的是,IIS6總是創(chuàng)建一個(gè)獨(dú)立的工作進(jìn)程—一個(gè)應(yīng)用程序池—并且所有的請求都在這個(gè)進(jìn)程里面,包括ISAPI dll的執(zhí)行。
應(yīng)用程序池是IIS6的一個(gè)重要改善,因?yàn)樗鼈冊试S非常細(xì)粒度的控制指定進(jìn)程執(zhí)行的東西。可以為每一個(gè)虛擬目錄或者一個(gè)站點(diǎn)設(shè)置應(yīng)用程序池,所以你能夠?qū)⒚恳粋€(gè)web應(yīng)用程序分割到每一個(gè)進(jìn)程中,這個(gè)進(jìn)程和其他的應(yīng)用程序的進(jìn)程完全獨(dú)立。如果其中的一個(gè)進(jìn)程死掉了,也不會影響到其他的進(jìn)程。
另外,應(yīng)用程序池是高度可設(shè)置化的。你可以通過設(shè)置它的執(zhí)行模擬級別 來設(shè)置其執(zhí)行的安全環(huán)境,你可以為每一個(gè)Web應(yīng)用程序定制權(quán)限。為ASP.NET的一個(gè)重要的改進(jìn)就是應(yīng)用程序池取代了大多數(shù)的在machine.config的進(jìn)程模型。這在IIS里面比較難于管理,因?yàn)檫@個(gè)設(shè)置是全局的,而且不能在應(yīng)用程序web.config中被繼承。當(dāng)IIS6運(yùn)行的時(shí)候,進(jìn)程模型設(shè)置大部分被忽略了忽略,取而代之的是從應(yīng)用程序池中讀取。我這里是說“大部分”,對于一些設(shè)置,像進(jìn)程池的大小,IO線程仍舊在里面(配置文件)設(shè)置,因?yàn)閼?yīng)用程序池里面沒有對應(yīng)的設(shè)置。
因?yàn)閼?yīng)用程序池是外部的可執(zhí)行的,而且這些可以很容易的進(jìn)行監(jiān)視和管理。IIS6提供了一些健康檢測,重啟,超時(shí)的選項(xiàng),這些可以檢測,大多數(shù)可以修正應(yīng)用程序的錯(cuò)誤。最后,IIS6的應(yīng)用程序池并不依賴COM+,和IIS5的獨(dú)立進(jìn)程一樣,它改進(jìn)了性能和穩(wěn)定性,尤其是內(nèi)部使用了COM對象的應(yīng)用程序。
盡管IIS6的應(yīng)用程序池區(qū)分于可執(zhí)行文件,他們通過直接的接觸HTTP.SYS核心模塊,而進(jìn)行了高度的HTTP操作優(yōu)化。進(jìn)入的請求,直接的路由到相應(yīng)的應(yīng)用程序池。InetInfo僅僅扮演了一個(gè)管理、設(shè)置服務(wù)-大多說交互實(shí)際發(fā)生在HTTP.SYS和應(yīng)用程序之間,所有的這些構(gòu)成了一個(gè)比IIS5更加穩(wěn)定,更加高性能的環(huán)境。尤其是對于一些靜態(tài)內(nèi)容和ASP.NET應(yīng)用程序。
一個(gè)IIS6的應(yīng)用程序池也有ASP.NET內(nèi)在的認(rèn)識,而且一個(gè)ASP.NET能夠和新的底層的API進(jìn)行交互,這使得ASP.NET直接接觸到HTTP Cache的API,也就使得能夠從ASP.NET層來控制Web服務(wù)器的緩存。
在IIS6,ISAPI擴(kuò)展運(yùn)行在一個(gè)應(yīng)用程序池的工作進(jìn)程中。.NET運(yùn)行時(shí)也運(yùn)行在這個(gè)進(jìn)程,所以.NET運(yùn)行時(shí)和ISAPI 擴(kuò)展的交互是進(jìn)程內(nèi)的,這樣肯定是比一定要使用命名管道接口的IIS5效率更高。盡管IIS兩個(gè)版本的宿主模型是不同的,但是到達(dá)托管代碼之后都是相同的了,只有在做請求路由的時(shí)候有一些不同。
ISAPIRuntime.ProcessRequest() 方法是進(jìn)入ASP.NET的第一個(gè)入口
進(jìn)入到.NET運(yùn)行時(shí)
實(shí)際上進(jìn)入.NET運(yùn)行時(shí),是通過一系列沒有給出文檔說明的類和接口。通過微軟,很少能夠了解這些接口和類,而且微軟并不想談?wù)撨@些細(xì)節(jié),因?yàn)樗麄冋J(rèn)為這些實(shí)現(xiàn)的細(xì)節(jié)幾乎對創(chuàng)建一個(gè)ASP.NET應(yīng)用程序沒有影響。
工作進(jìn)程ASP.NET_WP.EXE (IIS5)和W3WP.EXE (IIS6)宿主.NET運(yùn)行時(shí),并且ISAPI DLL 通過底層的COM調(diào)用了一些少量的非托管接口,而最終調(diào)用到了一個(gè)ISAPIRuntime的子類。第一個(gè)入口就是這個(gè)沒有文檔說明的ISAPIRuntime類,它通過COM將IISAPIRuntime接口暴露給調(diào)用者。這些COM接口,底層的以不了解的接口,意味著從ISAPI擴(kuò)展到ASP.NET的內(nèi)部調(diào)用。圖3顯示了這個(gè)接口,在Lutz Roeder的優(yōu)秀的.NET Reflector 工具(http://www./roeder/dotnet/)。反射程序集視圖和反編譯器,這使得我們能夠非常容易的看到反編譯的代碼(用IL,C#,VB),這是一個(gè)非常好的方法來談就這樣的過程。

圖3 –如果你想深入研究底層的接口,打開Reflector,并且打開 System.Web.Hosting 命名空間。進(jìn)入ASP.NET的入口點(diǎn)通過COM接口而被ISAPI dll調(diào)用,這就獲得了一個(gè)非托管的指針,指向了ISAPI ECB.。ECB包含了訪問ISAPI的全部接口,能夠獲得請求的數(shù)據(jù),也能夠?qū)?shù)據(jù)返回給IIS。
IISAPIRuntime接口在ISAPI擴(kuò)展的非托管代碼和托管代碼之間。如果你看一個(gè)這個(gè)類,你會發(fā)現(xiàn)有一個(gè)簽名如下的方法:
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb,
[In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
ecb這個(gè)參數(shù)是ISAPI擴(kuò)展控制模塊(Extension Control Block –ECB),這個(gè)方法將非托管的資源傳到ProcessRequest方法中。這個(gè)方法獲得ECB并且 通過Request和Response對象將它作為輸入輸出的基礎(chǔ)。一個(gè)ISAPI ECB包含了所有的底層請求信息,包括服務(wù)器信息,一個(gè)來組織變量的輸入流同時(shí)也有一個(gè)用來寫回?cái)?shù)據(jù)給客戶端的輸出流。這個(gè)單獨(dú)的ecb基本提供了所有的ISAPI請求的所有的功能,而ProcessRequest方法是這個(gè)資源和托管代碼交互的出口和入口。
ISAPI擴(kuò)展異步的處理請求。這個(gè)模型中,ISAPI擴(kuò)展立刻回報(bào)給工作進(jìn)程或者IIS線程,而保持當(dāng)前的請求的ECB存活。ECB包括一個(gè)機(jī)制允許ISAPI知道請求已經(jīng)完成(通過ecb.ServerSupportFunction),然后釋放ECB。這個(gè)異步處理立刻釋放ISAPI工作進(jìn)程,并且卸載進(jìn)程,而轉(zhuǎn)到由一個(gè)ASP.NET管理的獨(dú)立的線程。
ASP.NET接收到這個(gè)ecb引用,用它來獲取一些關(guān)于當(dāng)前請求的信息,比如服務(wù)器變量,POST數(shù)據(jù),也包括向服務(wù)器返回輸出數(shù)據(jù)。ECB存活到請求完成,或者IIS超時(shí)并且,ASP.NET會繼續(xù)和其進(jìn)行交互,直至請求完畢。輸出被寫入到ISAPI的輸出流(ecb.WriteClient()),并且,當(dāng)請求完成的時(shí)候,ISAPI擴(kuò)展被通知請求完成,ECB可以被釋放。這個(gè)實(shí)現(xiàn)非常的高效,因?yàn)?NET類完全扮演的是對高性能的非托管代碼簡單的封裝,
加載.NET - 有一些神秘Loading .NET
讓我們回到這里的一個(gè)步驟:我跳過了.NET運(yùn)行時(shí)是怎么加載的。這里的事情有一點(diǎn)模糊,這個(gè)過程,我沒有獲得任何的文檔,并且我們在談本地代碼,而又沒有簡單的方法反編譯ISAPI DLL把它找出來。
我的最好的猜想是,當(dāng)?shù)谝粋€(gè)ASP.NET映射擴(kuò)展名被請求的時(shí)候,ISAPI擴(kuò)展引導(dǎo)了.NET運(yùn)行時(shí)。一旦這個(gè)運(yùn)行時(shí)存在了之后,如果當(dāng)前沒有,非托管代碼可以為給定的虛擬路徑請求一個(gè)ISAPIRuntime的實(shí)例對象。每一個(gè)虛擬目錄都會有它自己的AppDomain(應(yīng)用程序域),在AppDomain里,ISAPIRuntime存在于一個(gè)獨(dú)立的應(yīng)用程序引導(dǎo)的過程。實(shí)例化看起來像發(fā)生在COM里面,因?yàn)榻涌诜椒ㄊ且粋€(gè)COM可調(diào)用的方法。
為了創(chuàng)建ISAPIRuntime實(shí)例,System.Web.Hosting.AppDomainFactory.Create()方法被調(diào)用,當(dāng)某一個(gè)虛擬目錄第一次被請求的時(shí)候。這開始了‘應(yīng)用程序’引導(dǎo)過程。這個(gè)調(diào)用獲得了參數(shù)的類型,模塊的名稱以及虛擬路徑的信息—對于應(yīng)用程序來說,這個(gè)ASP.NET用來創(chuàng)建一個(gè)AppDomain以及運(yùn)行指定虛擬目錄的ASP.NET應(yīng)用程序。這個(gè)HttpRuntime派生對象在一個(gè)新的AppDomain里面被創(chuàng)建。每一個(gè)虛擬目錄都被宿主在一個(gè)獨(dú)立的AppDomain中,僅僅加載指定的應(yīng)用程序的請求。ISAPI擴(kuò)展來管理HttpRuntime對象的實(shí)例,路由相應(yīng)的請求到正確的請求的虛擬路徑上。

圖4 – 從ISAPI請求到ASP.NET的HTTP管道的傳輸過程,使用到了一些沒有文檔說明的類和接口,并且需要一些工廠方法調(diào)用。通過其調(diào)用者把一個(gè)引用放在IISAPIRuntime接口中(這個(gè)接口觸發(fā)了ASP.NET請求處理)每一個(gè)Web應(yīng)用程序運(yùn)行在其自身的AppDomain中.
回到運(yùn)行時(shí)
在這時(shí),我們已經(jīng)有了一個(gè)被ISAPI擴(kuò)展激活,可被其調(diào)用的ISAPIRuntime的實(shí)例。一旦運(yùn)行時(shí)運(yùn)行起來,ISAPI代碼將會調(diào)用到ISAPIRuntime.ProcessRequest()這個(gè)方法,這個(gè)方法是真正的進(jìn)入ASP.NET管道的入口。這個(gè)流程已經(jīng)顯示在圖4中。
記住,ISAPI是一個(gè)多線程的,這樣請求將會以多線程的方式進(jìn)如ASP.NET,通過ApplicationDomainFactory.Create()返回的引用。列表1顯示了IsapiRuntime.ProcessRequest反編譯的結(jié)果,這個(gè)方法接收一個(gè)ISAPI ecb對象,這個(gè)方法是線程安全的,所以,多線程的ISAPI可以同時(shí)的安全的調(diào)用這個(gè)單獨(dú)返回的對象實(shí)例。
Listing 1: 處理請求的方法獲得了一個(gè)ISAPI Ecb并且將其傳入工作進(jìn)程

Code
public int ProcessRequest(IntPtr ecb, int iWRType)
{
HttpWorkerRequest request1 = ISAPIWorkerRequest.CreateWorkerRequest(ecb, iWRType);
string text1 = request1.GetAppPathTranslated();
string text2 = HttpRuntime.AppDomainAppPathInternal;
if (((text2 == null) || text1.Equals(".")) ||
(string.Compare(text1, text2, true, CultureInfo.InvariantCulture) == 0))
{
HttpRuntime.ProcessRequest(request1);
return 0;
}
HttpRuntime.ShutdownAppDomain("Physical application path changed from " +
text2 + " to " + text1);
return 1;
}
這里的代碼并不重要,并且記住,這是你將從來不會直接接觸到的,反編譯出來的框架內(nèi)部的代碼,而且這些代碼可能在以后會改變。這意味著證實(shí)在后臺發(fā)生了什么。ProcessRequest接收到非托管的ECB引用,并且將它傳送到ISAPIWorkerRequest對象中,這個(gè)對象負(fù)責(zé)為當(dāng)前請求創(chuàng)建請球上下文,就像在Listing2中顯示的一樣。
System.Web.Hosting.ISAPIWorkerRequest類是HttpWorkerRequest的一個(gè)抽象子類。它的職責(zé)是為輸入輸出創(chuàng)建一個(gè)抽象的視圖,這個(gè)作為整個(gè)web應(yīng)用程序的輸入。注意另外一個(gè)工廠方法: CreateWorkerRequest,做為第二個(gè)參數(shù),它接收到了要?jiǎng)?chuàng)建的worker request的類型,這里有三種不用的版本:ISAPIWorkerRequestInProc, ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestOutOfProc。這個(gè)對象在每一個(gè)請求進(jìn)來之后被創(chuàng)建,這個(gè)對象是Request和Response對象基礎(chǔ),他們從WorkerRequest里接收他們的數(shù)據(jù)和流。
這個(gè)HttpWorkerRequest抽象類用來為底層的接口提供一個(gè)高層的抽象,所以不管這個(gè)數(shù)據(jù)是來自一個(gè)CGI Web服務(wù)器,一個(gè)瀏覽器,或者一些訂制的機(jī)制。關(guān)鍵的是ASP.NET能夠以同樣的方式來獲得信息。
對于IIS的抽象,以一個(gè)ISAPI ECB模塊。在我們的請求過程中,ISAPIWorkRequst和IISAPI ECB交互,當(dāng)需要的時(shí)候,通過它獲得數(shù)據(jù)。Listing2顯示展示了如何獲得query string 的值。
Listing 2: 一個(gè)使用非托管的ISAPIWorkerRequest 的方法

Code
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;
}
// *** Implemented in a specific implementation class 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);
ISAPIWorkerRequest實(shí)現(xiàn)了一個(gè)高層的封裝的方法,這個(gè)方法調(diào)用底層的用來和訪問底層非托管的API的Core方法。Core 方法在ISAPIWorkerRequest實(shí)例的子類中實(shí)現(xiàn),這樣,為其宿主的環(huán)境提供實(shí)現(xiàn)方法。這樣構(gòu)造了一個(gè)更加容易插拔的環(huán)境,方便添加一些新的服務(wù)器或者服務(wù)ASP.NET的其他平臺的接口的實(shí)現(xiàn)。也有一個(gè)輔助類System.Web.UnsafeNativeMethods。很多的這些方法,他們就是通過操作ISAPI ECB結(jié)構(gòu)體來完成對ISAPI擴(kuò)展的調(diào)用。
HttpRuntime, HttpContext, and HttpApplication
當(dāng)一個(gè)請求到達(dá),被路由到ISAPIRuntime.ProcessRequest()方法。這個(gè)方法繼而調(diào)用HttpRuntime.ProcessRequest,這個(gè)方法做了很多重要的事情(使用Reflector 查看System.Web.HttpRuntime.ProcessRequestInternal):
•為請求創(chuàng)建一個(gè)新的HttpContext實(shí)例
•得到一個(gè)HttpApplication 實(shí)例
•調(diào)用 HttpApplication.Init() 來建立管道事件。
•Init() 觸發(fā) HttpApplication.ResumeProcessing(),這個(gè)方法開始ASP.NET管道處理
首先,一個(gè)新的HttpContext對象被封裝了ISAPI ECB的 ISAPIWorkerRequest創(chuàng)建,傳遞。這個(gè)Context在整個(gè)的請求生命周期中都可用,總是可以通過靜態(tài)的HttpContext.Current 屬性來獲得。就像名字提示的,HttpContext對象表示了當(dāng)前激活的請求的上下文,因?yàn)樗嗽谡埱蟮纳芷谥?,所有的你要接觸到的重要的典型的對象: Request, Response, Application, Server, Cache.在處理請求的任何時(shí)候,HttpContext.Current讓你接觸到所有的這些對象。
HttpContext對象同時(shí)包含了一個(gè)非常有用的集合,允許你用來存儲一些請求指定的數(shù)據(jù)。Context對象從請求周期被創(chuàng)建,當(dāng)請求完成的時(shí)候釋放,所以存儲在這個(gè)集合的數(shù)據(jù)僅僅對應(yīng)當(dāng)前的請求。一個(gè)很好的應(yīng)用的例子是一個(gè)時(shí)間請求記錄的機(jī)制,這里你想記錄請求開始和請求結(jié)束的時(shí)間,通過在Listing3所示,在Global.asax 里面,Application_BeginRequest 和Application_EndRequest 方法。HttpContext是你的朋友,你可以自由的使用,在不同的請求或者頁面處理的部分。
Listing 3 – 使用 HttpContext.Items 結(jié)合來讓你在管道事件之間保存數(shù)據(jù)

Code
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);
}
}
一旦Context對象被創(chuàng)立,ASP.NET需要路由進(jìn)入的請求到相應(yīng)的應(yīng)用程序/虛擬目錄,通過一個(gè)HttpApplication對象。每一個(gè)ASP.NET應(yīng)用程序被需建立一個(gè)虛擬目錄(或者根根目錄)每一個(gè)‘application’獨(dú)立的處理。
HttpApplication像一個(gè)典禮的主人,處理動(dòng)作在這里開始
你的域的主人: HttpApplication
每一個(gè)請求都被路由到一個(gè)HttpApplication對象。HttpApplicationFactory類根據(jù)你的ASP.NET應(yīng)用程序的負(fù)載情況,為其創(chuàng)建一個(gè)HttpApplication對象池,并且為每一個(gè)請求處理這些引用。這個(gè)池的容量受限于設(shè)置在machine.config的ProcessModel鍵里面的MaxWorkerThreads的值,默認(rèn)值是20.
然而這個(gè)池只啟動(dòng)了少量對象,通常是一個(gè)然后同時(shí)進(jìn)入的請求多了,池中對象將會增長。池是被監(jiān)視的,在負(fù)載量大的時(shí)候,將會增長到它的容量的最大值,當(dāng)負(fù)載下降的時(shí)候,池的容量又會降低。
HttpApplication是你指定的Web應(yīng)用程序的外部容器,并且它映射到定義在Global.asax的文件中。他是進(jìn)入HttpRuntime的第一個(gè)點(diǎn),如果你看Global.asax(或者其后置代碼)你會發(fā)現(xiàn),他是派生自HttpApplication的一個(gè)類:public class Global : System.Web.HttpApplication
HttpApplication的主要目的是扮演了Http管道的事件控制者,所以它的接口主要由事件組成。事件是多方面的,包括:
•BeginRequest
•AuthenticateRequest
•AuthorizeRequest
•ResolveRequestCache
•AquireRequestState
•PreRequestHandlerExecute
•Handler Execution
•PostRequestHandlerExecute
•ReleaseRequestState
•UpdateRequestCache
•EndRequest
這些事件都在Global.asax文件中通過以Application_為前綴的空方法實(shí)現(xiàn)。舉個(gè)例子,Application_BeginRequest(), Application_AuthorizeRequest().這樣的處理方法,非常的方便,因?yàn)樵趹?yīng)用程序中,他們經(jīng)常會被用到,這樣你就不用顯示的創(chuàng)建事件處理的委托。
理解每一個(gè)ASP.NET虛擬用應(yīng)程序運(yùn)行在它自己的AppDomain中,然而在這個(gè)AppDomain中,有多個(gè)HttpApplication實(shí)例在同時(shí)運(yùn)行,被一個(gè)ASP.NET的一個(gè)池來進(jìn)行管理。這樣,多個(gè)請求就可以同時(shí)被處理,而且沒有互相的影響。
來看一看AppDomain,線程和HttpApplication的關(guān)系,看看Listing4的代碼。
Listing 4 – 顯示了AppDomain,線程和HttpApplication實(shí)例的關(guān)系

Code
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
this.ApplicationId = ((HowASP.NETWorks.Global)
HttpContext.Current.ApplicationInstance).ApplicationId ;
this.ThreadId = AppDomain.GetCurrentThreadId();
this.DomainId = AppDomain.CurrentDomain.FriendlyName;
this.ThreadInfo = "ThreadPool Thread: " +
System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() +
"<br>Thread Apartment: " +
System.Threading.Thread.CurrentThread.ApartmentState.ToString();
// *** Simulate a slow request so we can see multiple
// requests side by side.
System.Threading.Thread.Sleep(3000);
}
這部分代碼運(yùn)行的結(jié)果在圖5中顯示,在兩個(gè)不同的瀏覽器中來訪問這個(gè)示例頁面,來看這不用的Id

圖 5 –通過同時(shí)用兩個(gè)瀏覽器訪問,你可以看到AppDomain,Application池還有請求進(jìn)程怎樣互相影響,當(dāng)多個(gè)請求到達(dá)的時(shí)候,你會發(fā)現(xiàn)線程,Application的Id都改變了,AppDomain卻保持不變。
你會注意到,AppDomain 的ID保持不變,而HttpApplication的ID在大多數(shù)請求中都變化了,盡管他們有可能會重復(fù)。這些HttpApplication用過了,會在后面的請求中復(fù)用,所以Id是會重復(fù)出現(xiàn)的。注意,Application實(shí)例并不綁定到指定的線程,他們只是被分配到當(dāng)前請求的線程中。
線程來自 .NET的ThreadPool,而且,默認(rèn)的是Multithreaded Apartment (MTA)這種形式的線程。你可以在ASP.NET頁面中重寫這部分,通過在@Page 指令設(shè)置ASPCOMPAT="true" 屬性,ASPCOMPAT意味著提供給COM組建一個(gè)安全的環(huán)境來運(yùn)行,并且,ASPCOMPAT使用特殊的Single Threaded Apartment (STA)線程來服務(wù)這些請求。STA的線程被擱置、合并,因?yàn)樗麄冃枰厥獾奶幚怼?br>
事實(shí)上,HttpApplication對象都在同一個(gè)AppDomain中非常重要。這正是ASP.NET怎樣保證修改了web.config或者獨(dú)立的頁面,可以被整個(gè)的AppDomain所識別。在web.config里面修改值,可以引起AppDomain的關(guān)閉和重啟,這使得所有的HttpApplication都發(fā)現(xiàn)了這個(gè)變化,因?yàn)锳ppDomain重新加載的時(shí)候,他重新讀取了信息。所有的靜態(tài)成員都重新加載了,因?yàn)锳ppDomain重新加載,所以,應(yīng)用程序從應(yīng)用程序配置文件讀取設(shè)置的時(shí)候這些值都被更新了。
來看這個(gè)例子。訪問ApplicationPoolsAndThreads.aspx頁面,并且注意AppDomain的Id。然后修改一下web.config(添加一個(gè)空格并且保存)。然后重新讀取這個(gè)頁面,你會發(fā)現(xiàn)AppDomain已經(jīng)被重新創(chuàng)建了。
本質(zhì)上,當(dāng)這發(fā)生后,web應(yīng)用程序/虛擬目錄完全的‘重啟’了。所有的已經(jīng)在管道中的請求,將會繼續(xù)的通過現(xiàn)在已經(jīng)存在的管道繼續(xù)運(yùn)行,同時(shí),所有的新的請求都會被路由到新的AppDomain。為了處理那些‘掛起的請求’,在這些請求超時(shí)后或者甚至請求還在進(jìn)行的時(shí)候,ASP.NET會強(qiáng)制關(guān)閉舊的AppDomain。這樣,實(shí)際上在某一特定時(shí)間點(diǎn),可以存在兩個(gè)相同的AppDomain,為同一個(gè)HttpApplication服務(wù),舊的AppDomain關(guān)閉,新的AppDomain里面的Application對象將會急劇增加。兩個(gè)AppDomain都繼續(xù)服務(wù),直到舊的一個(gè)將所有的請求都運(yùn)行完畢,舊的將會被關(guān)閉,只留下新的AppDomain。
ASP.NET管道的流程
HttpApplication通過觸發(fā)指示你的應(yīng)用程序狀態(tài)的事件,來負(fù)責(zé)請求的流程。這發(fā)生在HttpApplication.Init()方法中(用Reflector看System.Web.HttpApplication.InitInternal 和 HttpApplication.ResumeSteps() ),這個(gè)方法連續(xù)的創(chuàng)建并觸發(fā)了一系列的事件,包括了調(diào)用執(zhí)行所有的handlers.事件處理自動(dòng)的映射在global.asax里面的事件,并且,他們也映射所有已經(jīng)附加了的HTTPModule,本質(zhì)上,HTTPModule是一個(gè)形象化了事件槽。
HttpModules 和 HttpHandlers a都通過web.config里面條目而被動(dòng)態(tài)加載,并且將其綁定到事件鏈。HttpModules是實(shí)際的HttpApplication的事件處理者,而HttpHandlers是一個(gè)終點(diǎn),用來處理‘應(yīng)用程序級的請求處理’的。HttpModules 實(shí)際是HttpApplication的事件處理器。
Modules 和 Handlers的加載,附加到調(diào)用鏈都做為HttpApplication.Init()方法的一部分、圖6顯示了各種事件以及他們出發(fā)的時(shí)間和觸發(fā)影響的部分。

圖 6 – ASP.NET HTTP管道的事件流程。HttpApplication對象的事件驅(qū)動(dòng)貫穿管道。 Http Modules能夠攔截這些事件,進(jìn)而重寫或者增強(qiáng)已有的功能。
HttpContext, HttpModules 和 HttpHandlers
HttpApplication自身并不知道傳送進(jìn)來的數(shù)據(jù),他僅僅是一個(gè)通信對象,通過事件來進(jìn)行交互。他觸發(fā)事件,并且將信息通過HttpContext對象傳遞到被調(diào)用的方法中。當(dāng)前請求的狀態(tài)數(shù)據(jù)存儲在我們前面提到的Httpcontext對象。它提供了所有請求的數(shù)據(jù),并且在管道中,伴隨著每一個(gè)請求從開始到結(jié)束。圖7顯示了通過ASP.NET管道的流程,注意Context對象從開始到請求的結(jié)束,都可以用來存儲信息,在一個(gè)事件方法中存貯信息,在后面的事件方法中獲得這個(gè)數(shù)據(jù)。
一旦管道開始,HttpApplication如圖6一樣,開始一個(gè)接著一個(gè)的觸發(fā)事件。每一個(gè)事件處理器被調(diào)用,如果事件被調(diào)用這些處理器執(zhí)行他們的任務(wù)。這個(gè)過程的主要目的是最終調(diào)用HttpHandler處理一個(gè)請求。Handlers是處理ASP.NET請求的核心的機(jī)制,經(jīng)常位于應(yīng)用程序級代碼的執(zhí)行。記住,ASP.NET 頁面和WebService框架,都是作為HTTPHandler的實(shí)現(xiàn),在這里請求的核心處理過程被執(zhí)行。Modules往往是更核心的性質(zhì),用來準(zhǔn)備或者發(fā)送處理交付于Handlers的Context。ASP.NET中,典型的默認(rèn)的Handlers是Authentication, pre-processing的Caching 和 發(fā)送處理請求的編碼機(jī)制。
有很多的信息在HttpHandlers 和 HttpModules中,但是為了保持這篇文章的一個(gè)合理的長度,我下面只是簡短介紹一下handlers。
HttpModules
隨著請求通過管道,一系列的事件在HttpApplication對象中被觸發(fā)。我們已經(jīng)在Global.asax中看到了這些事件。這種方法是應(yīng)用程序指定的,這樣,就不一定總是你想要的。如果你想創(chuàng)建一個(gè)廣義的HttpApplication事件處理,而且能夠可插拔的放到任何一個(gè)應(yīng)用程序中,你可以使用HttpModules,他們是可以復(fù)用的,而且,不需要程序代碼指定,只需要在web.config里面設(shè)置一下。
Modules本質(zhì)上像篩選器,就像一個(gè)在ASP.NET請求層的ISAPI Filter。Modules允許附加事件到每一個(gè)通過ASP.NET HttpApplication對象的請求。這些Module在外部的程序集的類里面,在web.config里面設(shè)置,并且當(dāng)應(yīng)用程序啟動(dòng)的時(shí)候,隨著應(yīng)用程序的啟動(dòng)而加載。通過實(shí)現(xiàn)指定的接口和方法,將事件添加到HttpApplication事件鏈中。多個(gè)HttpModules能夠附加事件處理代碼到相同的事件上,這些附加的事件處理的順序根據(jù)在web.config里面設(shè)置的一樣:
<configuration>
<system.web>
<httpModules>
<add name= "BasicAuthModule"
type="HttpHandlers.BasicAuth,WebStore" />
</httpModules>
</system.web>
</configuration>
注意,你需要指定一個(gè)類型全名,還有一個(gè)程序集的名字.
Modules允許你看到每一個(gè)請求并且基于觸發(fā)事件形式的執(zhí)行動(dòng)作。Module非常適于修改request或者response的內(nèi)容,用以提供定制的身份驗(yàn)證或者為每一個(gè)請求執(zhí)行預(yù)處理。很多的ASP.NET的特性,像身份驗(yàn)證以及Sesion引擎都是通過HTTPModule來實(shí)現(xiàn)的。
雖然,HTTPModule感覺上像ISAPI Filter,他們可以查看每一個(gè)通過ASP.NET應(yīng)用程序的請求,但是他們只是能夠監(jiān)視映射到ASP.NET應(yīng)程序或者ASP.NET虛擬目錄的請求。這樣,你可以查看ASPX文件,或者其他的映射到ASP.NET的擴(kuò)展名。但是你不能監(jiān)視一個(gè)標(biāo)準(zhǔn)的.HTM或者圖片文件,除非你把他們的擴(kuò)展名顯式的映射到ASP.NET ISAPI dll,就像圖1顯式的。一個(gè)Module的經(jīng)常被用作,過濾指定文件夾下面的圖片,然后顯式一個(gè)‘SAMPLE’覆蓋在每一個(gè)圖片上面,通過使用GDI+。(譯者:水?。?
實(shí)現(xiàn)一個(gè)HTTPModule非常的容易:你必須實(shí)現(xiàn)IHttpModule接口,這個(gè)接口僅僅有兩個(gè)方法,Init()和Dispose().這個(gè)時(shí)間參數(shù)是一個(gè)HttpApplication對象,通過他你可以訪問Httpcontext對象,在這個(gè)方法中你可以接觸到HttpApplication的事件。舉個(gè)例子,如果你想附加AuthenticateRequest事件你可以像Listing5一樣。
Listing 5: 一個(gè)基礎(chǔ)的HTTP Module非常好實(shí)現(xiàn)

Code
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… }
}
記住,你的Module訪問的是HttpContext對象,通過其就能獲得其他的管道對象,就如Response和Request,這樣你可以獲得輸入等等,但是記住,這些不一定在后面的事件鏈中還有效。
你可以附件多個(gè)事件在Init()方法中,這樣你就可以通過一個(gè)Module來管理不同的功能操作。然而,最好將不同的邏輯放在不同的類中使得Module真正的模塊化。在你要實(shí)現(xiàn)的很多功能中,你需要附加到多個(gè)事件-舉個(gè)例子,一個(gè)日志filter需要早BeginRequest中記錄Request的開始時(shí)間,而在EndRequest中記錄請求的完成時(shí)間。
注意HttpModules 和 HttpApplication 的事件events: Response.End() 或者 HttpApplication.CompleteRequest()將會切斷HttpApplication對象或者M(jìn)odule的事件鏈。See the sidebar “Watch out for Response.End() “ for more info.
HttpHandlers
Modules相當(dāng)?shù)牡讓佣?,對?yīng)的是對應(yīng)ASP.NET應(yīng)用程序的每一個(gè)請求。HTTP Handler則更加側(cè)重于對于一個(gè)指定的請求的操作,通常一個(gè)頁面都被映射到Handler.
實(shí)現(xiàn)Http Handler要求非常基礎(chǔ),但是通過訪問HttpContext對象可以獲得強(qiáng)大的功能。Http Handler通過實(shí)現(xiàn)一個(gè)非常簡單的接口IHttpHandler來實(shí)現(xiàn)(或者其異步的版本的IHttpAsyncHandler),它僅僅包含了一的方法ProcessRequest()和一個(gè)IsReusagable屬性。關(guān)鍵的是,ProcessRequest()獲得了一個(gè)HttpContext對象的實(shí)例,這個(gè)方法負(fù)責(zé)處理Web請求,從開始到結(jié)束。
一個(gè)簡單的方法?非常的簡單,對么?確實(shí),一個(gè)簡單的接口,但是卻不是那么的簡單!記得WebForm和WebService都是做為HTTPHandler的實(shí)現(xiàn)的,所以,很強(qiáng)大的功能封裝在這一個(gè)看似簡單的接口中。關(guān)鍵點(diǎn)是,接觸到HTTP Handler的時(shí)候,ASP.NET的內(nèi)置對象已經(jīng)為開始處理請求而創(chuàng)建和設(shè)置好了。關(guān)鍵就是HttpContext對象,他提供了所有的請求相關(guān)的功能獲得輸入信息,輸出信息到Web服務(wù)器。
一個(gè)HTTPHandler所有的動(dòng)作發(fā)生都是通過調(diào)用這個(gè)單獨(dú)的ProcessRequest().這個(gè)可以簡單的像:
public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello World");
}
也可以完全實(shí)現(xiàn)一個(gè)象WebForm Page引擎,可以輸出復(fù)雜格式HTML模板。這點(diǎn)完全取決與你的決定,你到底如何用這個(gè)簡單,卻有強(qiáng)大的接口!
因?yàn)槟憧梢允褂肅ontext對象,你可以獲得Request,Response,Session和Cache對象,這樣你有了所有的ASP.NET請求的特性, 你可以找到用戶提交的內(nèi)容,也可以設(shè)置返回客戶端的內(nèi)容。記住Context對象,他是你的朋友,在這個(gè)ASP.NET請求的生命周期中!
Handler的關(guān)鍵性的操作應(yīng)帶是最終的把output輸出結(jié)果到Response對象,或者更具體的說是Response對象的OutputStream。這個(gè)output返回客戶端的信息。在背后,ISAPIWorkerRequest負(fù)責(zé)將OutputStream返回到ISAPI ecb.WriteClient方法,執(zhí)行了IIS輸出的過程。

圖 7 – ASP.NET請求管道流程通過一系列事件接口,提供了很大的靈活性。Application扮演了一個(gè)宿主容器的角色,它加載了Web應(yīng)用程序,并且隨著請求的進(jìn)入和在管道中的傳遞而觸發(fā)事件。每一個(gè)請求都是通過相同的路徑,通過HTTP Filters和設(shè)置了的Modules。Filters能夠檢測通過管道的每一個(gè)請求,而Handlers允許實(shí)現(xiàn)用用程序的邏輯和接口,就像WebForm和WebService。為了提供應(yīng)用程序的輸入和輸出Context在整個(gè)過程提供了請求的相關(guān)信息。
WebForm在這個(gè)基礎(chǔ)框架上面,通過實(shí)現(xiàn)一個(gè)HTTPHandler以及更高層的接口,然而最終,Wenforms的Render()方法簡單的使用一個(gè)HtmlTextWriter對象將其最終的輸出結(jié)果寫入到context.Response.OutputStream。這樣,最終,一個(gè)高層的工具,像WebForms僅僅是一個(gè)Request和Response對象的高層的抽象。
你可能想知道,這點(diǎn)上,你是否需要處理HTTPHandler。畢竟,WebForm提供了簡單的可訪問的HTTPHandler實(shí)現(xiàn),那么我們?yōu)槭裁匆艞夁@個(gè)靈活性而不厭其煩的做一些底層的事情呢?
WebForm對于生成復(fù)雜的HTML頁面和需要圖形布局工具,模板化頁面的商業(yè)邏輯非常的好。但是,WebForm執(zhí)行了很多的增加消耗的任務(wù)。如果你僅僅想在系統(tǒng)中讀取一個(gè)文件,并將其返回,那么你可以跳過Web Form page框架,直接處理文件。如果你做的事情像從數(shù)據(jù)庫提供圖片,你也不需要Page框架—你不需要模板,而且沒有一個(gè)UI。沒有理由創(chuàng)建一個(gè)頁面對象和Seesion并且處理頁面級別的事件.
所以handlers更加高效。Handler也可以完成WebForm不能完成的任務(wù)。例如,他能夠處理一個(gè)請求,不需要磁盤上有物理文件。 做這個(gè),你需要在圖1中的應(yīng)用程序擴(kuò)展對話框中。
關(guān)閉“檢查文件是否存在”選項(xiàng)。
對于內(nèi)容提供者是通用的,就像動(dòng)態(tài)圖片處理,XML服務(wù)器,Url重定向提供構(gòu)造的Url,下載管理等等,這些都不是適合Wenform引擎。
對你來說,我介紹的足夠了么?
恩,我們這里已經(jīng)介紹了處理整個(gè)請求的過程。有很多的底層信息,我沒有仔細(xì)的講HTTPHandler和HTTPModule具體工作細(xì)節(jié)。挖掘這些信息需要一些時(shí)間,在理解ASP.NET怎樣工作上面,希望能給你和我自己一樣的滿意程度。
在結(jié)束之前,讓我們簡短的回顧一下從IIS到handler的事件序列:
•IIS獲得請求
•檢測腳本映射,映射到ASP.NET_isapi.dll
•觸發(fā)工作進(jìn)程(ASP.NET_wp.exe 在 IIS5 或者 w3wp.exe 在 IIS6)
•.NET運(yùn)行時(shí)加載
•IsapiRuntime.ProcessRequest()通過非托管代碼調(diào)用
•IsapiWorkerRequest created once per request
•IsapiWorkerRequest 每一次請求創(chuàng)建一次
•HttpRuntime.ProcessRequest() called with Worker Request
•通過傳進(jìn)Work Request, HttpContext對象被創(chuàng)建
•HttpApplication.GetApplicationInstance() called with Context to retrieve instance from pool
•HttpApplication.Init() 調(diào)用,并且啟動(dòng)管道事件序列,附加Modules和Handler
•被調(diào)用,開始處理進(jìn)請求
•管道事件觸發(fā)
•Handlers被調(diào)用,并且ProcessRequest 方法執(zhí)行
•控件返回管道并且發(fā)送請求事件觸發(fā)
通過這個(gè)簡單的列表,把這些是如何組合起來的記住會更容易。我不時(shí)的來看它來記憶?,F(xiàn)在,我們回到工作上,繼續(xù)做一些不抽象的…
盡管,這里我說的是基于ASP.NET1.1,但是ASP.NET2.0中,并沒有改變這些底層的處理過程。
非常感謝微軟的Mike Volodarsky來審閱這篇文章,并且提了一些附件的提示并且 Michele Leroux Bustamante提供了ASP.NET管道請求基礎(chǔ)信息。