ASP.NET底層架構

進入底層
這篇文章以非常底層的視角講述了Web請求(request)在ASP.NET框架中是如何流轉的,從Web服務器,通過ISAPI直到請求處理器(handler)和你的代碼.看看在幕後都發生了些什麼,不要再把ASP.NET看成一個黑盒了.
 
ASP.NET是一個非常強大的構建Web應用的平臺,它提供了極大的靈活性和能力以致於可以用它來構建所有類型的Web應用.絕大多數的人只熟悉高層的框架如WebFormsWebServices-這些都在ASP.NET層次結構在最高層.在這篇文章中我將會討論ASP.NET的底層機制並解釋請求(request)是怎麼從Web服務器傳送到ASP.NET運行時然後如何通過ASP.NET管道來處理請求.
 
對我而言瞭解平臺的內幕通常會帶來滿足感和舒適感,深入瞭解也能幫助我寫出更好的應用.知道可以使用哪些工具以及他們是怎樣作爲整個複雜框架的一部分來互相配合的可以更容易地找出最好的解決方案,更重要的是可以在出現問題時更好的解決它們.這篇文章的目標是從系統級別了解ASP.NET並幫助理解請求(request)是如何在ASP.NET的處理管道中流轉的.同樣,我們會了解核心引擎和Web請求如何在那裏結束.這些信息大部分並不是你在日常工作時必須瞭解的,但是它對於理解ASP.NET架構如何把請求路由到你的代碼(通常是非常高層的)中是非常有益的.
 
不管怎麼樣,ASP.NET從更低的層次上提供了更多的靈活性.HTTP運行時和請求管道在構建WebFormsWebServices上提供了同樣的能力-它們事實上都是建立在.NET託管代碼上的.而且所有這些同樣的功能對你也是可用的,你可用決定你是否需要建立一個比WebForms稍低一點層次的定製的平臺.
 
WebForms顯然是最簡單的構建絕大多數Web接口的方法,不過如果你是在建立自定義的內容處理器(handler),或者有在處理輸入輸出內容上有特殊的要求,或者你需要爲另外的應用建立一個定製的應用程序服務接口,使用這些更低級的處理器(handler)或者模塊(module)能提供更好的性能並能對實際請求處理提供更多的控制.WebFormsWebServices這些高層實現提供它們那些能力的同時,它們也對請求增加了一些額外負擔,這些都是在更底層可以避免的.
 
 
ASP.NET是什麼

讓我們以一個簡單的定義開始:什麼是ASP.NET?我喜歡這樣定義ASP.NET:
          
ASP.NET是一個複雜的使用託管代碼來從頭到尾處理Web請求的引擎.
它並不只是WebFormsWebServies…   

