.NET ASP.NET 頁生命週期概述

ASP.NET 頁生命週期概述

ASP.NET 頁運行時,此頁將經歷一個生命週期,在生命週期中將執行一系列處理步驟。 這些步驟包括初始化、實例化控件、還原和維護狀態、運行事件處理程序代碼以及進行呈現。 瞭解頁生命週期非常重要,因爲這樣做您就能在生命週期的合適階段編寫代碼,以達到預期效果。

如果您要開發自定義控件,就必須熟悉頁生命週期,以便正確進行控件初始化,使用視圖狀態數據填充控件屬性以及運行控件行爲代碼。 控件的生命週期基於頁的生命週期,但是頁引發許多您需要在自定義控件中處理的事件。

常規頁生命週期階段

一般來說,頁要經歷下表概述的各個階段。 除了頁生命週期階段以外,在請求前後還存在應用程序階段,但是這些階段並不特定於頁。
生命週期的某些部分僅當頁處理爲回發時纔出現。 對於回發,部分頁回發過程中(當您使用 UpdatePanel 控件)與整頁回發過程中的頁生命週期是一樣。

階段 說明
頁請求 頁請求發生在頁生命週期開始之前。 用戶請求頁時,ASP.NET 將確定是否需要分析和編譯頁(從而開始頁的生命週期),或者是否可以在不運行頁的情況下發送頁的緩存版本以進行響應。
啓動 在啓動階段,將設置頁屬性,如 Request 和 Response。 在此階段,頁還將確定請求是回發請求還是新請求,並設置 IsPostBack 屬性。 頁還設置 UICulture 屬性。
初始化 頁初始化期間,可以使用頁中的控件,並將設置每個控件的 UniqueID 屬性。 如果需要,還會向頁應用於母版頁和主題。 如果當前請求是回發請求,則回發數據尚未加載,並且控件屬性值尚未還原爲視圖狀態中的值。
加載 加載期間,如果當前請求是回發請求,則將使用從視圖狀態和控件狀態恢復的信息加載控件屬性。
回發事件處理 如果請求是回發請求,則將調用控件事件處理程序。 之後,將調用所有驗證程序控件的 Validate 方法,此方法將設置各個驗證程序控件和頁的 IsValid 屬性。
呈現 在呈現之前,會針對該頁和所有控件保存視圖狀態。 在呈現階段中,頁會針對每個控件調用 Render 方法,它會提供一個文本編寫器,用於將控件的輸出寫入頁的 Response 屬性的 OutputStream 對象中。
卸載 完全呈現頁並已將頁發送至客戶端、準備丟棄該頁後,將引發 Unload 事件。 此時,將卸載頁屬性(如 Response 和 Request)並執行清理。

生命週期事件

在頁生命週期的每個階段中,頁將引發可運行您自己的代碼進行處理的事件。 對於控件事件,通過以聲明方式使用特性(如 onclick)或以使用代碼的方式,均可將事件處理程序綁定到事件。

頁還支持自動事件連接,即,ASP.NET 將查找具有特定名稱的方法,並在引發了特定事件時自動運行這些方法。 如果 @ Page 指令的 AutoEventWireup 特性設置爲 true,頁事件將自動綁定至使用命名約定 Page_事件(如 Page_Load 和 Page_Init)的方法。

下表列出了最常用的頁生命週期事件。 除了列出的事件外還有其他事件;不過,大多數頁處理方案不使用這些事件。 而是主要由 ASP.NET 網頁上的服務器控件使用,以初始化和呈現它們本身。 如果要編寫自定義 ASP.NET 服務器控件,則需要詳細瞭解這些事件。

頁事件 典型使用
PreInit
在啓動階段完成之後、初始化階段開始之前引發。
使用該事件來執行下列操作:
  • 檢查 IsPostBack 屬性來確定是不是第一次處理該頁。 此時還設置了 IsCallback 和 IsCrossPagePostBack 屬性。
  • 創建或重新創建動態控件。
  • 動態設置主控頁。
  • 動態設置 Theme 屬性。
  • 讀取或設置配置文件屬性值。

  • 注意: 如果請求是回發請求,則控件的值尚未從視圖狀態還原。如果在此階段設置控件屬性,則其值可能會在下一事件中被覆蓋。
