1. 程式人生 > >從底層瞭解ASP.NET體系結構

從底層瞭解ASP.NET體系結構

導讀:
  前言
  關於ASP.NET的底層的工作機制,最近園子裡討論的甚是火熱。相信很多人都看過Rick Strahl先生的一篇經典之作:A low-level Look at the ASP.NET Architecture,經Rick Strahl先生同意,我把他的這篇文章翻譯成中文,希望能夠給想深入瞭解ASP.NET工作機制的朋友一點幫助。
  特別說明:翻譯此文的目的僅僅是為了給廣大的ASP.NET愛好者提供一些幫助,由於本人能力有限,文中不對地方,還請批評指正。如果你需要轉載,請你保留該文以及原英文的連結。多謝!
  作者:Rick Strahl || 翻譯:today || 下載例子程式碼
  目錄

  1. ASP.NET是什麼?
  2. 從瀏覽器到ASP.NET
  3. ISAPI連線
  4. IIS5和IIS6的不同之處
  5. 進入.NET執行時
  6. 載入.NET—稍微有點神祕
  7. 回到執行時
  8. HttpRuntime,HttpContext以及HttpApplication
  9. Web程式的主要部分:HttpApplication
  10. 穿過ASP.NET管道
  11. HttpContext,HttpModules和HttpHandlers
  12. HttpModules
  13. HttpHandlers
  14. 是否已經提供了足夠的底層知識?
  摘要:
ASP.NET是一個用於構建Web程式的強大平臺,提供了巨大的彈性和能力以至於它可以構建任意的Web程式。許多人僅僅對處於ASP.NET高層次的框架如:WebForms和WebServices比較熟悉,因此,在這篇文章裡,我將會闡述有關ASP.NET比較底層的知識,並且將會解釋,如何將請求從Web Server移交給ASP.NET執行時,然後通過ASP.NET HTTP管道處理這些請求。
  對於我來說,瞭解一個平臺的內部工作機制總是會讓我感到一些滿足和安慰,如同洞察,可以幫助我寫出更好的程式。知道了工具有什麼用途,以及它們如何組裝成複雜框架的一部分,這些將會使你很容易的找到問題的解決方案,以及在你修改和除錯錯誤時,都顯得非常重要。這篇文章的目的就是從底層瞭解ASP.NET以及幫助你理解請求如何流入ASP.NET處理管道里。同時,你將會了解ASP.NET引擎的核心,以及一個Web請求如何在這裡結束。這裡講到的許多知識都是你日常工作中沒必要知道的,但是,如果你理解了ASP.NET如何把請求路由到應用程式的程式碼裡(通常比較高層次的),這將對你非常有用。
  
  注:整個ASP.NET引擎完全構建在託管程式碼裡,其所有的擴充套件性都是通過託管程式碼去構建。
  
  使用ASP.NET的大多數都比較熟悉WebForms和WebServices。這些高層次的實現,使得構建Web程式變得非常容易。ASP.NET被設計為驅動引擎,它把底層的介面提供給Web Server,為高層次Web應用程式的前端和末端提供了路由服務。WebForms和WebServices是建立在ASP.NET框架之上,有關HTTP處理的兩種最常用的方式。
  
  其實,在較低的層次上,ASP.NET也提供了足夠多的靈活性。HTTP執行時和請求管道提供了同樣的能力,可以構建類似於WebForms和WebServices的實現,當然,這些已經使用.NET託管程式碼實現了。如果你需要構建一個自定義HTTP處理平臺,而這個平臺要比WebForms所處的層次低一點,那麼你就會用到所有這些類似的功能。
  
  構建大多的Web介面,使用WebForms無疑是最容易的方法,但是,如果你想自定義一個內容處理器,或者需要對流入和流出的內容做特殊的處理,或者需要為一個應用程式定製一個應用伺服器介面,那麼使用這些低層次的處理或者模組將會得到更好的效能,以及可以在真正的請求處理中獲得更多的控制權。儘管那些高層次的實現,如:WebForms和WebServices已提供了類似的功能,但由於它們針對請求添加了太多的控制(導致效能下降)。所以你完全可以另闢佳境,在較低層次上處理這些請求。
  
  
  ASP.NET