ASP.NET是一個請求處理引擎.它接收一個發送過來的請求,把它傳給內部的管道直到終點,作爲一個開發人員的你可以在這裏附加一些代碼來處理請求.這個引擎是和HTTP/Web服務器完全分隔的.事實上,HTTP運行時是一個組件,使你可以擺脫IIS或者任何其他的服務器程序,將你自己的程序寄宿在內.例如,你可以將ASP.NET運行時寄宿在一個Windows form程序中(查看http://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.asp可以得到更加詳細的信息)
 
運行時提供了一個複雜但同時非常優雅的在管道中路由請求的機制.其中有很多相關的對象,大多數都是可擴展的(通過繼承或者事件接口),在幾乎所有的處理流程上都是如此.所以這個框架具有高度可擴展性.通過這個機制,掛接到非常底層的接口(比如緩存,認證和授權)都變得可能了.你甚至可以在預處理或者處理後過濾內容,也可以簡單的將符合特殊標記的請求直接路由你的代碼或者另一個URL.存在着許多不同的方法來完成同一件事,但是所有這些方法都是可以簡單直接地實現的,同時還提供了靈活性,可以得到最好的性能和開發的簡單性.
 
整個ASP.NET引擎是完全建立在託管代碼上的,所有的擴展功能也是通過託管代碼擴展來提供的
 
整個ASP.NET引擎是完全建立在託管代碼上的,所有的擴展功能也是通過託管代碼擴展來提供的.這是對.NET框架具有構建複雜而且高效的框架的能力的最好的證明.ASP.NET最令人印象深刻的地方是深思熟慮的設計,使得框架非常的容易使用,又能提供掛接到請求處理的幾乎所有部分的能力.
 
通過ASP.NET你可以從事從前屬於ISAPI擴展和IIS過濾器領域的任務-有一些限制,但是比起ASP來說是好多了.ISAPI是一個底層的Win32風格的API,有着非常粗劣的接口而且難以用來開發複雜的程序.因爲ISAPI非常底層,所以它非常的快,但是對於應用級的開發者來說是十分難以管理的.所以,ISAPI通常用來提供橋接的接口,來對其他應用或者平臺進行轉交.但是這並不意味者ISAPI將消亡.事實上,ASP.NET在微軟的平臺上就是通過ISAPI擴展來和IIS進行交互的,這個擴展寄宿着.NET運行時和ASP.NET運行時.ISAPI提供了核心的接口,ASP.NET使用非託管的ISAPI代碼通過這個接口來從Web服務器獲取請求,併發送響應回客戶端.ISAPI提供的內容可以通過通用對象(例如HttpRequestHttpResponse)來獲取,這些對象通過一個定義良好並有很好訪問性的接口來暴露非託管數據.
 
 
從瀏覽器到ASP.NET
 
讓我們從一個典型的ASP.NET Web請求的生命週期的起點開始.當用戶輸入一個URL,點擊了一個超鏈接或者提交了一個HTML表單(form)(一個POST請求,相對於前兩者在一般意義上都是GET請求).或者一個客戶端程序可能調用了一個基於ASP.NETWebService(同樣由ASP.NET來處理).Web服務器端,IIS56,獲得這個請求.在最底層,ASP.NETIIS通過ISAPI擴展進行交互.ASP.NET環境中這個請求通常被路由到一個擴展名爲.aspx的頁面上,但是這個流程是怎麼工作的完全依賴於處理特定擴展名的HTTP Handler是怎麼實現的.IIS.aspx通過應用程序擴展’(又稱爲腳本映射)被映射到ASP.NETISAPI擴展DLL-aspnet_isapi.dll.每一個請求都需要通過一個被註冊到aspnet_isapi.dll的擴展名來觸發ASP.NET(來處理這個請求).
 
依賴於擴展名ASP.NET將請求路由到一個合適的處理器(handler),這個處理器負責獲取這個請求.例如,WebService.asmx擴展名不會將請求路由到磁盤上的一個頁面,而是一個由特殊屬性(Attribute)標記爲WebService的類上.許多其他處理器和ASP.NET一起被安裝,當然你也可以自定義處理器.所有這些HttpHandlerIIS中被配置爲指向ASP.NET ISAPI擴展,並在web.config(譯著:ASP.NET中自帶的handler是在machine.config中配置的,當然可以在web.config中覆蓋配置)被配置來將請求路由到指定的HTTP Handler.每個handler都是一個處理特殊擴展的.NET,可以從一個簡單的只包含幾行代碼的Hello World,到非常複雜的handlerASP.NET的頁面或者WebServicehandler.當前,只要瞭解ASP.NET的映射機制是使用擴展名來從ISAPI接收請求並將其路由到處理這個請求的handler上就可以了.
 
對在IIS中自定義Web請求處理來說,ISAPI是第一個也是最高效的入口
 
ISAPI連接
 
ISAPI是底層的非託管Win32 API.ISAPI定義的接口非常簡單並且是爲性能做了優化的.它們是非常底層的-處理指針和函數指針表來進行回調-但是它們提供了最底層和麪向效率的接口,使開發者和工具提供商可以用它來掛接到IIS.因爲ISAPI非常底層所以它並不適合來開發應用級的代碼,而且ISAPI傾向於主要被用於橋接接口,向上層工具提供應用服務器類型的功能.例如,ASPASP.NET都是建立在ISAPI上的,Cold Fusion,運行在IIS上的多數Perl,PHP以及JSP實現,很多第三方解決方案(如我的Wisual FoxProWeb連接框架)都是如此.ISAPI是一個傑出的工具,可以爲上層應用提供高效的管道接口,這樣上層應用可以抽象出ISAPI提供的信息.ASPASP.NET,ISAPI接口提供的信息抽象成了類型RequestResponse這樣的對象,通過它們來讀取ISAPI請求中對應的信息.ISAPI想像成管道.ASP.NET來說,ISAPI dll是非常的,只是作爲一個路由機制來將原始的請求轉發到ASP.NET運行時.所有那些沉重的負擔和處理,甚至請求線程的管理都發生在ASP.NET引擎內部和你的代碼中.
 
作爲協議,ISAPI同時支持ISAPI擴展和ISAPI過濾器(Filter).擴展是一個請求處理接口,提供了處理Web服務器的輸入輸出的邏輯-它本質上是一個處理(事物?)接口.ASPASP.NET都被實現爲ISAPI擴展.ISAPI過濾器是掛接接口,提供了查看進入IIS的每一個請求的能力,並能修改請求的內容或者改變功能型的行爲,例如認證等.順便提一下,ASP.NET通過了兩種概念映射了類似ISAPI的功能:Http Handler類似擴展,Http Module類似過濾器.我們將在後面詳細討論它們.
 
ISAPI是開始一個ASP.NET請求的最初的入口.ASP.NET映射了好幾個擴展名到它的ISAPI擴展,此擴展位於.NET框架的目錄下:
 
<.NET FrameworkDir>/aspnet_isapi.dll
 
你可以在IIS服務管理界面上看到這些映射,如圖1.查看網站根目錄的屬性中的主目錄配置頁,然後查看配置|映射.
 
1:IIS映射了多種擴展名如.ASPXASP.NETISAPI擴展.通過這個機制請求會在Web服務器這一層被路由到ASP.NET的處理管道.
 
由於.NET需要它們中的一部分,你不應該設置手動這些擴展名.使用aspnet_regiis.exe這個工具來確保所有的映射都被正確的設置了:
 
cd <.NetFrameworkDirectory>
aspnet_regiis – i
 
這個命令將爲整個Web站點註冊特定版本的ASP.NET運行時,包括腳本 (擴展名) 映射和客戶端腳本庫(包括進行控件驗證的代碼等).注意它註冊的是<.NetFrameworkDirectory>中安裝的特定版本的CLR(1.1,2.0).aspnet_regiis的選項令您可以對不同的虛擬目錄進行配置.每個版本的.NET框架都有自己不同版本的aspnet_regiis工具,你需要運行對應版本的aspnet_regiis來爲web站點或者虛擬目錄來配置指定版本的.NET框架.ASP.NET2.0開始提供了ASP.NET配置頁面,可以通過這個頁面在IIS管理控制檯來交互的配置.NET版本.
 
IIS6通配符應用程序映射
如果你有一個ASP.NET應用程序需要處理虛擬目錄的(或者是整個Web站點,如果配置爲根目錄的話)每一個請求,IIS6引入了新的稱爲通配符應用程序映射的概念.一個映射到通配符的ISAPI擴展在每個請求到來時都會被觸發,而不管擴增名是什麼.這意味着每個頁面都會通過這個擴展來處理.這是一個強大的功能,你可以用這個機制來創建虛擬Url和不使用文件名的unix風格的URL.然而,使用這個設置的時候要注意,因爲它會把所有的東西都傳給你的應用,包括靜態htm文件,圖片,樣式表等等.
 
 
IIS 5 6以不同的方式工作
 
當一個請求來到時,IIS檢查腳本映射(擴展名映射)然後把請求路由到aspnet_isapi.dll.這個DLL的操作和請求如何進入ASP.NET運行時在IIS56中是不同的.2顯示了這個流程的一個粗略概覽.
 
IIS5,aspnet_isapi.dll直接寄宿在inetinfo.exe進程中,如果你設置了Web站點或虛擬目錄的隔離度爲中或高,則會寄宿在IIS單獨的(被隔離的)工作進程中.當第一個ASP.NET請求來到,DLL(aspnet_isapi.dll)會開始另一個新進程aspnet_wp.exe並將請求路由到這個進程中來進行處理.這個進程依次加載並寄宿.NET運行時.每個轉發到ISAPI DLL的請求都會通過命名管道調用被路由到這個進程來.
 
2-從較高層次來看請求從IISASP.NET運行時,並通過請求處理管道的流程.IIS5IIS6通過不同的方式與ASP.NET交互,但是一旦請求來到ASP.NET管道,整個處理流程就是一樣的了.
 
不同於以前版本的服務器,IIS6ASP.NET做了全面的優化
 
 
IIS6-應用程序池萬歲
 
IIS6對處理模型做了意義重大的改變,IIS不再直接寄宿象ISAPI擴展這樣的外部可執行代碼.IIS總是創建一個獨立的工作線程-一個應用程序池-所有的處理都發生在這個進程中,包括ISAPI dll的執行.應用程序池是IIS6的一個很大的改進,因爲它允許對指定線程中將會執行什麼代碼進行非常細粒度的控制.應用程序池可以在每個虛擬路徑上或者整個Web站點上進行配置,這樣你可以將每個Web應用隔離到它們自己的進程中,這樣每個應用都將和其他運行在同一臺機器上的Web應用完全隔離.如果一個進程崩潰了,不會影響到其他進程(至少在Web處理的觀點上來看是如此).
 
不止如此,應用程序池還是高度可配置的.你可以通過設置池的執行扮演級別(execution impersonation level )來配置它們的運行安全環境,這使你可以定製賦予一個Web應用的權限(同樣,粒度非常的細).對於ASP.NET的一個大的改進是,應用程序池覆蓋了在machine.config文件中大部分的ProcessModel節的設置.這一節的設置在IIS5中非常的難以管理,因爲這些設置是全局的而且不能在應用程序的web.config文件中被覆蓋.當運行IIS6,ProcessModel相關的設置大部分都被忽略了,取而代之的是從應用程序池中讀取.注意這裏說的是大部分-有些設置,如線程池的大小還有IO線程的設置還是從machine.config中讀取,因爲它們在線程池的設置中沒有對應項.
 
因爲應用程序池是外部的可執行程序,這些可執行程序可以很容易的被監控和管理.IIS6提供了一系列的進行系統狀況檢查,重啓和超時的選項,可以很方便的用來檢查甚至在許多情況下可以修正程序的問題.最後IIS6的應用程序池並不像IIS5的隔離模式那樣依賴於COM+,這樣做一來可以提高性能,二來提高了穩定性(特別對某些內部需要調用COM組件的應用來說)
 
儘管IIS6的應用程序池是單獨的EXE,但是它們對HTTP操作進行了高度的優化,它們直接和內核模式下的HTTP.SYS驅動程序進行通訊.收到的請求被直接路由給適當的應用程序池.InetInfo基本上只是一個管理程序和一個配置服務程序-大部分的交互實際上是直接在HTTP.SYS和應用程序池之間發生,所有這些使IIS6成爲了比IIS5更加的穩定和高效的環境.特別對靜態內容和ASP.NET程序來說這是千真萬確的.
 
一個IIS6應用程序池對於ASP.NET有着天生的認識,ASP.NET可以在底層的API上和它進行交互,這允許直接訪問HTTP緩存API,這樣做可以將ASP.NET級別的緩存直接下發到Web服務器.
 
IIS6,ISAPI擴展在應用程序池的工作進程中運行. .NET運行時也在同一個進程中運行,所以ISAPI擴展和.NET運行時的通訊是發生在進程內的,這樣做相比IIS5使用的命名管道有着天生的性能優勢.雖然IIS的寄宿模型有着非常大的區別,進入托管代碼的接口卻異常的相似-只有路由消息的過程有一點區別.
 
ISAPIRuntime.ProcessRequest()函數是進入ASP.NET的第一站
 
進入.NET運行時
 
進入.NET運行時的真正的入口發生在一些沒有被文檔記載的類和接口中(譯著:當然,你可以用Reflector來查看J).除了微軟,很少人知道這些接口,微軟的傢伙們也並不熱衷於談論這些細節,他們認爲這些實現細節對於使用ASP.NET開發應用的開發人員並沒有什麼用處.
工作進程(IIS5中是ASPNET_WP.EXE,IIS6中是W3WP.EXE)寄宿.NET運行時和ISAPI DLL,(工作進程)通過調用COM對象的一個小的非託管接口最終將調用發送到ISAPIRuntime類的一個實例上(譯註:原文爲an instance subclass of the ISAPIRuntime class,但是ISAPIRuntime類是一個sealed,疑爲作者筆誤,或者這裏的subclass並不是子類的意思).進入運行時的第一個入口就是這個沒有被文檔記載的類,這個類實現了IISAPIRuntime接口(對於調用者說明來說,這個接口是一個COM接口)這個基於Iunknown的底層COM接口是從ISAPI擴展到ASP.NET的一個預定的接口.3展示了IISAPIRuntime接口和它的調用簽名.(使用了Lutz Roeder出色的.NET Reflector 工具http://www.aisto.com/roeder/dotnet/).這是一個探索這個步步爲營過程的很好的方法.


3-如果你想深入這個接口,打開Reflector,指向System.Web.Hosting命名空間. ISAPI DLL通過調用一個託管的COM接口來打開進入ASP.NET的入口,ASP.NET接收一個指向ISAPI ECB的非託管指針.這個ECB包含訪問完整的ISAPI接口的能力,用來接收請求和發送響應回到IIS.
 
IISAPIRuntime接口作爲從ISAPI擴展來的非託管代碼和ASP.NET之間的接口點(IIS6中直接相接,IIS5中通過命名管道).如果你看一下這個類的內部,你會找到含有以下簽名的ProcessRequest函數:
 
 [return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb, 
                   [In, MarshalAs(UnmanagedType.I4)]
int useProcessModel);
 
其中的ecb參數就是ISAPI擴展控制塊(Extention Control Block),被當作一個非託管資源傳遞給ProcessRequest函數.這個函數接過ECB後就把它做爲基本的輸入輸出接口,RequestResponse對象一起使用.ISAPI ECB包含有所有底層的請求信息,如服務器變量,用於表單(form)變量的輸入流和用於回寫數據到客戶端的輸出流.這一個ecb引用基本上提供了用來訪問ISAPI請求所能訪問的資源的全部功能,ProcessRequest是這個資源(ecb)最初接觸到託管代碼的入口和出口.
 
ISAPI擴展異步地處理請求.在這個模式下ISAPI擴展馬上將調用返回到工作進程或者IIS線程上,但是在當前請求的生命週期上ECB會保持可用.ECB含有使ISAPI知道請求已經被處理完的機制(通過ecb.ServerSupportFunction方法)(譯註:更多信息,可以參考開發ISAPI擴展的文章),這使得ECB被釋放.這個異步的處理方法可以馬上釋放ISAPI工作線程,並將處理傳遞到由ASP.NET管理的一個單獨的線程上.
 
ASP.NET接收到ecb引用並在內部使用它來接收當前請求的信息,如服務器變量,POST的數據,同樣它也返回信息給服務器.ecb在請求完成前或超時時間到之前都保持可訪問(stay alive),這樣ASP.NET就可以繼續和它通訊直到請求處理完成.輸出被寫入ISAPI輸出流(使用ecb.WriteClient())然後請求就完成了,ISAPI擴展得到請求處理完成的通知並釋放ECB.這個實現是非常高效的,因爲.NET類本質上只是對高效的、非託管的ISAPI ECB的一個非常”(thin)的包裝器.
 
裝載.NET-有點神祕
 
讓我們從這兒往回退一步:我跳過了.NET運行時是怎麼被載入的.這是事情變得有一點模糊的地方.我沒有在這個過程中找到任何的文檔,而且因爲我們在討論本機代碼,沒有很好的辦法來反編譯ISAPI DLL並找出它(裝載.NET運行時的代碼).
 
我能作出的最好的猜測是當ISAPI擴展接受到第一個映射到ASP.NET的擴展名的請求時,工作進程裝載了.NET運行時.一旦運行時存在,非託管代碼就可以爲指定的虛擬目錄請求一個ISAPIRuntime的實例(如果這個實例還不存在的話).每個虛擬目錄擁有它自己的應用程序域(AppDomain),當一個獨立的應用(指一個ASP.NET程序)開始的時候ISAPIRuntime從啓動過程就一直在應用程序域中存在.實例化(譯註:應該是指ISAPIRuntime的實例化)似乎是通過COM來進行的,因爲接口方法都被暴露爲COM可調用的方法.
 
當第一個針對某虛擬目錄的請求到來時,System.Web.Hosting.AppDomainFactory.Create()函數被調用來創建一個ISAPIRuntime的實例.這就開始了這個應用的啓動進程.這個調用接收這個應用的類型,模塊名稱和虛擬目錄信息,這些信息被ASP.NET用來創建應用程序域並啓動此虛擬目錄的ASP.NET程序.這個HttpRuntime實例(譯註:原文爲This HttpRuntime derived object,HttpRuntime是一個sealed,疑爲原文錯誤)在一個新的應用程序域中被創建.每個虛擬目錄(即一個ASP.NET應用程序寄)宿在一個獨立的應用程序域中,而且他們也只有在特定的ASP.NET程序被請求到的時候纔會被載入.ISAPI擴展管理這些HttpRuntime對象的實例,並根據請求的虛擬目錄將內部的請求路由到正確的那個HttpRuntime對象上.
 


4-ISAPI請求使用一些沒有文檔記載的類,接口並調用許多工廠方法傳送到ASP.NETHTTP管道的過程.每個Web程序/虛擬目錄在它自己的應用程序域中運行,調用者(譯註:ISAPI DLL)保持一個IISAPIRuntime接口的引用來觸發ASP.NET的請求處理.
 
回到運行時
 
在這裏我們有一個在ISAPI擴展中活動的,可調用的ISAPIRuntime對象的實例.每次運行時是啓動的並運行着的時候(譯註:相對的,如果運行時並沒有啓動,就需要象上一章所說的那樣載入運行時),ISAPI的代碼調用ISAPIRuntime.ProcessRequest()方法,這個方法是真正的進入ASP.NET管道的入口.這個流程在圖4中顯示.
 
記住ISAPI是多線程的,所以請求也會通過AppDomainFactory.Create()(譯註:原文爲ApplicationDomainFactory,疑有誤)函數中返回的引用在多線程環境中被處理.列表1顯示了ISAPIRuntime.ProcessRequest()方法中反編譯後的代碼,這個方法接收一個ISAPI ecb對象和服務類型(WorkerRequestType)作爲參數.這個方法是線程安全的,所以多個ISAPI線程可以同時在這一個被返回的對象實例上安全的調用這個方法.
 
列表1:ProcessRequest方法接收一個ISAPI Ecb並將其傳給工作線程
publicint 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;
}
 
這裏實際的代碼並不重要,記住這是從內部框架代碼中反編譯出來的,你不能直接處理它,它也有可能在將來發生改變.它只是用來揭示在幕後發生了什麼.ProcessRequest方法接收非託管的ECB引用並將它傳送給ISAPIWorkerRequest對象,此對象負責爲當前請求創建創建請求上下文.在列表2中顯示了這個過程.
 
System.Web.Hosting.ISAPIWorkerRequest類是HttpWorkerRequest類的一個抽象子類(譯註:HttpWorkerRequestISAPIWorkerRequest都是抽象類,並且ISAPIWorkerRequest繼承自HttpWorkerRequest),它的工作是構建一個作爲Web應用輸入的輸入輸出的抽象視角.注意這裏有另一個工廠方法:CreateWorkerRequest,通過判斷接受到的第二個參數來創建對應的WorkerRequest對象.有三個不同的版本:ISAPIWorkerRequestInProc,ISAPIWorkerRequestInProcForIIS6,ISAPIWorkerRequestOutOfProc.每次有請求進入,這個對象被創建並作爲請求和響應對象的基礎,它會接收它們的數據和由WorkerRequest提供的數據流.
 
抽象的HttpWorkerRequest類在低層接口上提供一個高層的抽象,這樣就封裝了數據是從哪裏來的,可以是一個CGI Web服務器,Web瀏覽器控件或者是一些你用來給HTTP運行時數據的自定義的機制.關鍵是ASP.NET能用統一的方法來接收信息.
 
在使用IIS的情況下,這個抽象是建立在ISAPI ECB塊周圍.在我們的請求處理過程中,ISAPIWorkerRequest掛起ISAPI ECB並根據需要從它那裏取出信息.列表2顯示了請求字符串值(query string value)是如何被取出來的.
 
列表2:使用非託管數據的ISAPIWorkerRequest方法
// *** Implemented in ISAPIWorkerRequest
publicoverridebyte[] GetQueryStringRawBytes()
{
    byte[] buffer1 = newbyte[this._queryStringLength];
    if (this._queryStringLength > 0)
    {
       int num1 = this.GetQueryStringRawBytesCore(buffer1, this._queryStringLength);
       if (num1 != 1)
       {
           thrownew HttpException( "Cannot_get_query_string_bytes");
       }
    }
    return buffer1;
}
 
// *** Implemented in a specific implementation class ISAPIWorkerRequestInProcIIS6
internaloverrideint GetQueryStringCore(int encode, StringBuilder buffer, int size)
{
    if (this._ecb == IntPtr.Zero)
    {
       return 0;
    }
    return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode, buffer, size);
}
 