Init 在所有控件都已初始化且已應用所有外觀設置後引發。 各個控件的 Init 事件在頁的 Init 事件之前發生。
使用該事件來讀取或初始化控件屬性。
InitComplete 在頁的初始化階段結束時引發。 Init 和 InitComplete 事件之間僅發生一個操作:開啓對視圖狀態更改的跟蹤。 視圖狀態跟蹤使控件可以保留所有以編程方式添加到 ViewState 集合的值。 在開啓視圖狀態跟蹤之前,所有添加到視圖狀態的值都會在回發期間丟失。 控件通常在引發其 Init 事件後立即開啓視圖狀態跟蹤。
使用此事件對要在下一個回發後務必保留的視圖狀態進行更改。
PreLoad 在頁爲自身和所有控件加載視圖狀態之後以及處理 Request 實例包括的回發數據之後引發。
Load Page 對象對 Page 對象調用 OnLoad 方法,然後以遞歸方式對每個子控件執行相同操作,直到加載完本頁和所有控件爲止。 各個控件的 Load 事件在頁的 Load 事件之後發生。
使用 OnLoad 事件方法來設置控件中的屬性並建立數據庫連接。
控件事件 使用這些事件來處理特定控件事件,如 Button 控件的 Click 事件或 TextBox 控件的 TextChanged 事件。
注意: 在回發請求中,如果頁包含驗證程序控件,請在執行任何處理之前檢查 Page 和各個驗證控件的 IsValid 屬性。
LoadComplete 在事件處理階段結束時引發。
對需要加載頁上的所有其他控件的任務使用該事件。
PreRender 在 Page 對象創建呈現頁所需的所有控件(包括組合控件的子控件)之後引發。 (爲此,Page 對象會針對每個控件和頁調用 EnsureChildControls。)
Page 對象在 Page 對象上調用 PreRender 事件,然後以遞歸方式對每個子控件執行相同操作。 各個控件的 PreRender 事件在頁的 PreRender 事件之後發生。
在呈現階段開始之前,使用該事件對頁或其控件的內容進行最後更改。
PreRenderComplete 在設置了 DataSourceID 屬性的每個數據綁定控件調用其 DataBind 方法之後引發。
SaveStateComplete 在爲頁和所有控件保存視圖狀態和控件狀態之後發生。 此時對頁或控件進行的任何更改都會影響呈現,但是在下一個回發中將不會檢索到這些更改。
Render 這不是事件;在處理的這個階段,Page 對象會在每個控件上調用此方法。 所有 ASP.NET Web 服務器控件都有一個用於寫出發送給瀏覽器的控件標記的 Render 方法。
如果創建自定義控件,通常要覆蓋此方法以輸出控件的標記。 不過,如果自定義控件只合並標準的 ASP.NET Web 服務器控件,不合並自定義標記,則不需要覆蓋 Render 方法。
用戶控件(.ascx 文件)自動合併呈現,因此不需要在代碼中顯式呈現該控件。
Unload 首先針對每個控件引發,繼而針對該頁引發。
在控件中,使用該事件對特定控件執行最後清理,如關閉控件特定數據庫連接。
對於頁自身,使用該事件來執行最後清理工作,如:關閉打開的文件和數據庫連接,或完成日誌記錄或其他請求特定任務。
**注意:**在卸載階段,頁及其控件已被呈現,因此無法對響應流做進一步更改。如果嘗試調用方法(如 Response.Write 方法),則該頁將引發異常。

其他的頁生命週期注意事項

各個 ASP.NET 服務器控件都有自己的生命週期,該生命週期與頁生命週期類似。 例如,控件的 Init 和 Load 事件在相應的頁事件期間發生。

雖然 Init 和 Load 都在每個控件上以遞歸方式發生,但它們的發生順序相反。 每個子控件的 Init 事件(還有 Unload 事件)在爲其容器引發相應的事件之前發生(由下到上)。 但是,容器的 Load 事件是在其子控件的 Load 事件之前發生(由上到下)。 母版頁的行爲方式類似於頁上的子控件:母版頁 Init 事件發生在頁的 Init 和 Load 事件之前,母版頁 Load 事件發生在頁的 Init 和 Load 事件之後。

當您創建從 Page 類繼承的類時,除了可以處理由頁引發的事件以外,還可以重寫頁的基類中的方法。 例如,可以覆蓋頁的 InitializeCulture 方法,以便動態設置區域性信息。 注意,在使用 Page_事件 語法創建事件處理程序時,將隱式調用基實現,因此無需在方法中調用它。 例如,無論是否創建 Page_Load 方法,始終都會調用頁基類的 OnLoad 方法。 但是,如果使用 override 關鍵字(在 Visual Basic 中爲 Overrides)覆蓋頁的 OnLoad 方法,則必須顯式調用基方法。 例如,如果在頁中覆蓋 OnLoad 方法,則必須調用 base.Load(在 Visual Basic 中爲 MyBase.Load)以運行基實現。

下圖顯示了 Page 類的一些最重要方法,您可以對其進行重寫以便添加在頁生命週期的特定點執行的代碼。 該圖還演示這些方法如何與頁事件和控件事件相關。 在該圖中方法和事件的順序爲從上到下排列,在每行中爲從左到右。

