頁面的生存週期
介紹
弄懂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);
}
總結
我已經解釋了頁的生存週期的一些相關事件及其重要性,同時我也會不定期更新這篇文章以增加一些小提示和小技巧,此外也歡迎讀者指出本文的缺陷之處及修改建議
記住頁的整個生存週期的各個事件的順序是非常重要的,這樣我們就可以根據不同的需求在合適的位置寫出相應的程式碼。
介紹
弄懂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);
}
總結
我已經解釋了頁的生存週期的一些相關事件及其重要性,同時我也會不定期更新這篇文章以增加一些小提示和小技巧,此外也歡迎讀者指出本文的缺陷之處及修改建議
記住頁的整個生存週期的各個事件的順序是非常重要的,這樣我們就可以根據不同的需求在合適的位置寫出相應的程式碼。