是什麼?
  
  讓我們從最簡單的定義開始,ASP.NET是什麼?我通常喜歡用如下語句來描述ASP.NET。
  
  ASP.NET是完全使用託管程式碼處理Web請求的一個成熟引擎平臺。它不僅僅只是WebForms和WebServices。
  
  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.NET和IIS的介面是通過宿主在.NET裡的ISAPI擴充套件來通訊的,然後直達ASP.NET執行時。ISAPI提供了與Web Server通訊的核心介面,然後ASP.NET使用非託管程式碼獲取請求以及對客戶端請求發出響應。ISAPI提供的內容經由公共物件類似於HttpRequest和HttpResponse,通過一個設計優良的、可訪問的介面,以託管物件的方式暴露非託管資料。
  從瀏覽器到ASP.NET
  
  讓我們從一個典型的ASP.NET Web請求的生命週期的起點開始。使用者通過在瀏覽器中鍵入一個URL,點選一個超連結,提交一個HTML表單(一個post請求),或者一個客戶端程式呼叫基於ASP.NET的WebService(通過ASP.NET提供服務)。在伺服器端,IIS5或者IIS6將會收到這個請求。ASP.NET的底層通過ISAPI擴充套件與IIS通訊,然後,通過ASP.NET,這個請求通常被路由到一個帶有.aspx副檔名的頁面。但是,這個處理過程如何工作,則完全依賴於HTTP處理器(handler)的執行。這個處理器將被安裝用於處理指定的擴充套件。在IIS中,.aspx經由“應用程式擴充套件”被對映到ASP.NET ISAPI的dll檔案: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趨向於被當作橋介面使用,用於給高層次的工具提供應用服務型別的功能。例如,ASP和ASP.NET都是被當作冷聚變(cold fusion)構建於ISAPI之上。大多Perl、PHP和JSP的執行如同許多第三方解決方案一樣,可以在IIS執行。ISAPI是個非常好的工具,它給高層次的應用程式提供了高效能垂直訪問介面。這使得那些高層次的應用程式需要的資訊可以從ISAPI提供的資訊中提煉。在ASP和ASP.NET裡,引擎可以提煉ISAPI介面提供的表單裡的物件如:Request和Response,這些物件可以從ISAPI請求的資訊中讀取它們各自的內容。
  
  作為約定,ISAPI支援ISAPI擴充套件(extensions)和ISAPI過濾(filters)。擴充套件是請求處理介面,提供了跟Web Server輸入和輸出相關的邏輯處理。從本質上來說,它是一個事務介面。ASP和ASP.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站點的根目錄的屬性,選擇主目錄選項卡,然後檢視 配置|應用程式對映。
  
  
  圖1:IIS把不同的副檔名如.aspx對映到ASP.NET的ISAPI擴充套件。通過這種機制,在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檔案的操作是什麼呢?在IIS5和IIS6裡,這個請求又是如何到達ASP.NET執行時的呢?它們兩者的處理方式有沒有重大變化呢?圖2展示了一個大致的流程。
  
  
  圖2:站在比較高的角度,觀看請求從IIS到ASP.NET執行時的流程,然後直達請求處理管道。IIS5和IIS6與ASP.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.exe(IIS5)和w3wp.exe(IIS6)宿主在.NET執行時裡。ISAPI DLL通過底層的COM呼叫一小撮非託管型別的介面,其實,最終呼叫的是ISAPIRuntime派生類的例項。進入執行時的第一個登入點是未歸檔ISAPIRuntime類,它通過COM把介面IISAPIRuntime暴露給呼叫者。這些COM
  介面是底層的IUnknown,基於這些介面,就意味著從ISAPI擴充套件到ASP.NET之間的呼叫屬於內部呼叫。圖3是使用有名的反射工具Refector(http://www.aisto.com/roeder/dotnet/)看到的IISAPIRuntime介面的簽名。Refector是一個可以檢視和反編譯程式集的工具,使用它可以很容易的檢視元資料、反編譯程式碼,就像圖3中看到的那樣。使用它一步一步地探究處理的過程,這是個非常不錯的方法。
  
  
  圖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,然後把它作為基本的輸入和輸出介面,用於Request和Response物件。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展示了這裡的流程。
  
  
  圖4:把ISAPI的請求轉到ASP.NET通道需要呼叫很多沒有正式文件的類和介面,以及幾個工廠方法。每一個Web程式/虛擬目錄都執行在屬於自己的AppDomain裡。呼叫者將維護一個IISAPIRuntime介面的代理引用,負責觸發ASP.NET的請求處理。
  
  記住,ISAPI是多執行緒的,因此請求可以以多執行緒的方式穿過AppDomainFactory.Create()返回的物件引用。列表1展現了從IsapiRuntime.ProcessRequest方法反編譯得到的程式碼。這個方法接收一個ISAPI ecb物件和一個伺服器型別引數(這個引數用於指定建立何種版本的ISAPIWorkerRequest),這個方法是執行緒安全的,因此多個ISAPI執行緒可以同時安全的呼叫單個返回物件的例項。
  
  列表1: ProcessRequest請求進入.NET的登入點
  public int ProcessRequest(IntPtr ecb, int iWRType)
  {
  // ISAPIWorkerRequest從HttpWorkerRequest 繼承,這裡建立的是
  // ISAPIWorkerRequest派生類的一個例項
  HttpWorkerRequest request1 =
  ISAPIWorkerRequest.CreateWorkerRequest(ecb,iWRType);
  //得到請求的物理路徑 string text1 = request1.GetAppPathTranslated();
  //得到AppDomain的物理路徑 string text2 = HttpRuntime.AppDomainAppPathInternal;
  if (((text2 == null) || text1.Equals(".")) ||
  (string.Compare(text1, text2, true,
  CultureInfo.InvariantCulture) == 0))
  {
  HttpRuntime.ProcessRequest(request1);
  return 0;
  } //如果外部請求的AppDomain物理路徑和原來AppDomain的路徑不同,說明ISAPI維持
  //的AppDomain的引用已經失效了,所以,需要把原來的程式關閉,當有新的請求時,會
  //再次啟動程式。 HttpRuntime.ShutdownAppDomain("Physical path changed from " +
  text2 + " to " + text1);
  return 1;
  }
  
  
  這裡實際的程式碼並不重要,需要提醒的是,這裡的程式碼是通過反編譯.NET框架內的程式碼得到的,你永遠也不會和這些程式碼打交道,而且這些程式碼以後可能會有所變動。這裡的用意是揭示ASP.NET在底層發生了什麼。ProcessRequest接收了非託管引數ecb的引用,然後把它傳給了ISAPIWorkerRequest物件,這個物件負責建立當前請求的內容。如列表2所示。
  
  列表2: 一個ISAPIWorkerRequest 的方法
  // *** ISAPIWorkerRequest裡的實現程式碼
  public override byte[] GetQueryStringRawBytes()
  {
  byte[] buffer1 = new byte[this._queryStringLength];
  if (this._queryStringLength > 0)
  {
  int num1 = this.GetQueryStringRawBytesCore(buffer1,
  this._queryStringLength);
  if (num1 != 1)
  {
  throw new HttpException( "Cannot_get_query_string_bytes");
  }
  }
  return buffer1;
  }// *** 再派生於ISAPIWorkerRequest的類ISAPIWorkerRequestInProcIIS6的實現// *** 程式碼
  // *** ISAPIWorkerRequestInProcIIS6
  internal override int GetQueryStringCore(int encode, StringBuilder
  buffer, int size)
  {
  if (this._ecb == IntPtr.Zero)
  {
  return 0;
  }
  return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode,
  buffer, size);
  }
  
  System.Web.Hosting.ISAPIWorkerRequest繼承於抽象類HttpWorkerRequest,它的職責是建立一個抽象的輸入和輸出檢視,為Web程式的輸入提供服務。注意這裡的另外一個工廠方法CreateWorkerRequest,它的第二個引數用於指定建立什麼樣的工作請求物件(即ISAPIWorkerRequest的派生類)。這裡有3個不同的版本:ISAPIWorkerRequestInProc,ISAPIWorkerRequestInProcForIIS6,ISAPIWorkerRequestOutOfProc。當請求到來時,這個物件(指ISAPIWorkerRequest物件)將被建立,用於給Request和Response物件提供基礎服務,而這兩個物件將從資料的提供者WorkerRequest接收資料流。
  
  抽象類HttpWorkerRequest圍繞著底層的介面提供了高層的抽象(譯註:抽象的目的是要把資料的處理與資料的來源解藕)。這樣,就不用考慮資料的來源,無論它是一個CGI Web Server,Web瀏覽器控制元件還是你自定義的機制(用於把資料流入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物件表示當前活動請求的上下文,因為它包含了在請求生命週期裡你會用到的所有必需物件的引用,如:Request,Response,Application,Server,Cache。在請求處理過程的任何時候,你都可以使用HttpContext.Current訪問這些物件。
  
  HttpContext物件還包含了一個非常有用的列表集合,你可以使用它儲存有關特定的請求需要的資料。上下文(context)物件創建於一個請求生命週期的開始,在請求結束時被釋放。因此,儲存在列表集合裡的資料僅僅對當前的請求有效。一個很好的例子,就是記錄請求的日誌機制,在這裡,通過使用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根目錄),並且它們都是獨立處理請求的。
  
  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() +
  "