ISAPIWorkerRequest實現了一個高層次的包裝方法,它調用了低層的核心方法,負責真正的訪問非託管APIs-或稱爲服務級別的實現”(service level implementation).這些核心方法在特殊的ISAPIWorkerRequest子類中爲它寄宿的環境提供特殊的實現.這實現了簡單的擴展的(pluggable)環境,這樣一來當以後新的Web服務器接口或其他平臺成爲了ASP.NET的目標時附加的實現類可以在被簡單的提供出來.這裏還有一個協助類(helper class)System.Web.UnsafeNativeMethods.裏面許多對ISAPI ECB結構的操作實現了對ISAPI擴展的非託管操作.
 
HttpRuntime,HttpContextHttpApplication
 
當一個請求到來時,它被路由到ISAPIRuntime.ProcessRequest()方法.這個方法調用HttpRuntime.ProcessRequest方法,它作一些重要的事情(Reflector查看System.Web.HttpRuntime.ProcessRequestInternal方法):
 
·             爲請求創建一個新的HttpContext實例
·             獲取一個HttpApplication實例
·             調用HttpApplication.Init()方法來設置管道的事件
·             Init()方法觸發開始ASP.NET管道處理的HttpApplication.ResumeProcessing()方法
 
首先一個新的HttpContext對象被創建並用來傳遞ISAPIWorkerRequest(ISAPI ECB的包裝器).這個上下文在整個請求的生命週期總都是可用的並總可以通過靜態屬性HttpContext.Currect來訪問.正像名字所暗示的那樣,HttpContext對象代表了當前活動請求的上下文因爲他包含了在請求生命週期中所有典型的你需要訪問的重要對象:Request,Response,Application,Server,Cache.在請求處理的任何時候HttpContext.Current給你訪問所有這些的能力.
 
