原文地址:http://www.codeproject.com/KB/aspnet/lifecycle.aspx
[翻譯]asp.net 2.0中頁的生存周期(Lifecycle)和動態控制項
原文發布日期:2006.12.08
作者:Vivek Thakur
翻譯:webabcd
介紹
弄懂Page的生存周期(lifecycle)對於開發asp.net應用程式來說是非常重要的。很多.net初學者在處理動態載入控制項的時候都出現過回傳後丟值,丟狀態之類的問題。HTTP協議是無狀態的,這就是web程式不同與windows程式的一個天生的問題,如果要學習asp.net的話,Page的生存周期將是你最重要的基礎之一。事件的順序是怎樣的,特別是asp.net 2.0新增了母板頁後,使其變得更複雜了,本文的目的就是通過解釋每一個事件的順序及其用途讓你弄清楚這些事件到底是怎麼回事。
背景
在asp.net應用程式中,使用者總是要去請求一個.aspx頁的,讓我們感興趣的一件事就是在使用者訪問一個.aspx頁的時候,應用程式所屬的web伺服器到底做了哪些事呢?弄懂事件的順序將有助於我們在恰當的事件中做我們想做的事,也可以消除我們的一些混淆,比如把一些問題歸咎給web程式的無狀態之類的。
基礎:新的編譯模型和部分類(Partial Classes)
asp.net中的每一個web form都直接或間接的繼承自System.Web.UI.Page類。一個web from包括兩部分:一個是代碼檔案(WebForm.aspx.cs),它包括一些和page相關聯的事件和方法,另一個是aspx檔案,它包括一些HTML控制項聲明(在Visual Studio 2005的web應用程式中,我們還有一個名為WebForm.aspx.designer.cs的設計類)
在asp.net 2.0中,我們不需要再定義控制項變數,也不需要再在代碼檔案中寫一些事件委託,這一切都要歸功於部分類。在asp.net 1.x中,這些代碼都會自動的在InitializeComponent()裡產生。但是到了2.0版本,runtime將會建立一個部分類,這個類會包含aspx頁中的所有資訊。這將使得代碼檔案非常清晰並且易於管理。
這將消除VS2003中的代碼檔案和aspx頁面之間的名字相互聯絡的改變(如果我們要改變任意控制項的ID,都不得不改變aspx頁和代碼檔案)。在VS2005中所有控制項的事件都定義在aspx頁裡。所以代碼檔案中的事件委託和控制項變數將被清除,這是比先前的VS2003方便的地方。
頁的生存周期
瞭解頁的生存周期中的每一個請求是非常重要的,丟值、丟狀態的問題都可能是你對頁的生存周期瞭解不夠造成的。當然,如果你要在asp.net保留狀態的話,可以用諸如Application,Session,Cache,或者Cookies之類的
注意:asp.net 2.0中的檢視狀態由兩部分組成,控制項狀態和檢視狀態。詳細瞭解請參考這篇文章
http://msdn2.microsoft.com/en-us/library/1whwt1k7(VS.80).aspx
下面我們將按照web程式的代碼檔案中的各個事件的觸發順序來詳細的介紹它們
重點提示:除了Init()和Unload()之外的所有事件都是從最外面到最裡面被激發的。例如,一個使用者控制項的init事件在它的父頁類的Page_Init()事件之前被激發(譯者註:這是從裡到外)。
1. PreInit()
在這個頁面級的事件中,所有在設計時建立的控制項都將被用預設值做初始化。例如,如果你有一個Text屬性值為“Hello”的TextBox控制項,則此時這個屬性被設定。我們也可以在這裡動態建立控制項。
這個事件僅僅發生在頁層級的類中,使用者控制項和主版頁面沒有這個事件
下面的程式碼範例了如何重寫這個方法以增加你的自訂代碼protected override void OnPreInit(EventArgs e)
{
// custom code
base.OnPreInit(e);
}
注意,我們只能在PreInit()事件中動態設定themes
使用主版頁面時的特例
我們先要瞭解一個非常重要的知識點——主版頁面被處理的過程就相當於內容頁中的一個控制項。
所以如果一個頁有其相關聯的主版頁面的話,那麼在PreInit()事件裡頁中的所有控制項都不會被初始化。而只有在Init()事件開始之後,你才能直接存取這些控制項。為什嗎?
這個原因就是內容頁中的所有控制項都包含在“ContentPlaceholder”裡,而“ContentPlaceholder”其實就是主版頁面的一個子控制項。現在主版頁面被處理的過程就相當於內容頁中的一個控制項,我們早先提到過,除了Init()和Unload()之外的所有事件都是從最外面到最裡面被激發的。雖然頁的PreInit()是第一個被觸發的事件,但是使用者控制項和主版頁面是沒有這個事件的,所以在頁的Page_PreInit()方法中,主版頁面和使用者控制項都不會被初始化,而是在Init()事件之後
接下來讓我們來看一下Page_Init()事件之後控制項的階層
2. OnInit()
在這個事件裡,我們能讀出控制項的屬性(在設計模式中設定的)。但是我們不能讀出使用者佈建的值,因為得到使用者佈建的值是在LoadPostData()事件被激發之後。不過在這個事件中我們可以得到POST資料,如下string selectedValue = Request.Form[controlID].ToString();
3. LoadViewState
這個事件僅僅在回傳之後被激發(IsPostBack == true)。在這個事件中runtime從隱藏欄位中分解出view state並載入到所有啟用了view state的控制項。
4. LoadPostBackData
這個事件也僅僅是在回傳之後被激發。
在這個事件裡實現了IPostBackDataHandler介面的控制項從HTTP的POST資料中得到值。注意,textbox控制項不能從view state中獲得值,而是在此事件中從POST資料中獲得值。所以即使有些控制項沒有啟用view state,只要它實現了IPostBackDataHandler介面就可以從HTTP的POST資料中得到值。
另一個重要的知識點是如果我們有一個DropDownList控制項並動態給它增加一些選擇項,那麼runtime將不能得到這些值除非啟用了view state(即使控制項繼承自IPostBackDataHandler介面)。這個原因就是在HTTP的POST資料中的每一個控制項只能有一個值,並且POST資料中的所有值都不會被儲存,除了使用view state。
5. Page_Load
這是最常用的方法了,而且是一些開發新手放置他們代碼的第一個地方,有些新手們往往認為這就是Page類第一個觸發的方法。這個方法是混淆我們Page生存周期的罪魁禍首之一。
注意:如果頁裡有任何使用者控制項的話,那麼使用者控制項的Load方法將在頁類的Load方法之後被觸發。這個原因早先已經解釋過了,除了Init()和Unload()之外的所有事件都是從最外面到最裡面被激發的。所以頁的Page_Load()之後,頁內的其它控制項的Load方法才被觸發。
6. Control Event Handlers
事件處理(比如像Button1_Click()之類的)是定義在ASPX頁面中的,有一些開發人員認為當單擊一個按鈕後會立即出發Button_Click() ,他們忘了在這個事件觸發之前首先要觸發Page_Load。
7. PreRender
如果我們想改變某一個控制項的值,這是最後的機會了
8. SaveViewState
控制項的ViewState被儲存在form的隱藏欄位中
9. Render
呈現
10. Unload
這是最後的清理操作
動態控制項
現在我們已經知道了頁的生存周期的重要事件,接下來讓我們關注一下如何建立以及保持動態產生控制項的狀態。有的時候我們需要動態產生控制項,比如我原來管理的一個酒店預訂的項目,使用者在一個TextBox裡輸入房間號,根據這個值動態產生一個使用者控制項來顯示該房間的詳細資料。
開發人員雖然能動態產生使用者控制項,但是卻不能儲存使用者控制項的狀態。當我看了代碼後,他們把產生控制項的代碼寫到了Button的Click事件裡。根據我們上面所討論的,Button_Click()在LoadViewState()和LoadPostData()之後觸發,而控制項的值是要在view state或POST資料中取得的。
所以除非在Page_Init()或Pre_Init()方法裡重新建立控制項(它們發生在LoadViewState和LoadPostData之前),這樣就可以在下一個事件裡獲得控制項的值
現在,如果把代碼寫到Page_Init()事件裡的話,將不能得到使用者在TextBox(它是一個靜態控制項)裡輸入的值。原因就在於這是Page_Init()事件,控制項的值被初始化為它們設計時的預設值,而不會得到使用者輸入的值
所以如果要在這裡訪問到使用者輸入的值話只有一個辦法,就是從POST資料中取值。代碼如下protected override void OnInit(EventArgs e)
{
// 通過Post資料得到使用者在TextBox裡輸入的值
string selectedValue ;
if(Request.Form["txtNoOfRooms"] != null)
selectedValue = Request.Form["txtNoOfRooms"].ToString();
// 動態產生控制項的代碼
base.OnInit(e);
}
注意:感謝ASP.NET論壇的Mike Banavige,有了他的協助才讓我增加了這部分內容。如果你在Page_Load事件裡建立一個動態控制項,並把它添加到PlaceHolder或Panel裡(要開啟view state),那麼動態控制項將會維持它的狀態,即使它不是在Page_Init()中建立的,為什嗎?
原因就是控制項一旦被添加到頁的控制項樹裡,TrackViewState()方法就負責跟蹤其狀態。只要控制項被添加到控制項樹裡,這個方法就會被自動的觸發。因為這個原因,對控制項的任何修改(如添加item之類的)都應該在動態控制項被添加到頁的控制項樹之後來做,否則其狀態將丟失。請看如下代碼protected void Page_Load(object sender, EventArgs e)
{
// 建立一個DropDownList
DropDownList d = new DropDownList();
// TrackViewState()方法將被觸發去跟蹤這個DropDownList的狀態,所以其狀態將被保持
PlaceHolder1.Controls.Add(d);
if (!IsPostBack)
{
d.Items.Add("test1");
d.Items.Add("test2");
}
}
下面的代碼則不會保持動態控制項的狀態protected void Page_Load(object sender, EventArgs e)
{
// 動態建立一個控制項
dropdownDropDownList d = new DropDownList();
if (!IsPostBack)
{
d.Items.Add("test1");
d.Items.Add("test2");
}
// "test1"和"test2"值將丟失
PlaceHolder1.Controls.Add(d);
}
總結
我已經解釋了頁的生存周期的一些相關事件及其重要性,同時我也會不定期更新這篇文章以增加一些小提示和小技巧,此外也歡迎讀者指出本文的缺陷之處及修改建議
記住頁的整個生存周期的各個事件的順序是非常重要的,這樣我們就可以根據不同的需求在合適的位置寫出相應的代碼。