(翻譯)從底層瞭解ASP.NET體系結構

前言

關於ASP.NET的底層的工作機制,最近園子裏討論的甚是火熱。相信很多人都看過Rick Strahl先生的一篇經典之作:A low-level Look at the ASP.NET Architecture,經Rick Strahl先生同意,我把他的這篇文章翻譯成中文,希望能夠給想深入瞭解ASP.NET工作機制的朋友一點幫助。 

摘要: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的版本。

 

IIS5IIS6的不同之處

 

當一個請求進來的時候,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)
{
  
  // ISAPIWorkerRequestHttpWorkerRequest 繼承,這裏創建的是             

//  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擴展的非託管操作。

 

HttpRuntimeHttpContext以及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就是一個端點,它可以被調用處理“應用程序級的請求處理”。

 

HTTPModulesHttpHandlers將被加載,然後添加到調用鏈上作爲HttpApplication.Init()方法調用的一部分。圖6展示了不同的事件,這些事件何時被觸發以及通道上的哪些部分會受到它們的影響。


event_flow.jpg
6:事件在ASP.NET HTTP管道里傳輸。HttpApplication對象的事件驅動請求在管道里傳輸。HTTPModules可以截獲這些事件,可以覆蓋或增強已存在的功能。

 

HttpContextHttpModulesHttpHandlers

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管道請求流程的幻燈片提供了依據。


特別說明:翻譯此文的目的僅僅是爲了給廣大的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.  是否已經提供了足夠的底層知識?

 
發佈了116 篇原創文章 · 獲贊 2 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章