ASP.NET頁面生存周期關鍵事件及執行步驟
ASP.NET頁面生存周期介紹
一旦完全確定 HTTP 頁面處理程序類,ASP.NET 運行時就調用該處理程序的 ProcessRequest 方法以處理請求。通常情況下,無需更改此方法的實現方式,因為它是由 Page 類提供的。
此實現方法一開始就調用 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 方法 | |
處理回發事件 |
控件所定義的任何回發事件 |
實現了 IPostBackEventHandler 接口的任何控件的 RaisePostBackEvent 方法 |
頁面呈現前階段 |
PreRender |
|
保存視圖狀態 |
SaveViewState | |
呈現頁面 |
Render | |
卸載頁面 |
Unload |
在頁面級上,以上所列的某些階段是不可見的,并僅影響服務器控件編寫者和那些湊巧要創建從 Page 派生的類的開發人員。頁面向外界發送的活動信號僅包括 Init、Load、PreRender、Unload 以及嵌入式控件所定義的所有回發事件。
ASP.NET頁面生存周期:執行的各個階段
頁面生存周期中的第一個階段是初始化。這一階段的標志就是 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 事件。
如果在處理兩個不同的請求時某個敏感的屬性被修改,則頁面上可能有些控件需要完成某些任務。例如,如果在客戶端修改了某個文本框控件的文本,則該控件激發 TextChanged 事件。如果利用來自客戶端的值對該控件的一個或多個屬性進行修改,每個控件可以決定激發一個適當的事件。對控件而言,如果這些更改是至關重要的,則這些控件實現 IPostBackDataHandler 接口,在 Load 事件之后立即調用該接口的 LoadPostData 方法。通過編寫 LoadPostData 方法的代碼,一個控件可以確認自最近一次請求以來是否發生了任何關鍵的更改,并激發自己的更改事件。
頁面生存周期內的關鍵事件就是:它被調用來執行與客戶端上所激發的某個事件相關聯的服務器端代碼。當用戶單擊某個按鈕時,頁面回發數據。已發送值的集合中包含該按鈕(該按鈕啟動整個操作)的 ID。如果已知該控件實現了 IPostBackEventHandler 接口(按鈕和鏈接按鈕將實現此接口),則頁面框架調用 RaisePostBackEvent 方法。此方法所進行的操作取決于相應控件的類型。對于按鈕和鏈接按鈕,此方法查找 Click 事件處理程序并運行相關的委托。
在處理回發事件后,頁面就準備進行呈現。這一階段的標志是 PreRender 事件。各個控件可利用這個很好的時機,以便執行任何需要在保存視圖狀態和呈現輸出結果的前一刻完成的最后一些更新操作。下一個狀態為 SaveViewState,在這一狀態中所有控件以及頁面本身可以刷新自己的 ViewState 集合的內容。所得到的視圖狀態隨后得以序列化、進行哈希運算、進行 Base64 編碼并關聯到 __VIEWSTATE 隱藏字段。
通過重寫 Render 方法,即可更改各個控件的呈現機制。該方法獲取一個 HTML 編寫器對象,并使用該對象聚集所有將針對該控件生成的 HTML 文本。Page 類的 Render 方法的默認實現方式包括對所有成員控件的遞歸調用。對于每個控件,頁面都調用 Render 方法并將 HTML 輸出放入高速緩存。
一個頁面的最后生存標志就是 Unload 事件,該事件在頁面對象被解除之前發生。在此事件中,您應該釋放可能占用的任何關鍵資源(例如,文件、圖形對象、數據庫連接)。
終于,在此事件之后,瀏覽器收到 HTTP 響應數據包并顯示頁面。
【編輯推薦】