HttpContext對象也包含一個非常有用的Items集合,你可以用它來保存針對特定請求的數據.上下文對象在請求週期的開始時被創建,在請求結束時被釋放,所有在Items集合中保存的數據只在這個特定的請求中可用.一個很好的使用的例子是請求日誌機制,當你通過想通過在Global.asax中掛接Application_BeginRequestApplication_EndRequest方法記錄請求的開始和結束時間(象在列表3中顯示的那樣).HttpContext對你就非常有用了-如果你在請求或頁面處理的不同部分需要數據,你自由的使用它.
 
列表3-使用HttpContext.Items集合使你在不同的管道事件中保存數據
protectedvoid Application_BeginRequest(Object sender, EventArgs e)
{
    //*** Request Logging
    if (App.Configuration.LogWebRequests)
       Context.Items.Add("WebLog_StartTime",DateTime.Now);
}
 
protectedvoid 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根目錄)而且每個應用程序是被單獨的處理的.
 
HttpApplication類似儀式的主人-它是處理動作開始的地方
 
ASP.NET2.0中的變化
ASP.NET2.0並沒有對底層架構做很多改變.主要的新特性是HttpApplication對象有了一系列新的事件-大部分是預處理和後處理事件鉤子-這使得應用程序事件管道變得更加的顆粒狀了.ASP.NET2.0也支持新的ISAPI功能- HSE_REQ_EXEC_URL-這允許在ASP.NET處理的內部重定向到另外的URL上.這使得ASP.NET可以在IIS中設置一個通配符擴展,並處理所有的請求,其中一部分被HTTP處理器(handler)處理,另一部分被新的DefaultHttpHandler對象處理. DefaultHttpHandler會在內部調用ISAPI來定位到原始的URL上.這允許ASP.NET可以在其他的頁面,如ASP,被調用前處理認證和登錄等事情.
 
