瞭解ASP.NET底層架構
瞭解ASP.NET底層架構
進入底層
這篇文章以非常底層的視角講述了Web請求(request)在ASP.NET框架中是如何流轉的,從Web伺服器,通過ISAPI直到請求處理器(handler)和你的程式碼.看看在幕後都發生了些什麼,不要再把ASP.NET看成一個黑盒了.
ASP.NET是一個非常強大的構建Web應用的平臺,它提供了極大的靈活性和能力以致於可以用它來構建所有型別的Web應用.絕大多數的人只熟悉高層的框架如WebForms和WebServices-這些都在ASP.NET層次結構在最高層.在這篇文章中我將會討論ASP.NET的底層機制並解釋請求(request)是怎麼從Web伺服器傳送到ASP.NET執行時然後如何通過ASP.NET管道來處理請求.
對我而言瞭解平臺的內幕通常會帶來滿足感和舒適感,深入瞭解也能幫助我寫出更好的應用.知道可以使用哪些工具以及他們是怎樣作為整個複雜框架的一部分來互相配合的可以更容易地找出最好的解決方案,更重要的是可以在出現問題時更好的解決它們.這篇文章的目標是從系統級別了解ASP.NET並幫助理解請求(request)是如何在ASP.NET的處理管道中流轉的.同樣,我們會了解核心引擎和Web請求如何在那裡結束.這些資訊大部分並不是你在日常工作時必須瞭解的,但是它對於理解ASP.NET架構如何把請求路由到你的程式碼(通常是非常高層的)中是非常有益的.
不管怎麼樣,ASP.NET從更低的層次上提供了更多的靈活性.HTTP執行時和請求管道在構建WebForms和WebServices上提供了同樣的能力-它們事實上都是建立在.NET託管程式碼上的.而且所有這些同樣的功能對你也是可用的,你可用決定你是否需要建立一個比WebForms稍低一點層次的定製的平臺.
WebForms顯然是最簡單的構建絕大多數Web介面的方法,不過如果你是在建立自定義的內容處理器(handler),或者有在處理輸入輸出內容上有特殊的要求,或者你需要為另外的應用建立一個定製的應用程式服務介面,使用這些更低階的處理器(handler)或者模組(module)能提供更好的效能並能對實際請求處理提供更多的控制.在WebForms和WebServices這些高層實現提供它們那些能力的同時,它們也對請求增加了一些額外負擔,這些都是在更底層可以避免的.
ASP.NET是什麼
讓我們以一個簡單的定義開始:什麼是ASP.NET?我喜歡這樣定義ASP.NET:
ASP.NET是一個複雜的使用託管程式碼來從頭到尾處理Web請求的引擎.
它並不只是WebForms和WebServies…
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提供的內容可以通過通用物件(例如HttpRequest和HttpResponse)來獲取,這些物件通過一個定義良好並有很好訪問性的介面來暴露非託管資料.
從瀏覽器到ASP.NET
讓我們從一個典型的ASP.NET Web請求的生命週期的起點開始.當使用者輸入一個URL,點選了一個超連結或者提交了一個HTML表單(form)(一個POST請求,相對於前兩者在一般意義上都是GET請求).或者一個客戶端程式可能呼叫了一個基於ASP.NET的WebService(同樣由ASP.NET來處理).在Web伺服器端,IIS5或6,獲得這個請求.在最底層,ASP.NET和IIS通過ISAPI擴充套件進行互動.在ASP.NET環境中這個請求通常被路由到一個副檔名為.aspx的頁面上,但是這個流程是怎麼工作的完全依賴於處理特定副檔名的HTTP Handler是怎麼實現的.在IIS中.aspx通過’應用程式擴充套件’(又稱為指令碼對映)被對映到ASP.NET的ISAPI擴充套件DLL-aspnet_isapi.dll.每一個請求都需要通過一個被註冊到aspnet_isapi.dll的副檔名來觸發ASP.NET(來處理這個請求).
依賴於副檔名ASP.NET將請求路由到一個合適的處理器(handler)上,這個處理器負責獲取這個請求.例如,WebService的.asmx副檔名不會將請求路由到磁碟上的一個頁面,而是一個由特殊屬性(Attribute)標記為WebService的類上.許多其他處理器和ASP.NET一起被安裝,當然你也可以自定義處理器.所有這些HttpHandler在IIS中被配置為指向ASP.NET ISAPI擴充套件,並在web.config(譯著:ASP.NET中自帶的handler是在machine.config中配置的,當然可以在web.config中覆蓋配置)被配置來將請求路由到指定的HTTP Handler上.每個handler都是一個處理特殊擴充套件的.NET類,可以從一個簡單的只包含幾行程式碼的Hello World類,到非常複雜的handler如ASP.NET的頁面或者WebService的handler.當前,只要瞭解ASP.NET的對映機制是使用副檔名來從ISAPI接收請求並將其路由到處理這個請求的handler上就可以了.
對在IIS中自定義Web請求處理來說,ISAPI是第一個也是最高效的入口
ISAPI連線
ISAPI是底層的非託管Win32 API.ISAPI定義的介面非常簡單並且是為效能做了優化的.它們是非常底層的-處理指標和函式指標表來進行回撥-但是它們提供了最底層和麵向效率的介面,使開發者和工具提供商可以用它來掛接到IIS上.因為ISAPI非常底層所以它並不適合來開發應用級的程式碼,而且ISAPI傾向於主要被用於橋接介面,向上層工具提供應用伺服器型別的功能.例如,ASP和ASP.NET都是建立在ISAPI上的,Cold Fusion,執行在IIS上的多數Perl,PHP以及JSP實現,很多第三方解決方案(如我的Wisual FoxPro的Web連線框架)都是如此.ISAPI是一個傑出的工具,可以為上層應用提供高效的管道介面,這樣上層應用可以抽象出ISAPI提供的資訊.在ASP和ASP.NET中,將ISAPI介面提供的資訊抽象成了型別Request和Response這樣的物件,通過它們來讀取ISAPI請求中對應的資訊.將ISAPI想像成管道.對ASP.NET來說,ISAPI dll是非常的”瘦”的,只是作為一個路由機制來將原始的請求轉發到ASP.NET執行時.所有那些沉重的負擔和處理,甚至請求執行緒的管理都發生在ASP.NET引擎內部和你的程式碼中.
作為協議,ISAPI同時支援ISAPI擴充套件和ISAPI過濾器(Filter).擴充套件是一個請求處理介面,提供了處理Web伺服器的輸入輸出的邏輯-它本質上是一個處理(事物?)介面.ASP和ASP.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對映了多種副檔名如.ASPX到ASP.NET的ISAPI擴充套件.通過這個機制請求會在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執行時在IIS5和6中是不同的.圖2顯示了這個流程的一個粗略概覽.
在IIS5中,aspnet_isapi.dll直接寄宿在inetinfo.exe程式中,如果你設定了Web站點或虛擬目錄的隔離度為中或高,則會寄宿在IIS單獨的(被隔離的)工作程式中.當第一個ASP.NET請求來到,DLL(aspnet_isapi.dll)會開始另一個新程式aspnet_wp.exe並將請求路由到這個程式中來進行處理.這個程式依次載入並寄宿.NET執行時.每個轉發到ISAPI DLL的請求都會通過命名管道呼叫被路由到這個程式來.
圖2-從較高層次來看請求從IIS到ASP.NET執行時,並通過請求處理管道的流程.IIS5和IIS6通過不同的方式與ASP.NET互動,但是一旦請求來到ASP.NET管道,整個處理流程就是一樣的了.
不同於以前版本的伺服器,IIS6為ASP.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後就把它做為基本的輸入輸出介面,和Request和Response物件一起使用.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.NET的HTTP管道的過程.每個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並將其傳給工作執行緒
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;
}
這裡實際的程式碼並不重要,記住這是從內部框架程式碼中反編譯出來的,你不能直接處理它,它也有可能在將來發生改變.它只是用來揭示在幕後發生了什麼.ProcessRequest方法接收非託管的ECB引用並將它傳送給ISAPIWorkerRequest物件,此物件負責為當前請求建立建立請求上下文.在列表2中顯示了這個過程.
System.Web.Hosting.ISAPIWorkerRequest類是HttpWorkerRequest類的一個抽象子類(譯註:HttpWorkerRequest和ISAPIWorkerRequest都是抽象類,並且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
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實現了一個高層次的包裝方法,它呼叫了低層的核心方法,負責真正的訪問非託管APIs-或稱為”服務級別的實現”(service level implementation).這些核心方法在特殊的ISAPIWorkerRequest子類中為它寄宿的環境提供特殊的實現.這實現了簡單的擴充套件的(pluggable)環境,這樣一來當以後新的Web伺服器介面或其他平臺成為了ASP.NET的目標時附加的實現類可以在被簡單的提供出來.這裡還有一個協助類(helper class)System.Web.UnsafeNativeMethods.裡面許多對ISAPI ECB結構的操作實現了對ISAPI擴充套件的非託管操作.
HttpRuntime,HttpContext和HttpApplication
當一個請求到來時,它被路由到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_BeginRequest和Application_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根目錄)而且每個”應用程式”是被單獨的處理的.
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)).
HttpModule和HttpHandler兩者都是根據Web.config中對應的配置被動態載入並附加到事件處理鏈中.HttpModule實際上是事件處理器,附加到特殊的HttpApplication事件上,然而HttpHandler是用來處理”應用級請求處理”的終點.
HttpModule和HttpHandler兩者都是在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中典型的預設處理器包括預處理的認證,快取以及後處理中各種不同的編碼機制.
有很多關於HttpHandler和HttpModule的可用資訊,所以為了保持這篇文章在一個合理的長度,我將提供一個關於處理器的概要介紹.
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>
<add name= "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
{
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管道中固有的物件,如請求(Request)和響應(Response),這樣你還可以接收使用者輸入的資訊等等.但是記住有些東西可能是不能訪問的,它們只有在處理鏈的後段才能被訪問.
你可以在Init()方法中掛接多個事件,這樣你可以在一個模組中實現多個不同的功能.然而,將不同的邏輯分到單獨的類中可能會更清楚的將模組進行模組化(譯註:這裡的模組化和前面的模組沒有什麼關係)在很多情況下你實現的功能可能需要你掛接多個事件-例如一個日誌過濾器可能在BeginRequest事件中記錄請求開始時間,然後在EndRequest事件中將請求結束寫入到日誌中.
注意一個HttoModule和HttpApplication事件中的重點:Response.End()或HttpApplication.CompleteRequest()會在HttpApplication和Module的事件鏈中”抄近道”.看”注意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請求.
單獨的,簡單的函式?太簡單了,對吧?好的,簡單的介面,但並不弱小!記住WebForm和WebService都是作為Http處理器實現的,所以在這個看上去簡單的介面中包裝了很強大的能力.關鍵是這樣一個事實,當一個請求來到Http處理器時,所有的ASP.NET的內部物件都被準備和設定好來處理請求了.主要的是HttpContext物件,提供所有相關的請求功能來接收輸入並輸出回Web伺服器.
對一個HTTP處理其來說所有的動作都在這個單獨的ProcessRequest()函式的呼叫中發生.這像下面所展示的這樣簡單:
public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello World");
}
也可以像一個可以從HTML模板渲染出複雜表單的WebForm頁面引擎那麼完整,複雜.通過這個簡單,但是強大的介面要做什麼,完全取決於你的決定.
因為Context物件對你是可用的,你可用訪問Request,Response,Session和Cache物件,所以你擁有所有ASP.NET請求的關鍵特性,可以獲得使用者提交的內容並返回你產生的內容給客戶端.記住HttpContext物件-它是你在整個ASP.NET請求的生命週期中的”朋友”.
處理器的關鍵操作應該是將輸出寫入Response物件或者更具體一點,是Response物件的OutputStream.這個輸出是實際上被送回到客戶端的.在幕後,ISAPIWorkerRequest管理著將輸出流返回到ISAPI ecb的過程.WriteClient方法是實際產生IIS輸出的方法.
圖7-ASP.NET請求管道通過一系列事件介面來轉發請求,提供了更大的靈活性.Application當請求到來並通過管道時作為一個載入Web應用並觸發事件的宿主容器.每個請求都沿著配置的Http過濾器和模組的路徑走(譯註:原文為Http Filters And Modules,應該是指Http Module和Http Handler).過濾器可以檢查每個通過管道的請求,Handler允許實現應用程式邏輯或者像Web Form和WebService這樣的應用層介面.為了嚮應用提供輸入輸出,Context物件在這個處理過程中提供了特定於請求的的資訊.
WebForm使用一系列在框架中非常高層的介面來實現一個Http處理器,但是實際上WebForm的Render()方法簡單的以使用一個HtmlTextWriter物件將它的最終結果輸出到context.Response.OutputStream告終.所以非常夢幻的,終究即使是向WebForm這樣高階的工具也只是在Request和Response物件之上進行了抽象而已.
到了這裡你可能會疑惑在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中這些處理過程看上去並沒有發生太大的變化.
相關文章
- 【乾貨】MySQL底層架構設計,你瞭解多少?MySql架構
- 深入瞭解Redis底層資料結構Redis資料結構
- 簡單瞭解InnoDB底層原理
- Vue底層架構及其應用Vue架構
- docker架構和底層技術Docker架構
- 區塊鏈的底層架構區塊鏈架構
- 探索小程式底層架構原理架構
- ASp.net 剖析三層架構ASP.NET架構
- 深入詳細瞭解synchronized底層原理synchronized
- HashMap原理(一) 概念和底層架構HashMap架構
- 深度解析HashMap底層實現架構HashMap架構
- 架構師之路-https底層原理架構HTTP
- SQL Server底層架構技術對比SQLServer架構
- 瞭解安卓架構(linux核心層、系統執行庫層、應用框架層、應用層)安卓架構Linux框架
- 三分鐘深入瞭解Spring底層Spring
- 大流量網站的底層系統架構網站架構
- Asp.net 2.0三層架構的構建與理解ASP.NET架構
- 快速瞭解雲原生架構架構
- asp.net標準三層架構--工廠模式ASP.NET架構模式
- 簡單瞭解 TiDB 架構TiDB架構
- 瞭解 Linkerd Service Mesh 架構架構
- 深入瞭解Mybatis架構設計MyBatis架構
- 你瞭解微服務架構麼?微服務架構
- 《架構師之路1》三張圖徹底瞭解Java中字串的不變性架構Java字串
- 筆記-runtime原始碼解析之讓你徹底瞭解底層原始碼筆記原始碼
- 新零售SaaS架構:組織管理的底層邏輯與架構設計架構
- Redis資料結構SortedSet底層原理詳解Redis資料結構
- 瞭解ansible架構與工作原理架構
- 【AQS面試篇】瞭解ReentrantLock嗎?講講其底層實現AQS面試ReentrantLock
- 三層架構及分層架構
- 大語言模型底層架構丨帶你認識Transformer模型架構ORM
- Zookeeper之Zookeeper底層客戶端架構實現原理(轉載)客戶端架構
- Redis - 底層資料結構Redis資料結構
- 三分鐘瞭解架構的起源架構
- AWS副總裁Peter:解密基礎架構底層運維和構建之道解密架構運維
- MVC 三層架構案例詳細講解MVC架構
- 【包建強】ASP.NET底層機制 HttpHandlerASP.NETHTTP
- iOS底層系統:BSD層詳解iOS