簡介
對由 Microsoft® Internet 信息服務 (IIS) 處理的 Microsoft® ASP.NET 頁面的每個請求都會被移交到 ASP.NET HTTP 管道。HTTP 管道由一系列托管對象組成,這些托管對象按順序處理請求,並將 URL 轉換為純 HTML 文本。HTTP 管道的入口是 HttpRuntime 類。ASP.NET 結構為輔助進程中的每個 AppDomain 創建一個此類的實例。(請注意,輔助進程為每個當前正在運行的 ASP.NET 應用程序維護一個特定的 AppDomain。)
HttpRuntime 類從內部池中獲取 HttpApplication 對象,並安排此對象來處理請求。HTTP 應用程序管理器完成的主要任務就是找到將真正處理請求的類。當請求 .aspx 資源時,處理程序就是頁面處理程序,即從 Page 繼承的類的實例。資源類型和處理程序類型之間的關聯關系存儲在應用程序的配置文件中。更確切地說,默認的映射集是在 machine.config 文件的 <httpHandlers> 部分定義的。但是,應用程序可以在本地的 web.config 文件中自定義自己的 HTTP 處理程序列表。以下這一行代碼就是用來為 .aspx 資源定義 HTTP 處理程序的。
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
擴展名可以與處理程序類相關聯,並且更多是與處理程序工廠類相關聯。在所有情況下,負責處理請求的HttpApplication 對象都會獲得一個實現 IHttpHandler 接口的對象。如果根據 HTTP 處理程序來解析關聯的資源/類,則返回的類將直接實現接口。如果資源被綁定到處理程序工廠,則還需要額外的步驟。處理程序工廠類實現IHttpHandlerFactory 接口,此接口的 GetHandler 方法將返回一個基於 IHttpHandler 的對象。
HTTP 運行時是如何結束這個循環並處理頁面請求的?ProcessRequest 方法在 IHttpHandler 接口中非常重要。通過對代表被請求頁面的對象調用此方法,ASP.NET 結構會啟動將生成瀏覽器輸出的進程。
特定頁面的 HTTP 處理程序類型取決於 URL。首次調用 URL 時,將構建一個新的類,這個類被動態編譯為一個程序集。檢查 .aspx 資源的分析進程的結果是類的源代碼。該類被定義為命名空間 ASP 的組成部分,並且被賦予了一個模擬原始 URL 的名稱。例如,如果 URL 的終點是 page.aspx,則類的名稱就是 ASP.Page_aspx。不過,類的名稱可以通過編程方式來控制,方法是在 @Page 指令中設置 ClassName 屬性。
HTTP 處理程序的基類是 Page。這個類定義了由所有頁面處理程序共享的方法和屬性的最小集合。Page 類實現IHttpHandler 接口。
在很多情況下,實際處理程序的基類並不是 Page,而是其他的類。例如,如果使用了代碼分離,就會出現這種情況。代碼分離是一項開發技術,它可以將頁面所需的代碼隔離到單獨的 C# 和 Microsoft Visual Basic® .NET 類中。頁面的代碼是一組事件處理程序和輔助方法,這些處理程序和方法真正決定了頁面的行為。可以使用 <script runat=server> 標記對此代碼進行內聯定義,或者將其放置在外部類(代碼分離類)中。代碼分離類是從 Page 繼承並使用額外的方法的類,被指定用作 HTTP 處理程序的基類。
還有一種情況,HTTP 處理程序也不是基於 Page 的,即在應用程序配置文件的 <pages> 部分中,包含了PageBaseType 屬性的重新定義。
<pages PageBaseType="Classes.MyPage, mypage" />
PageBaseType 屬性指明包含頁面處理程序的基類的類型和程序集。從 Page 導出的這個類可以自動賦予處理程序擴展的自定義方法和屬性集。
完全識別 HTTP 頁面處理程序類后,ASP.NET 運行時將調用處理程序的 ProcessRequest 方法來處理請求。通常情況下,無需更改此方法的實現,因為它是由 Page 類提供的。
此實現將從調用為頁面構建控件樹的 FrameworkInitialize 方法開始。FrameworkInitialize 方法是TemplateControl 類(Page 本身從此類導出)的一個受保護的虛擬成員。所有為 .aspx 資源動態生成的處理程序都將覆蓋 FrameworkInitialize。在此方法中,構建了頁面的整個控件樹。
接下來,ProcessRequest 使頁面經歷了各個階段:初始化、加載視圖狀態信息和回發數據、加載頁面的用戶代碼以及執行回發服務器端事件。之后,頁面進入顯示模式:收集更新的視圖狀態,生成 HTML 代碼並隨后將代碼發送到輸出控制台。最后,卸載頁面,並認為請求處理完畢。
在各個階段中,頁面會觸發少數幾個事件,這些事件可以由 Web 控件和用戶定義的代碼截取並進行處理。其中的一些事件是嵌入式控件專用的,因此無法在 .aspx 代碼級進行處理。
要處理特定事件的頁面應該明確注冊一個適合的處理程序。不過,為了向后兼容早期的 Visual Basic 編程風格,ASP.NET 也支持隱式事件掛鈎的形式。默認情況下,頁面會嘗試將特定的方法名稱與事件相匹配,如果實現匹配,則認為此方法就是匹配事件的處理程序。ASP.NET 提供了六種方法名稱的特定識別,它們是Page_Init、Page_Load、Page_DataBind、Page_PreRender 和 Page_Unload。這些方法被認為是由 Page類提供的相應事件的處理程序。HTTP 運行時會自動將這些方法綁定到頁面事件,這樣,開發人員就不必再編寫所需的粘接代碼了。例如,如果命名為 Page_Load 的方法綁定到頁面的 Load 事件,則可省去以下代碼。
this.Load += new EventHandler(this.Page_Load);
對特定名稱的自動識別是由 @Page 指令的 AutoEventWireup 屬性控制的。如果該屬性設置為 false,則要處理事件的所有應用程序都需要明確連接到頁面事件。不使用自動綁定事件的頁面性能會稍好一些,因為不需要額外匹配名稱與事件。請注意,所有 Microsoft Visual Studio® .NET 項目都是在禁用 AutoEventWireup 屬性的情況下創建的。但是,該屬性的默認設置是 true,即 Page_Load 等方法會被識別,並被綁定到相關聯的事件。
下表中按順序列出了頁面的執行包括的幾個階段,執行的標志是一些應用程序級的事件和/或受保護並可覆蓋的方法。
表 1:ASP.NET 頁面生命中的關鍵事件
階段 | 頁面事件 | 可覆蓋的方法 |
---|---|---|
頁面初始化 | Init | |
加載視圖狀態 | LoadViewState | |
處理回發數據 | 任意實現 IPostBackDataHandler 接口的控件中的 LoadPostData 方法 | |
加載頁面 | Load | |
回發更改通知 | 任意實現 IPostBackDataHandler 接口的控件中的RaisePostDataChangedEvent 方法 | |
處理回發事件 | 由控件定義的任意回發事件 | 任意實現 IPostBackDataHandler 接口的控件中的 RaisePostBackEvent方法 |
頁面顯示前階段 | PreRender | |
保存視圖狀態 | SaveViewState | |
顯示頁面 | Render | |
卸載頁面 | Unload |
以上所列的階段中有些在頁面級是不可見的,並且僅對服務器控件的編寫者和要創建從 Page 導出的類的開發人員有意義。Init、Load、PreRender、Unload,再加上由嵌入式控件定義的所有回發事件,就構成了向外發送頁面的各個階段標記。
頁面生命周期中的第一個階段是初始化。這個階段的標志是 Init 事件。在成功創建頁面的控件樹后,將對應用程序觸發此事件。換句話說,當 Init 事件發生時,.aspx 源文件中靜態聲明的所有控件都已實例化並采用各自的默認值。控件可以截取 Init 事件以初始化在傳入的 Web 請求的生命周期內所需的所有設置。例如,這時控件可以加載外部模板文件或設置事件的處理程序。請注意,這時視圖狀態信息尚不可用。
初始化之后,頁面框架將加載頁面的視圖狀態。視圖狀態是名稱/值對的集合,在此集合中,控件和頁面本身存儲了對所有 Web 請求都必須始終有效的全部信息。視圖狀態代表了頁面的調用上下文。通常,它包含上次在服務器上處理頁面時控件的狀態。首次在會話中請求頁面時,視圖狀態為空。默認情況下,視圖狀態存儲在靜默添加到頁面的隱藏字段中,該字段的名稱是 __VIEWSTATE。通過覆蓋 LoadViewState 方法(Control 類的受保護、可覆蓋方法),組件開發人員可以控制視圖狀態的存儲方式以及視圖狀態的內容映射到內部狀態的方式。
有些方法(如 LoadPageStateFromPersistenceMedium 以及其對應的SavePageStateToPersistenceMedium),可以用來將視圖狀態加載並保存到其他存儲介質(例如會話、數據庫或服務器端文件)中。與 LoadViewState 不同,上述方法只能在從 Page 導出的類中使用。
存儲視圖狀態之后,頁面樹中控件的狀態與頁面最后一次顯示在瀏覽器中的狀態相同。下一步是更新它們的狀態以加入客戶端的更改。處理回發數據階段使控件有機會更新其狀態,從而准確反映客戶端相應的 HTML 元素的狀態。例如,服務器的 TextBox 控件對應的 HTML 元素是 <input type=text>。在回發數據階段,TextBox 控件將檢索 <input> 標記的當前值,並使用該值來刷新自己內部的狀態。每個控件都要從回發的數據中提取值並更新自己的部分屬性。TextBox 控件將更新它的 Text 屬性,而 CheckBox 控件將刷新它的 Checked 屬性。服務器控件和 HTML 元素的對應關系可以通過二者的 ID 找到。
在處理回發數據階段的最后,頁面中的所有控件的狀態都將使用客戶端輸入的更改來更新前一狀態。這時,將對頁面觸發 Load 事件。
頁面中可能會有一些控件,當其某個敏感屬性在兩個不同的請求中被修改時,需要完成特定的任務。例如,如果TextBox 控件的文本在客戶端被修改,則此控件將觸發 TextChanged 事件。每個控件在其一個或多個屬性被修改為客戶端輸入的值時都可以決定觸發相應的事件。對於這些更改對其非常關鍵的控件,控件實現IPostBackDataHandler 接口,此接口的 LoadPostData 方法是在 Load 事件后立即調用的。通過對LoadPostData 方法進行編碼,控件將驗證自上次請求后是否發生了關鍵更改,並觸發自己的更改事件。
頁面生命周期中的關鍵事件是被調用以執行服務器端代碼的事件,此代碼與客戶端觸發的事件相關聯。當用戶單擊按鈕時,將回發頁面。回發值的集合中包括啟動整個操作的按鈕的 ID。如果控件實現 IPostBackEventHandler 接口(如按鈕和鏈接按鈕),頁面框架將調用 RaisePostBackEvent 方法。此方法的行為取決於控件的類型。就按鈕和鏈接按鈕而言,此方法將查找 Click 事件處理程序並運行相關的委托。
處理完回發事件之后,頁面就可以顯示了。這個階段的標志是 PreRender 事件。控件可以利用這段時間來執行那些需要在保存視圖狀態和顯示輸出的前一刻執行的更新操作。下一個狀態是 SaveViewState,在此狀態中,所有控件和頁面本身都將更新自己 ViewState 集合的內容。然后,將得到序列化、散列、Base64 編碼的視圖狀態,而且此視圖狀態與隱藏字段 __VIEWSTATE 相關聯。
通過覆蓋 Render 方法可以改變各個控件的顯示機制。此方法接受 HTML 書寫器對象,並使用此對象來積累所有要為控件生成的 HTML 文本。Page 類的 Render 方法的默認實現包括對所有成員控件的遞歸調用。對於每個控件,頁面都將調用 Render 方法,並緩存 HTML 輸出。
頁面生命中的最后一個標志是 Unload 事件,在頁面對象消除之前發生。在此事件中,您應該釋放所有可能占用的關鍵資源(例如文件、圖形對象、數據庫連接等)。
在此事件之后,也就是最后,瀏覽器接收 HTTP 響應數據包並顯示頁面。
ASP.NET 頁面對象模型因其事件機制而顯得格外新穎獨特。Web 頁面由控件組成,這些控件既可以產生豐富的基於 HTML 的用戶界面,又可以通過事件與用戶交互。以前,在 Web 應用程序的上下文中設置事件模型是件有挑戰性的工作。可我們驚奇的看到,客戶端生成的事件可以由服務器端的代碼來解決,而且只進行一些相應的修改后,此過程仍可以輸出相同的 HTML 頁面。
掌握這個模型對於了解頁面生命周期的各個階段,以及頁面對象如何被 HTTP 運行時實例化並使用是非常重要的