在這裏插入圖片描述

添加的控件的追趕事件

如果控件是在運行時動態創建的,或者是以聲明方式在數據綁定控件的模板中創建的,它們的事件最初與頁上的其他控件的事件並不同步。 例如,對於運行時添加的控件,Init 和 Load 事件在頁生命週期中的發生時間可能要比以聲明方式創建的控件的相同事件晚得多。 因此,從實例化那一刻起,動態添加的控件的事件就一直是在模板中的控件的事件之後發生,直到趕上該控件加入 Controls 集合時所對應事件爲止。

數據綁定控件的數據綁定事件

爲了幫助您理解頁生命週期與數據綁定事件之間的關係,下表列出了數據綁定控件(如 GridView、DetailsView 和 FormView 控件)中與數據相關的事件

控件事件 典型使用
DataBinding 在控件的 PreRender 事件(該事件在頁的 PreRender 事件之後發生)之後引發。 (這將應用於以聲明方式設置其屬性的 DataSourceID 控件。 否則,該事件在您調用控件的 DataBind 方法時發生。)
該事件標記將控件綁定到數據的進程的開始。 使用此事件可以手動打開數據庫連接,如果需要,還可以在運行查詢之前動態設置參數值。
RowCreated (僅限 GridView)或 ItemCreated(DataList、DetailsView、SiteMapPath、DataGrid、FormView、Repeater 和 ListView 控件) 在控件的 DataBinding 事件之後引發。
使用該事件來操作不依賴於數據綁定的內容。 例如,在運行時,可以以編程方式向 GridView 控件中的頁眉或頁腳行添加格式。
RowDataBound (僅限 GridView)或 ItemDataBound(DataList、SiteMapPath、DataGrid、Repeater 和 ListView 控件) 在控件的 RowCreated 或 ItemCreated 事件之後引發。
當此事件發生時,行或項中的數據可用,因此,可以在子數據源控件上格式化數據或設置 FilterExpression 屬性,以便顯示行或項中的相關數據。
DataBound 在數據綁定控件中的數據綁定操作結尾引發。 在 GridView 控件中,會爲所有行和子控件完成數據綁定。
使用此事件格式化數據綁定內容,或在依賴來自當前控件的內容的值的其他控件中啓動數據綁定。 (有關更多信息,請參見本主題中前面的「添加的控件的追趕事件」。)

嵌套的數據綁定控件
如果子控件已執行數據綁定,但其容器控件尚未執行數據綁定,則子控件中的數據與其容器控件中的數據可能不同步。 如果子控件中的數據根據容器控件中的數據綁定值執行了處理,這種情況則尤其顯著。

例如,假定有一個 GridView 控件,它的每一行顯示一條公司記錄,此外,還有一個 ListBox 控件包含公司管理者列表。 若要填充管理者列表,則需要將 ListBox 控件綁定到一個數據源控件(如 SqlDataSource),後者在查詢中使用公司 ID 來檢索公司管理者數據。

如果以聲明方式設置了 ListBox 控件的數據綁定屬性(如 DataSourceID 和 DataMember),ListBox 控件將嘗試在包含行的 DataBinding 事件期間綁定到其數據源。 不過,行的 CompanyID 字段直到 GridView 控件的 RowDataBound 事件發生後才包含值。 這種情況下,先綁定子控件(ListBox 控件),後綁定包含控件(GridView 控件),因此它們的數據綁定階段並不同步。

若要避免此種情況,需要將 ListBox 控件的數據源控件與 ListBox 控件自身放在同一模板項中,並且不要以聲明方式設置 ListBox 的數據綁定屬性。 而應在 RowDataBound 事件期間在運行時以編程方式設置它們,這樣,到 CompanyID 信息可用時 ListBox 控件纔會綁定到其數據。

登錄控件事件

Login 控件可以使用 Web.config 文件中的設置來自動管理成員資格驗證。 不過,如果應用程序要求您自定義控件的工作方式,或者您要了解 Login 控件事件與頁生命週期的關聯方式,可以使用下表中列出的事件。

控件事件 典型使用
LoggingIn 在回發期間,當頁的 LoadComplete 事件發生後引發。 此事件標記登錄過程的起點。
對必須在驗證過程開始前發生的任務使用該事件。
Authenticate 在 LoggingIn 事件之後引發。
使用該事件來覆蓋或增強 Login 控件的默認驗證行爲。
LoggedIn 在驗證用戶名和密碼後引發。
使用該事件來重定向到另一個頁或動態設置控件中的文本。 如果出現錯誤或驗證失敗,就不會發生該事件。
LoginError 在身份驗證失敗的情況下引發。使用該事件來設置控件中的問題解釋文本或將用戶定向到不同的頁。