Thread Apartment: " +
  Thread.CurrentThread.ApartmentState.ToString();
  // *** 為了可以同時看到多個請求一起到達,故意放慢速度 Thread.Sleep(3000);
  }
  
  這是樣例程式的一部分,執行的結果如圖5所示。為了檢驗結果,你應該開啟兩個瀏覽器,輸入相同的地址,觀察那些不同的ID的值。
  
  
  
  圖5:同時執行幾個瀏覽器,你會很容易的看到AppDomains,application物件以及處理請求的執行緒之間內在的關係。當多個請求觸發時,你會看到執行緒和application的ID在改變,而AppDomain的ID卻沒有發生變化。
  
  你將會觀察到AppDomain ID一直保持不變,而執行緒和HttpApplication的ID在請求多的時候會發生改變,儘管它們會出現重複。這是因為HttpApplications是在一個集合裡面執行,下一個請求可能會再次使用同一個HttpApplication例項,所以有時候HttpApplication的ID會重複。注意,一個HttpApplication例項物件並不依賴於一個特定的執行緒,它們僅僅是被分配給處理當前請求的執行緒而已。
  
  執行緒由.NET的ThreadPool提供服務,預設情況下,執行緒模型為多執行緒單元(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頁面,注意觀察AppDomain的ID。然後在web.config裡做一些改動(增加一個空格,然後儲存),重新載入這個頁面(譯註:由於快取的影響可能會在原來的頁面上重新整理無效,需要先刪除快取再重新整理即可),你就會看到一個新的AppDomain被建立了。
  
  本質上,這些改變將引起Web程式重新啟動。對於已經存在於處理管道的請求,將繼續通過原來的管道處理。而對於那些新的請求,將被路由到新的AppDomain裡。為了處理這些“掛起的請求”,在這些請求超時結束之後,ASP.NET將強制關閉AppDomain甚至某些請求還沒有被處理。因此,在一個特定的時間點上,同一個HttpApplication例項在兩個AppDomain裡存在是有可能的,這個時間點就是舊的AppDomain正在關閉,而新的AppDomain正在啟動。這兩個AppDomain將繼續為客戶端請求提供服務,直到舊的AppDomain處理完所有的未處理的請求,然後關閉,這時候才會僅剩下新的AppDomain在執行。
  
  穿過ASP.NET管道
  
  HttpApplication負責請求的傳輸,通過觸發事件,通知應用程式正在發生的事情。這個是作為HttpApplication.Init()方法的一部分實現的(使用Refector檢視System.Web.HttpApplication.InitInternal和HttpApplication.ResumeSteps()的程式碼可以看到這一點)。在這個方法裡,建立和啟動了一系列事件,包括繫結事件處理器。Global.asax裡的事件處理器會自動對映到對應的事件,它們也可以對映到額外新增的HTTPModules,這些HTTPModules本質上是HttpApplication已釋出事件的一種擴充套件。
  
  通過在web.config裡註冊,HTTPModules和HttpHandlers可以被動態的載入,並且可以新增到事件鏈條上。HTTPModules實際上就是事件處理器,它可以鉤住指定HttpApplication的事件。而HttpHandlers就是一個端點,它可以被呼叫處理“應用程式級的請求處理”。
  
  HTTPModules和HttpHandlers將被載入,然後新增到呼叫鏈上作為HttpApplication.Init()方法呼叫的一部分。圖6展示了不同的事件,這些事件何時被觸發以及通道上的哪些部分會受到它們的影響。
  
  
  圖6:事件在ASP.NET HTTP管道里傳輸。HttpApplication物件的事件驅動請求在管道里傳輸。HTTPModules可以截獲這些事件,可以覆蓋或增強已存在的功能。
  
  HttpContextHttpModulesHttpHandlers
  HttpApplication本身並不知曉傳送給Web程式的資料。它僅僅是個訊息郵遞者,只負責事件之間的通訊。它觸發事件,然後通過傳遞HttpContext物件,把資訊傳送給被呼叫的方法。在之前我們提到,當前請求的資料是在HttpContext物件裡儲存。它提供了請求從開始到結束需要的所有資料。圖7展示了ASP.NET的管道之間的傳輸流程。注意,從請求的開始至結束,上下文物件(Context)都是你的夥伴,你可以在一個事件方法裡使用它儲存資料,然後在之後的事件方法裡獲取這些資料。
  
  
  圖7:ASP.NET管道在一系列事件介面之間傳輸請求,這提供了足夠的靈活性。HttpApplication擔當主容器,負責載入Web程式,當請求到來時觸發事件以及在管道之間傳輸請求。經過已配置的HTTP過濾和模組時,每一個請求都將遵循一個公有的路徑。過濾器可以檢查穿梭在管道里的每一個請求,而處理器允許實現應用程式的邏輯或者應用程式級的介面像WebForms和WebServices一樣。為了給程式提供輸入和輸出,上下文物件(Context)給請求提供了所需的資訊,它貫穿了請求生命週期的始終。
  
  ASP.NET管道一旦啟動,HttpApplication將逐一觸發事件,如圖6展示的那樣。每一個事件都將被觸發,如果事件綁定了事件處理器,那麼這些事件處理器將被呼叫,執行它們的任務。這個過程的主要目的是通過呼叫HttpHandler處理指定的請求。對於ASP.NET請求而言,HttpHandler是處理請求機制的核心,在這裡任意的應用程式級的程式碼被執行。記住,ASP.NET的頁面和Web Service都是HttpHandler的具體實現,在這裡,所有請求處理的核心功能被實現。HTTPModule則傾向於在分發給事件處理器之前或者之後對內容進行處理。在ASP.NET裡典型的預設操作有:鑑定(Authentication),處理前的快取操作以及各種處理後的編碼操作機制。
  
  關於HTTPModule和HttpHandler,這裡有很多有用的資訊,但為了保持這篇文章合理的尺度,我將僅僅講述關於它們一些簡短的、整體的看法。
  
  HttpModules
  
  伴隨著HttpApplication觸發的一系列事件,請求將會在管道之間穿梭。你已經看到了這些釋出的事件,在Global.asax裡都有對應事件的處理方法。這個方法(步驟)是程式(ASP.NET應用程式)攜帶的,儘管這個並不總是你需要的。如果你想構建一套通用的HttpApplication事件處理程式,並以外掛的形式新增到任意的Web程式裡。那麼你可以使用HTTPModule,它是可重複使用的,不需要新增任何實現程式碼就可以在其它程式裡使用,而你所做的僅僅在web.config裡註冊。
  
  模組本質上是過濾器,在功能上類似於ASP.NET請求級別的ISAPI過濾。對於每一個穿過ASP.NET的HttpApplication物件的請求,模組都允許在HttpApplication物件觸發的事件處理方法裡截獲這些請求。這些模組以類的形式儲存在外部程式集裡,可以在web.config裡配置,當程式啟動的時候載入。通過實現指定的介面和方法,模組就可以被新增到HttpApplication的事件鏈上。多個HttpModules可以鉤住相同的事件,事件發生的順序是它們在web.config裡宣告(配置)的順序。如下就是在web.config裡,一個模組的宣告。
  
  
  
  
  注意,在這裡你需要指定一個完整的型別名和一個不帶副檔名的程式集的名字。
  
  模組允許你檢視每一個傳入的Web請求,基於觸發的事件基礎上執行操作。模組是非常有用的,它可以修改請求,輸出響應的內容以及提供自定義的身份驗證,另外還可以在特定的程式裡,針對ASP.NET的每一個請求提供響應前處理和響應後處理。許多ASP.NET的特徵像身份驗證,會話引擎都是作為HTTP模組實現的。
  
  HttpModules感覺有點類似於ISAPI過濾器,是由於它們檢視進入ASP.NET程式的每一個請求,但它們侷限性於僅可以檢視對映到某一個ASP.NET程式或者虛擬目錄的請求,和對映到ASP.NET的請求。因此,你僅可以檢視所有的ASPX頁面或者任意其它自定義的已經對映到這個程式的副檔名(譯註:作者可能漏掉了ASMX,ASHX等,這裡的意思應該是所有的ASP.NET預設的副檔名)。但是,你不能檢視標準的.HTM或者影象檔案,除非你通過新增這些副檔名,明確的把它們對映到ASP.NET的ISAPI 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管道物件如Response和Request,因此你可以獲取輸入的資料等等。但緊記,某些物件現在可能不能使用,只有到這條鏈的後面的環節才會有效,
  
  在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請求。
  
  單一的,簡單的方法?可能太簡單了,是嗎?可是,一個簡單的介面,但它的實現可能並不簡單。記住,WebForms和WebServices都是作為HTTP處理器實現的。因此,在這個看似簡單的接口裡,過多的實現過程被隱藏了。關鍵是,事實上到現在,一個HTTP處理器可以訪問所有為了開始處理請求而被組建和配置起來的ASP.NET的內建物件。關鍵是,HttpContext物件提供了所有與請求相關的功能,可以獲取流入的資料和輸出資料到Web伺服器。
  對於HTTP處理器而言,所有的操作都通過簡單地呼叫ProcessRequest()方法執行。可以簡單到如下的樣子:
  public void ProcessRequest(HttpContext context)
  {
  context.Response.Write("Hello World");
  }
  一個完整的實現像WebForms頁面引擎那樣,可以根據HTML模版展現複雜的表單。所以,這裡的關鍵點是你想用這個簡單但功能強大的介面做什麼。
  
  因為對你而言,HttpContext物件是可以使用的,這樣你就可以訪問Request,Response,Session和Cache物件,因此你已經擁有了ASP.NET請求的所有特徵,可以自己做主如何處理使用者提交的資訊,然後給客戶端返回處理後產生的內容。記住,在一個ASP.NET請求的生命期內,上下文物件始終都是你的朋友。
  
  處理器的關鍵操作通常是往Response物件裡寫輸出資料,或者更確切的說,是往Response物件的OutputStream裡寫。這個就是真正返回到客戶端的輸出資料。在底層,由ISAPIWorkerRequest負責把OutputStream發回給ISAPI的ecb.WriteClient方法,因為ecb.WriteClient方法才是真正執行IIS產生輸出資料的。
  
  通過使用大量的處於高層的、基礎的框架介面,WebForms實現了一個HTTP處理器。但最後,WebForm的Render()方法卻簡單的使用了一個HtmlTextWriter物件,把最終的輸出傳送給context.Response.OutputStream物件而結束。因此,相當的奇妙,最終甚至一個高層次的工具像WebForm,也僅僅是在Response和Request物件之上的一個高層次的抽象。
  
  在這個時候,你可能想知道,是否需要從頭實現一個完整的HTTP處理器?畢竟WebForms已經提供了一個易用的HTTP處理器的實現,因此為什麼還要為這些大量底層的東西而煩惱,放棄這些已經提供的靈活性呢?
  
  WebForms是非常棒的,使用它可以生成複雜的HTML頁面和處理業務層的邏輯而這些都需要圖形化的設計和模版化的頁面。除此以外,WebForms引擎還可以完成更多的,需要豐富的表現層的任務。如果所有你想做的是:從系統讀取檔案,由執行的程式碼把它返回。這樣的任務,如果避開使用Web Forms頁面框架,代替的是直接處理檔案然後返回,相信後者才是更高效的方法。如果你做的事情僅僅是像從資料庫裡讀取圖片一樣,那麼完全沒有必要使用頁面框架來處理,因為這裡的確沒有Web UI需要你俘獲離開圖片的事件。
  
  在這裡找不到,組建一個頁面物件和會話物件以及俘獲頁面層上事件的理由。對於你的任務而言,這裡需要做的僅僅是執行程式碼,而這些與你手頭上的任務毫不相干。
  
  因此,這種情況下使用處理器會更加高效。處理器可以完成使用WebForms不可能完成的事情。比如:需要處理這樣的請求,它們沒有必要在磁碟上存在對應的物理檔案,這些被請求的路徑通常稱為虛擬的URL。為了使這樣的請求正常的工作,你需要確保已經在應用程式的擴充套件對話方塊(如圖一所示)裡關閉了“確認檔案是否存在”的複選框。
  
  對於內容提供者來說,這是通用的,如:動態的影象處理,XML服務,提供虛擬的URL使原來的URL重定向,下載管理等等,這些均不能使用WebForms實現。
  
  是否已經提供了足夠的底層知識?
  
  哎呀!我們終於繞著請求的處理週期回到了原地。儘管我在這裡沒有探討有關HTTP模組和HTTP處理器如何工作的更多細節,但仍舊提供了許多對你有幫助的底層資訊。挖掘這些資訊花費了我很多時間,通過了解ASP.NET在底層的工作模式,使我感到非常地滿足,希望它也可以給你帶來同樣的感受。
  
  在結束之前,還是讓我們來回顧一下,我在這篇文章中討論的從IIS到HTTP處理器的事件序列:
  l IIS得到一個請求
  l 查詢指令碼對映擴充套件,然後把請求對映到aspnet_isapi.dll檔案
  l 程式碼進入工作者程序(IIS5裡是aspnet_wp.exe;IIS6裡是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管道請求流程的幻燈片提供了依據。

本文轉自
http://www.cnblogs.com/rijing2004/archive/2007/09/14/howaspnetwork.html