域的主人:HttpApplication
 
每個請求都被路由到一個HttpApplication對象上.HttpApplicationFactory類根據應用程序的負載爲你的ASP.NET應用創建一個HttpApplication對象池併爲每個請求分發HttpApplication對象的引用.對象池的大小受machine.config文件中ProcessModel鍵中的MaxWorkerThreads設置限制,默認是20(譯註:此處可能有誤,根據Reflector反編譯的代碼,池的大小應該是100,如果池大小小於100,HttpApplicationFactory會創建滿100,但是考慮到會有多個線程同時創建HttpApplication的情況,實際情況下有可能會超過100).
 
對象池以一個更小的數字開始;通常是一個然後增長到和同時發生的需要被處理的請求數量一樣.對象池被監視,這樣在大負載下它可能會增加到最大的實例數量,當負載降低時會變回一個更小的數字.
 
HttpApplication是你的Web程序的外部包裝器,而且它被映射到在Global.asax裏面定義的類上.它是進入HttpRuntime的第一個入口點.如果你查看Global.asax(或者對應的代碼類)你會發現這個類直接繼承自HttpApplication:
 
public class Global : System.Web.HttpApplication
 
HttpApplication的主要職責是作爲Http管道的事件控制器,所以它的接口主要包含的是事件.事件掛接是非常廣泛的,包括以下這些:
l         BeginRequest
l         AuthenticateRequest
l         AuthorizeRequest
l         ResolveRequestCache
l         AquireRequestState
l         PreRequestHandlerExecute
l         …Handler Execution…
l         PostRequestHandlerExecute
l         ReleaseRequestState
l         UpdateRequestCache
l         EndRequest
 
