摘要:了解為 ASP.NET Web 頁(yè)面建立的事件模型,以及 Web 頁(yè)面轉變?yōu)?HTML 過(guò)程中的各個(gè)階段。ASP.NET HTTP 運行時(shí)負責管理對象管道,這些對象首先將請求的 URL 轉換成 Page 類(lèi)的具體實(shí)例,然后再將這些實(shí)例轉換成純 HTML 文本。本文將探討那些作為頁(yè)面生命周期標志的事件,以及控件和頁(yè)面編寫(xiě)者如何干預并改變標準行為。
簡(jiǎn)介 對由 Microsoft? Internet 信息服務(wù) (IIS) 處理的 Microsoft? ASP.NET 頁(yè)面的每個(gè)請求都會(huì )被移交到 ASP.NET HTTP 管道。HTTP 管道由一系列托管對象組成,這些托管對象按順序處理請求,并將 URL 轉換為純 HTML 文本。HTTP 管道的入口是 HttpRuntime 類(lèi)。ASP.NET 結構為輔助進(jìn)程中的每個(gè) AppDomain 創(chuàng )建一個(gè)此類(lèi)的實(shí)例。(請注意,輔助進(jìn)程為每個(gè)當前正在運行的 ASP.NET 應用程序維護一個(gè)特定的 AppDomain。)
HttpRuntime 類(lèi)從內部池中獲取 HttpApplication 對象,并安排此對象來(lái)處理請求。HTTP 應用程序管理器完成的主要任務(wù)就是找到將真正處理請求的類(lèi)。當請求 .
aspx 資源時(shí),處理程序就是頁(yè)面處理程序,即從 Page 繼承的類(lèi)的實(shí)例。資源類(lèi)型和處理程序類(lèi)型之間的關(guān)聯(lián)關(guān)系存儲在應用程序的配置文件中。更確切地說(shuō),默認的映射集是在 machine.config 文件的 <httpHandlers> 部分定義的。但是,應用程序可以在本地的 web.config 文件中自定義自己的 HTTP 處理程序列表。以下這一行代碼就是用來(lái)為 .aspx 資源定義 HTTP 處理程序的。
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
擴展名可以與處理程序類(lèi)相關(guān)聯(lián),并且更多是與處理程序工廠(chǎng)類(lèi)相關(guān)聯(lián)。在所有情況下,負責處理請求的 HttpApplication 對象都會(huì )獲得一個(gè)實(shí)現 IHttpHandler 接口的對象。如果根據 HTTP 處理程序來(lái)解析關(guān)聯(lián)的資源/類(lèi),則返回的類(lèi)將直接實(shí)現接口。如果資源被綁定到處理程序工廠(chǎng),則還需要額外的步驟。處理程序工廠(chǎng)類(lèi)實(shí)現 IHttpHandlerFactory 接口,此接口的 GetHandler 方法將返回一個(gè)基于 IHttpHandler 的對象。
HTTP 運行時(shí)是如何結束這個(gè)循環(huán)并處理頁(yè)面請求的?ProcessRequest 方法在 IHttpHandler 接口中非常重要。通過(guò)對代表被請求頁(yè)面的對象調用此方法,ASP.NET 結構會(huì )啟動(dòng)將生成瀏覽器輸出的進(jìn)程。
真正的 Page 類(lèi) 特定頁(yè)面的 HTTP 處理程序類(lèi)型取決于 URL。首次調用 URL 時(shí),將構建一個(gè)新的類(lèi),這個(gè)類(lèi)被動(dòng)態(tài)編譯為一個(gè)程序集。檢查 .aspx 資源的分析進(jìn)程的結果是類(lèi)的源代碼。該類(lèi)被定義為
命名空間 ASP 的組成部分,并且被賦予了一個(gè)模擬原始 URL 的名稱(chēng)。例如,如果 URL 的終點(diǎn)是 page.aspx,則類(lèi)的名稱(chēng)就是 ASP.Page_aspx。不過(guò),類(lèi)的名稱(chēng)可以通過(guò)編程方式來(lái)控制,方法是在 @Page 指令中設置 ClassName 屬性。
HTTP 處理程序的基類(lèi)是 Page。這個(gè)類(lèi)定義了由所有頁(yè)面處理程序共享的方法和屬性的最小集合。Page 類(lèi)實(shí)現 IHttpHandler 接口。
在很多情況下,實(shí)際處理程序的基類(lèi)并不是 Page,而是其他的類(lèi)。例如,如果使用了代碼分離,就會(huì )出現這種情況。代碼分離是一項開(kāi)發(fā)技術(shù),它可以將頁(yè)面所需的代碼隔離到單獨的 C# 和 Microsoft Visual Basic? .NET 類(lèi)中。頁(yè)面的代碼是一組事件處理程序和輔助方法,這些處理程序和方法真正決定了頁(yè)面的行為??梢允褂?<script runat=server> 標記對此代碼進(jìn)行內聯(lián)定義,或者將其放置在外部類(lèi)(代碼分離類(lèi))中。代碼分離類(lèi)是從 Page 繼承并使用額外的方法的類(lèi),被指定用作 HTTP 處理程序的基類(lèi)。
還有一種情況,HTTP 處理程序也不是基于 Page 的,即在應用程序配置文件的 <pages> 部分中,包含了 PageBaseType 屬性的重新定義。
<pages PageBaseType="Classes.MyPage, mypage" />
PageBaseType 屬性指明包含頁(yè)面處理程序的基類(lèi)的類(lèi)型和程序集。從 Page 導出的這個(gè)類(lèi)可以自動(dòng)賦予處理程序擴展的自定義方法和屬性集。
頁(yè)面的生命周期 完全識別 HTTP 頁(yè)面處理程序類(lèi)后,ASP.NET 運行時(shí)將調用處理程序的 ProcessRequest 方法來(lái)處理請求。通常情況下,無(wú)需更改此方法的實(shí)現,因為它是由 Page 類(lèi)提供的。
此實(shí)現將從調用為頁(yè)面構建控件樹(shù)的 FrameworkInitialize 方法開(kāi)始。FrameworkInitialize 方法是 TemplateControl 類(lèi)(Page 本身從此類(lèi)導出)的一個(gè)受保護的虛擬成員。所有為 .aspx 資源動(dòng)態(tài)生成的處理程序都將覆蓋 FrameworkInitialize。在此方法中,構建了頁(yè)面的整個(gè)控件樹(shù)。
接下來(lái),ProcessRequest 使頁(yè)面經(jīng)歷了各個(gè)階段:初始化、加載視圖狀態(tài)信息和回發(fā)數據、加載頁(yè)面的用戶(hù)代碼以及執行回發(fā)服務(wù)器端事件。之后,頁(yè)面進(jìn)入顯示模式:收集更新的視圖狀態(tài),生成 HTML 代碼并隨后將代碼發(fā)送到輸出控制臺。最后,卸載頁(yè)面,并認為請求處理完畢。
在各個(gè)階段中,頁(yè)面會(huì )觸發(fā)少數幾個(gè)事件,這些事件可以由 Web 控件和用戶(hù)定義的代碼
截取并進(jìn)行處理。其中的一些事件是嵌入式控件專(zhuān)用的,因此無(wú)法在 .aspx 代碼級進(jìn)行處理。
要處理特定事件的頁(yè)面應該明確注冊一個(gè)適合的處理程序。不過(guò),為了向后兼容早期的 Visual Basic 編程風(fēng)格,ASP.NET 也支持隱式事件掛鉤的形式。默認情況下,頁(yè)面會(huì )嘗試將特定的方法名稱(chēng)與事件相匹配,如果實(shí)現匹配,則認為此方法就是匹配事件的處理程序。ASP.NET 提供了六種方法名稱(chēng)的特定識別,它們是 Page_
Init、Page_
Load、Page_DataBind、Page_PreRender 和 Page_Unload。這些方法被認為是由 Page 類(lèi)提供的相應事件的處理程序。HTTP 運行時(shí)會(huì )自動(dòng)將這些方法綁定到頁(yè)面事件,這樣,開(kāi)發(fā)人員就不必再編寫(xiě)所需的粘接代碼了。例如,如果命名為 Page_Load 的方法綁定到頁(yè)面的 Load 事件,則可省去以下代碼。
this.Load += new EventHandler(this.Page_Load);
對特定名稱(chēng)的自動(dòng)識別是由 @Page 指令的 AutoEventWireup 屬性控制的。如果該屬性設置為 false,則要處理事件的所有應用程序都需要明確連接到頁(yè)面事件。不使用自動(dòng)綁定事件的頁(yè)面性能會(huì )稍好一些,因為不需要額外匹配名稱(chēng)與事件。請注意,所有 Microsoft Visual Studio? .NET 項目都是在禁用 AutoEventWireup 屬性的情況下創(chuàng )建的。但是,該屬性的默認設置是 true,即 Page_Load 等方法會(huì )被識別,并被綁定到相關(guān)聯(lián)的事件。
下表中按順序列出了頁(yè)面的執行包括的幾個(gè)階段,執行的標志是一些應用程序級的事件和/或受保護并可覆蓋的方法。
表 1:ASP.NET 頁(yè)面生命中的關(guān)鍵事件
| 階段 | 頁(yè)面事件 | 可覆蓋的方法 |
| 頁(yè)面初始化 | Init | |
| 加載視圖狀態(tài) | | LoadViewState |
| 處理回發(fā)數據 | | 任意實(shí)現 IPostBackDataHandler 接口的控件中的 LoadPostData 方法 |
| 加載頁(yè)面 | Load | |
| 回發(fā)更改通知 | | 任意實(shí)現IPostBackDataHandler接口的控件中的RaisePostDataChangedEvent 方法 |
| 處理回發(fā)事件 | 由控件定義的任意回發(fā)事件 | 任意實(shí)現 IPostBackDataHandler 接口的控件中的 RaisePostBackEvent 方法 |
| 頁(yè)面顯示前階段 | PreRender | |
| 保存視圖狀態(tài) | | SaveViewState |
| 顯示頁(yè)面 | | Render |
| 卸載頁(yè)面 | Unload | |
以上所列的階段中有些在頁(yè)面級是不可見(jiàn)的,并且僅對服務(wù)器控件的編寫(xiě)者和要創(chuàng )建從 Page 導出的類(lèi)的開(kāi)發(fā)人員有意義。Init、Load、PreRender、Unload,再加上由嵌入式控件定義的所有回發(fā)事件,就構成了向外發(fā)送頁(yè)面的各個(gè)階段標記。