每個事件在Global.assx文件中以Application_前綴開頭的空事件作爲實現.例如, Application_BeginRequest(), Application_AuthorizeRequest()..這些處理器爲了便於使用而提供因爲它們是在程序中經常被使用的,這樣你就不用顯式的創建這些事件處理委託了.
 
理解每個ASP.NET虛擬目錄在它自己的應用程序域中運行,而且在應用程序域中有多個從ASP.NET管理的池中返回的HttpApplication實例同時運行,是非常重要的.這是多個請求可以被同時處理而不互相妨礙的原因.
 
查看列表4來獲得應用程序域,線程和HttpApplication之間的關係.
列表4-顯示應用程序域,線程和HttpApplication實例之間的關係
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: " +
            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);

}
 
 
這是隨sample提供的demo的一部分,運行的結果在圖5中顯示.運行兩個瀏覽器,打開這個演示頁面可以看到不同的ID.
 
5-你可以通過同時運行多個瀏覽器來簡單的查看應用程序域,應用程序池實例和請求線程是如何交互的.當多個請求同時發起,你可以看到線程ID和應用程序ID變化了,但是應用程序域還是同一個.
 
你可能注意到在大多數請求上,當線程和HttpApplication ID變化時應用程序域ID卻保持不變,雖然它們也可能重複(指線程和HttpApplication ID).HttpApplication是從一個集合中取出,在隨後到來的請求中可以被複用的,所以它的ID有時是會重複的.注意Application實例並不和特定的線程綁定-確切的說它們是被指定給當前請求的活動線程.
 
線程是由.NET的線程池管理的,默認是多線程套間(MTA)線程.你可以在ASP.NET的頁面上通過指定@Page指令的屬性ASPCOMPAT=”true”來覆蓋套間屬性.ASPCOMPAT意味着爲COM組件提供一個安全的執行環境,指定了這個屬性,就會爲這些請求使用特殊的單線程套間(STA).STA線程被存放在單獨的線程池中,因爲它們需要特殊的處理.
 
這些HttpApplication對象全部在同一個應用程序域中運行的事實是非常重要的.這是爲什麼ASP.NET可以保證對web.config文件或者單獨的ASP.NET頁面的修改可以在整個應用程序域中生效.改變web.config中的一個值導致應用程序域被關閉並重啓.這可以保證所有的HttpApplication可以看到這個修改,因爲當應用程序域重載入的時候,所做的修改(譯註:即被修改的文件)會在啓動的時候被重新讀入.所有的靜態引用也會被重載,所以如果程序通過App Configuration settings讀取值,這些值也會被刷新.
 
爲了在sample中看到這點,點擊ApplicationPoolsAndThreads.aspx頁面並記下應用程序域ID.然後打開並修改web.config(加入一個空格並保存).然後重新載入頁面.你會發現一個新的應用程序域已經被創建了.
 
本質上當上面的情況發生時,Web應用/虛擬目錄是完整的重啓.所有已經在管道中被處理得請求會繼續在現存的管道中被處理,當任何一個新的請求來到時,它會被路由到新的應用程序域中.爲了處理被掛起的請求”,ASP.NET在請求已超時而它(指請求)還在等待時強制關閉應用程序域.所有事實上是可能出現一個應用程序對應兩個應用程序域,此時舊的那個正在關閉而新的正在啓動.兩個應用程序域都繼續爲它們的客戶服務,直到老的那個處理玩正在等待處理的請求並關閉,此時只有一個應用程序域在運行.
 
流過”ASP.NET管道
 
HttpApplication觸發事件來通知你的程序有事發生,以此來負責請求流轉.這作爲HttpApplication.Init()函數的一部分發生(Reflector查看System.Web.HttpApplication.InitInternal()方法和HttpApplication.ResumeSteps()方法來了解更多詳情),連續設置並啓動一系列事件,包括執行所有的處理器(handler).這些事件處理器映射到global.asax中自動生成的哪些事件中,同時它們也映射到所有附加的HttpModule(它們本質上是HttpApplication對外發布的額外的事件接收器(sink)).
 
HttpModuleHttpHandler兩者都是根據Web.config中對應的配置被動態載入並附加到事件處理鏈中.HttpModule實際上是事件處理器,附加到特殊的HttpApplication事件上,然而HttpHandler是用來處理應用級請求處理的終點.
 
HttpModuleHttpHandler兩者都是在HttpApplication.Init()函數調用的一部分中被載入並附加到調用鏈上.6顯示了不同的事件,它們是何時發生的以及它們影響管道的哪一部分.
 

6-事件在ASP.NET http管道中流轉的過程.HttpApplication對象的事件驅動請求在管道中流轉.Http Module可以攔截這些事件並覆蓋或者擴展現有的功能.
 
 
HttpContext, HttpModules HttpHandlers
 
httpApplication它本身對發送給應用程序的數據一無所知-它只是一個通過事件來通訊的消息對象.它觸發事件並通過HttpContext對象來向被調用函數傳遞消息.實際的當前請求的狀態數據由前面提到的HttpContext對象維護.它提供了所有請求專有的數據並從進入管道開始到結束一直跟隨請求.7顯示了ASP.NET管道中的流程.注意上下文對象(HttpContext),這個從請求開始到結束一直都是你朋友的對象,可以在一個事件處理函數中保存信息並在以後的事件處理函數中取出.
 
一旦管道被啓動,HttpApplication開始象圖六那樣一個個的觸發事件.每個事件處理器被觸發,如果事件被掛接,這些處理器將執行它們自己的任務.這個處理的主要任務是最終調用掛接到此特定請求的HttpHandler.處理器(handler)ASP.NET請求的核心處理機制,通常也是所有應用程序級別的代碼被執行的地方.記住ASP.NET頁面和Web服務框架都是作爲HttpHandler實現,這裏也是處理請求的的核心之處.模塊(module)趨向於成爲一個傳遞給處理器(handler)的上下文的預處理或後處理器.ASP.NET中典型的默認處理器包括預處理的認證,緩存以及後處理中各種不同的編碼機制.
 
有很多關於HttpHandlerHttpModule的可用信息,所以爲了保持這篇文章在一個合理的長度,我將提供一個關於處理器的概要介紹.
 
HttpModule
 
當請求在管道中傳遞時,HttpApplicaion對象中一系列的事件被觸發.我們已經看到這些事件在Global.asax中作爲事件被髮布.這種方法是特定於應用程序的,可能並不總是你想要的.如果你要建立一個通用的可用被插入任何Web應用程序的HttpApplication事件鉤子,你可用使用HttpModule,這是可複用的,不需要特定語應用程序代碼的,只需要web.config中的一個條目.
 
模塊本質上是過濾器(fliter)-功能上類似於ISAPI過濾器,但是它工作在ASP.NET請求級別上.模塊允許爲每個通過HttpApplication對象的請求掛接事件.這些模塊作爲外部程序集中的類存貯.,web.config文件中被配置,在應用程序啓動時被載入.通過實現特定的接口和方法,模塊被掛接到HttpApplication事件鏈上.多個HttpModule可用被掛接在相同的事件上,事件處理的順序取決於它們在Web.config中聲明的順序.下面是在Web.config中處理器定義.
 
<configuration>
  <system.web>
    <httpModules>
  <addname="BasicAuthModule"
      type="HttpHandlers.BasicAuth,WebStore"/>
    </httpModules>
 </system.web>
</configuration>
 
注意你需要指定完整的類型名和不帶dll擴展名的程序集名.
 
模塊允許你查看每個收到的Web請求並基於被觸發的事件執行一個動作.模塊在修改請求和響應數據方面做的非常優秀,可用爲特定的程序提供自定義認證或者爲發生在ASP.NET中的每個請求增加其他預處理/後處理功能.許多ASP.NET的功能,像認證和會話(Session)引擎都是作爲HttpModule來實現的.
 
雖然HttpModule看上去很像ISAPI過濾器,它們都檢查每個通過ASP.NET應用的請求,但是它們只檢查映射到單個特定的ASP.NET應用或虛擬目錄的請求,也就是隻能檢查映射到ASP.NET的請求.這樣你可以檢查所有ASPX頁面或者其他任何映射到ASP.NET的擴展名.你不能檢查標準的.HTM或者圖片文件,除非你顯式的映射這些擴展名到ASP.NET ISAPI dll,就像圖1中展示的那樣.一個常見的此類應用可能是使用模塊來過濾特定目錄中的JPG圖像內容並在最上層通過GDI+來繪製樣品字樣.
 
實現一個HTTP模塊是非常簡單的:你必須實現之包含兩個函數(Init()Dispose())IHttpModule接口.傳進來的事件參數中包含指向HTTPApplication對象的引用,這給了你訪問HttpContext對象的能力.在這些方法上你可以掛接到HttpApplication事件上.例如,如果你想掛接AuthenticateRequest事件到一個模塊上,你只需像列表5中展示的那樣做
 
列表5:基礎的HTTP模塊是非常容易實現的
列表5:基礎的HTTP模塊是非常容易實現的
public class BasicAuthCustomModule : IHttpModule
{
 
    publicvoid Init(HttpApplication application)
    {
       // *** Hook up any HttpApplication events
       application.AuthenticateRequest +=
                new EventHandler(this.OnAuthenticateRequest);
    }
    publicvoid Dispose() { }
 
    publicvoid OnAuthenticateRequest(object source, EventArgs eventArgs)
    {
       HttpApplication app = (HttpApplication) source;
       HttpContext Context = HttpContext.Current;
       … do what you have to do                        }
}
 
記住你的模塊訪問了HttpContext對象,從這裏可以訪問到其他ASP.NET管道中固有的對象,如請求(Request)和響應(Response),這樣你還可以接收用戶輸入的信息等等.但是記住有些東西可能是不能訪問的,它們只有在處理鏈的後段才能被訪問.
 
你可以在Init()方法中掛接多個事件,這樣你可以在一個模塊中實現多個不同的功能.然而,將不同的邏輯分到單獨的類中可能會更清楚的將模塊進行模塊化(譯註:這裏的模塊化和前面的模塊沒有什麼關係)在很多情況下你實現的功能可能需要你掛接多個事件-例如一個日誌過濾器可能在BeginRequest事件中記錄請求開始時間,然後在EndRequest事件中將請求結束寫入到日誌中.
注意一個HttoModuleHttpApplication事件中的重點:Response.End()HttpApplication.CompleteRequest()會在HttpApplicationModule的事件鏈中抄近道”.注意Response.End()”來獲得更多信息.
 
注意Response.End()
當創建HttpModule或者在Global.asax中實現事件鉤子的時候,當你調用Response.End或 Appplication.CompleteRequest的時候要特別注意.這兩個函數都結束當前請求並停止觸發在HTTP管道中後續的事件,然後發生將控制返回到Web服務器中.當你在處理鏈的後面有諸如記錄日誌或對內容進行操作的行爲時,因爲他們沒有被觸發,有可能使你上當.例如,sample中logging的例子就會失敗,因爲如果調用Response.End()的話,EndRequest事件並不會被觸發.
 
HttpHandlers
 
模塊是相當底層的,而且對每個來到ASP.NET應用程序的請求都會被觸發.Http處理器更加的專注並處理映射到這個處理器上的請求.
 
Http處理器需要實現的東西非常簡單,但是通過訪問HttpContext對象它可以變得非常強大.Http處理器通過實現一個非常簡單的IHttpHandler接口(或是它的異步版本,IHttpAsyncHandler),這個接口甚至只含有一個方法-ProcessRequest()-和一個屬性IsReusable.關鍵部分是ProcessRequest(),這個函數獲取一個HttpContext對象的實例作爲參數.這個函數負責從頭到尾處理Web請求.
 
單獨的,簡單的函數?太簡單了,對吧?好的,簡單的接口,但並不弱小!記住WebFormWebService都是作爲Http處理器實現的,所以在這個看上去簡單的接口中包裝了很強大的能力.關鍵是這樣一個事實,當一個請求來到Http處理器時,所有的ASP.NET的內部對象都被準備和設置好來處理請求了.主要的是HttpContext對象,提供所有相關的請求功能來接收輸入並輸出回Web服務器.
 
對一個HTTP處理其來說所有的動作都在這個單獨的ProcessRequest()函數的調用中發生.這像下面所展示的這樣簡單:
 
public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello World");
}
 
也可以像一個可以從HTML模板渲染出複雜表單的WebForm頁面引擎那麼完整,複雜.通過這個簡單,但是強大的接口要做什麼,完全取決於你的決定.
 
因爲Context對象對你是可用的,你可用訪問Request,Response,SessionCache對象,所以你擁有所有ASP.NET請求的關鍵特性,可以獲得用戶提交的內容並返回你產生的內容給客戶端.記住HttpContext對象-它是你在整個ASP.NET請求的生命週期中的朋友”.
 
處理器的關鍵操作應該是將輸出寫入Response對象或者更具體一點,Response對象的OutputStream.這個輸出是實際上被送回到客戶端的.在幕後,ISAPIWorkerRequest管理着將輸出流返回到ISAPI ecb的過程.WriteClient方法是實際產生IIS輸出的方法.
 
 
7-ASP.NET請求管道通過一系列事件接口來轉發請求,提供了更大的靈活性.Application當請求到來並通過管道時作爲一個載入Web應用並觸發事件的宿主容器.每個請求都沿着配置的Http過濾器和模塊的路徑走(譯註:原文爲Http Filters And Modules,應該是指Http ModuleHttp Handler).過濾器可以檢查每個通過管道的請求,Handler允許實現應用程序邏輯或者像Web FormWebService這樣的應用層接口.爲了嚮應用提供輸入輸出,Context對象在這個處理過程中提供了特定於請求的的信息.
 
WebForm使用一系列在框架中非常高層的接口來實現一個Http處理器,但是實際上WebFormRender()方法簡單的以使用一個HtmlTextWriter對象將它的最終結果輸出到context.Response.OutputStream告終.所以非常夢幻的,終究即使是向WebForm這樣高級的工具也只是在RequestResponse對象之上進行了抽象而已.
 
到了這裏你可能會疑惑在Http handler中你到底需要處理什麼.既然WebForm提供了簡單可用的Http Handler實現,那麼爲什麼需要考慮更底層的東西而放棄這擴展性呢?
 
WebForm對於產生複雜的HTML頁面來說是非常強大的,業務層邏輯需要圖形佈局工具和基於模塊的頁面.但是WebForm引擎做了一系列overhead intensive的任務.如果你想要做的是從系統中讀入一個文件並通過代碼將其返回的話,不通過WebForm框架直接返回文件會更有效率.如果你要做的是類似從數據庫中讀出圖片的工作,並不需要使用頁面框架-你不需要模板而且確定不需要Web頁面並從中捕捉用戶事件.
 
沒有理由需要建立一個頁面對象和Session並捕捉頁面級別的事件-所有這些需要執行對你的任務沒有幫助的額外的代碼.
 
所以自定義處理器更加有效率.處理器也可用來做WebForm做不到的事情,例如不需要在硬盤上有物理文件就可用處理請求的能力,也被稱爲虛擬Url.要做到這個,確認你在圖1中展示的應用擴展對話框中關掉了檢查文件存在選項.
 
這對於內容提供商來說非常常見,象動態圖片處理,XML服務,URL重定向服務提供了vanity Urls,下載管理以及其他,這些都不需要WebForm引擎.
 
異步HTTP Handler
在這篇文章中我大部分都在討論同步處理,但是ASP.NET運行時也可以通過異步HTTP handler來支持異步操作.這些處理器自動的將處理卸載到獨立的線程池的線程中並釋放主ASP.NET線程,使ASP.NET線程可以處理其他的請求.不幸的是在1.x版的.NET,”卸載後的處理還是在同一個線程池中,所以這個特性之增加了一點點的性能.爲了創建真正的異步行爲,你必須創建你自己的線程並在回調處理中自己管理他們.
當前版本的ASP.NET 2.0 Beta 2在IhttpHandlerAsync(譯註:此處應該是指IHttpAsyncHandler,疑爲作者筆誤)接口和Page類兩方面做了一些對異步處理的改進,提供了更好的性能,但是在最終發佈版本中這些是否會保留.
 
 
我說的這些對你來說夠底層了嗎?
 
唷-我們已經走完了整個請求處理過程了.這過程中有很多底層的信息,我對HTTP模塊和HTTP處理器是怎麼工作的並沒有描述的非常詳細.挖掘這些信息相當的費時間,我希望在瞭解了ASP.NET底層機制後,你能獲得和我一樣的滿足感.
 
在結束之前讓我們快速的回顧一下我在本文中討論的從IIS到處理器(handler)的過程中,事件發生的順序
 
  • IIS獲得請求
  • 檢查腳本映射中,此請求是否映射到aspnet_isapi.dll
  • 啓動工作進程 (IIS5中爲aspnet_wp.exe,IIS6中爲w3wp.exe)
  • .NET運行時被載入
  • IsapiRuntime.ProcessRequest()被非託管代碼調用
  • 爲每個請求創建一個IsapiWorkerRequest
  • HttpRuntime.ProcessRequest()被工作進程調用
  • IsapiWorkerRequest對象爲參數創建HttpContext對象
  • 調用HttpApplication.GetApplicationInstance()來從池中取得一個對象實例
  • 調用HttpApplication.Init()來開始管道事件並掛接模塊和處理器
  • HttpApplicaton.ProcessRequest被調用以開始處理.
  • 管道中的事件被依次觸發
  • 處理器被調,ProcessRequest函數被觸發
  • 控制返回到管道中,後處理事件被依次觸發
 
有了這個簡單的列表,記住這些東西並把他們拼在一起就變得容易多了.我時常看看它來加深記憶.所以現在,回去工作,做一些不那麼抽象的事情
 
雖然我都是基於ASP.NET1.1來進行討論的,不過在ASP.NET2.0中這些處理過程看上去並沒有發生太大的變化.
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章