ASP.NET頁面生命週期 (轉載)
#
事件或方法
功能
描述
1
Init 事件
頁面初始化
頁面生存週期中的第一個階段是初始化。當 Init 事件發生時,在 .aspx 原始檔中靜態宣告的所有控制元件都已例項化並取其預設值。應該注意的是,這時檢視狀態資訊還不可用。
2
LoadViewState 方法
載入檢視狀態
可以重寫 LoadViewState 方法來自定義狀態還原。
3
LoadPostData 方法
處理回發資料
處理傳入窗體資料。
4
Load 事件
載入頁面
頁面控制元件初始化完成並反映了客戶端的資料。
5
RaisePostDataChangedEvent 方法
回發更改通知
引發更改事件。
6
RaisePostBackEvent 方法
處理回發事件
處理引起回發的客戶端事件,並在服務上引發相應時間。
7
PreRender 事件
頁面預呈現
各個控制元件可利用這個很好的時機,以便執行任何需要在儲存檢視狀態和呈現輸出結果的前一刻完成的最後一些更新操作。
8
SaveViewState 方法
儲存檢視狀態
將ViewState屬性儲存到字串中,重寫 SaveViewState 方法可以修改 ViewState 屬性。
9
Render 方法
呈現頁面
重寫 Render 方法,即可更改各個控制元件的呈現機制。該方法獲取一個 HTML 編寫器物件,並使用該物件聚集所有將針對該控制元件生成的 HTML 文字。Page 類的 Render 方法的預設實現方式包括對所有成員控制元件的遞迴呼叫。對於每個控制元件,頁面都呼叫 Render 方法並將 HTML 輸出放入快取記憶體。
10
Dispose 方法
處置
是否對昂貴資源的引用。
11
Unload 事件
解除安裝頁面
Unload 事件是一個頁面的最後生存標誌,該事件在頁面物件被解除之前發生。在此事件中,您應該釋放可能佔用的任何關鍵資源(例如,檔案、圖形物件、資料庫連線)。在此事件之後,瀏覽器收到 HTTP 響應資料包並顯示頁面。
當一個頁面請求傳送到WEB伺服器時,不論該事件是由頁面提交還是由頁面重定向而激發的,頁面在其被建立到釋放的過程中都會執行一系列的事件。一個ASP.NET頁面從被建立到釋放的過程包含10個事件。
(1)物件初始化Init事件:頁面初始化的標誌是Init事件。頁面中的控制元件(包括頁面本身)都是在它們最初的Form中被首次初始化的。在成功建立頁面的控制元件樹後,對應用程式激發這個事件。當Init事件發生時,在.aspx原始檔中靜態宣告的所有控制元件都以例項化並取其預設值。應該注意到,這是還沒有檢視狀態資訊可供使用。雖然可以過載OnInit方法,但是系統並不保證這些控制元件例項是按照怎樣的順序被建立的。
(2)載入檢視:在初始化之後,頁面框架立即載入該頁面的檢視狀態(ViewState)。所謂檢視狀態就是一些名稱/值對的集合,例如可以儲存TextBox控制元件的ID和Text屬性值。它一般被用於在一個往返行程中存留資訊到伺服器,即參與HTTP請求與響應。
頁面檢視狀態被儲存在<input type=”hidden”>欄位中,做為_VIEWSTAE的值進行記錄。該檢視狀態通過ASP.NE自動維護。通過重寫LoadViewState方法元件,開發人員可控制如何還原檢視狀態以及如何將其內容影射到內部狀態。LoadViewState方法就是從ViewState中獲取上一次的狀態,並按照頁面的控制元件樹的結構,用遞迴來遍歷整個樹,將對應的狀態恢復到每一個控制元件上。
(3)處理回發資料:還原了檢視狀態,頁面樹種的各個控制元件的狀態就與瀏覽器上次呈現該頁面時這些控制元件所處的狀態相同。下一步需要更新這些控制元件的狀態以傳送給客戶端。
回發資料處理階段是各個控制元件有機會更新其狀態,以便準確的反映相應的HTML元素在客戶端的狀態。例如,一個伺服器TextBox控制元件對應的HTML元素是<input type=text>,在回發資料階段,TextBox控制元件將檢索<input>標記的當前值並用它重新整理其內部狀態。每個控制元件負責從以傳送的資料中提取相應值,並更新其某些屬性。TextBox控制元件將更新Text屬性,而CheckBox控制元件將重新整理其Checked屬性。伺服器控制元件和HTML元素之間的匹配關係由二者的ID確定。
頁框架將在每個提交資料的控制元件上實現IpostBackDataHandler介面,然後激發LoadPostData事件,通過頁面解析發現實現了IpostBackDataHandle介面的控制元件,這樣就能正確的回傳資料更新控制元件狀態。在識別控制元件時,ASP.NET通過匹配控制元件的唯一標示符來更新正確的控制元件,該識別符號具有名稱值集和中的名稱值對。這也就是在所有特定的頁中每個控制元件都需要一個唯一識別符號的原因之一。其他的步驟都由框架來完成,例如確定每個識別符號在環境中是否唯一以及控制元件的基本屬性等。
LostPostData方法的原型如下:
Public virtual bool LoadPostData(string postDatakey, NameValueCollection postCollection)
PostDataKey是標識控制元件的關鍵字,可以理解為控制元件的ID,postCollection是包含回發資料的集合,可以理解為檢視狀態值。該方法返回一個bool值,如果是true,則表示控制元件狀態因回發而更改;否則返回false。頁框架會更跟蹤所有返回true的控制元件並在這些控制元件上呼叫RaisePostDataChangeEvent事件。
LoadPostData方法是由System..Web.WebControls.Control定義的,而新增的每一個伺服器控制元件也是從System..Web.WebControls.Control繼承的,所以對於資料的回發處理並不需要干預。
(4)載入頁面Load:在回發資料處理階段結束時,頁面中的所有控制元件都根據客戶端上所輸入的更改來更新的狀態。此時,對頁面激發OnLoad事件。對於這個事件,相信大多數朋友都會比較熟悉,用Visual Studio.Net生成的頁面中的Page_Load方法就是響應Load事件的方法,對於每一次請求,Load事件都會觸發,Page_Load方法也就會執行。可以利用該方法執行一些頁面初始化,例如準備好資料庫的連線字串。在事件引用中,為了提高效能,通常使用Page類的IsPostBack屬性判斷是不是資料回發。
(5)回發更改通知RaisePostDataChanged:如(3)所述,在所有實現了IpostBackDataHandler介面的控制元件被正確的回傳資料更新後,每個控制元件都有一個布林值的標識,標識其自上一次提交後改控制元件的資料是被更改還是保持其值。然後ASP.NET通過搜尋頁來尋找任何顯示控制元件資料被更改的標識並激發RaisePostDataChanged。RaisePostDataChanged事件直到Load事件發生後,所有控制元件被更新後才激發。這保證了在控制元件被回傳資料更新前,其他控制元件的資料在RaisePostDataChanged事件中沒有被手動更改過。雖然也可以在Page的基礎上自己定義資料更改的事件,但通常這個事件由太大用處。
(6)處理回發事件RaisePostBackEvent:當回傳更新導致資料改變而引發伺服器端事件後,引發回傳的物件會在RaisePostBackEvent事件中被處理。這種引發回傳的物件往往是一個按鈕被單擊或者其狀態改變而引發回傳的控制元件。例如Button觸發樂Onclick事件、客戶端修改了某個文字框的文字、同時將AutoPostBack設定為true、觸發TextChanged事件等。
很多程式碼都在這個事件中執行,因為這是控制事件驅動邏輯的理想位置。為了保證呈現到瀏覽器的資料的正確性,在一系列的回傳事件後,RaisePostBackEvent事件最終被激發。基於一致性考慮,會傳中改變的控制元件直到這個函式被執行後才被更新。在實際的ASP.NET開發工作中要做的工作就是在此事件發生前處理程式碼。
(7) 預呈現PreRender:在處理回發事件後,頁面就準備進行呈現。這一階段的標誌是PreRender事件。各個控制元件可利用這個很好的時機,以便執行任何需要在儲存檢視狀態和呈現輸出結果的前一刻完成得最後一些更新操作。最終請求的處理都會轉變為發揮伺服器的響應,預呈現這個階段就是執行在最終呈現之前所做的狀態的更改,因為在呈現一個控制元件之前,必須更具它的屬性來產生HTML,比如Style屬性。這是典型的例子,這預呈現之前,可以更改一個控制元件的Style,當執行預呈現時,就可以把Style儲存下來,做為呈現階段顯示HTML的樣式資訊。
(8)儲存狀態SaveViewState:下一個狀態為SaveViewState,在這一狀態中所有控制元件以及頁面本身可以重新整理自己的SaveState集合的內容。所得到的檢視狀態隨後得以序列化、進行雜湊運算、進行Base64編碼並關聯到VI-EMSTATE隱藏自端。
(9)呈現檢視Render:到這裡,實際上頁面對請求的處理基本就告一段落了,在Render事件中,也呼叫物件是它們呈現為HTML,然後也收集HTML傳送給客戶。客戶接收到HTML標記後進行重組,最終顯示給客戶。當Render事件被過載時,開發者可以為瀏覽器建立定值的HTML,此時頁面建立的任何HTML都還沒有生效。Render方法用HtmlTextWriter物件做引數並由它產生HTML送給瀏覽器。這主要用於自定義控制元件的開發。
(10)處置Disposed:執行銷燬控制元件前的所有最終清理操作。在此階段必須釋放對昂貴資源的引用,如記憶體的退出、資料庫的連線等。
(11)解除安裝Unload:一個頁面的最後生存標誌就是Unload事件,該事件在頁面物件被解除之前發生。在此事件中,可以呼叫Dispose方法儘可能釋放佔用的任何關鍵資源(例如,檔案、圖形物件以及資料庫連線)。
////////////////////////////////////////////////////////////////////////////////////////////////////////////
一個Page處理的過程講解
Page類的派生關係
Page是從TemplateControl中派生的,TemplateControl從Control派生。他們的定義分別如下:
//Control,從object派生,實現了四個介面
public class Control : System.ComponentModel.IComponent,
System.IDisposable,
System.Web.UI.IParserAccessor,
System.Web.UI.IDataBindingsAccessor
{ }
//TemplateControl從Control派生,實現了介面System.Web.UI.INamingContainer
public abstract class TemplateControl : Control, System.Web.UI.INamingContainer
{ }
//Page從TemplateControl派生,實現了介面System.Web.UI. IHttpHandler
public class Page : TemplateControl, System.Web.IHttpHandler
{ }
而你編寫的Page,你在_aspx.cs檔案中編寫的類是從Page派生,這個繼承關係是你新增WebForm頁面時,Visual Studio .net幫你建好的。而aspx的頁面在執行時會被解釋為一個從_aspx.cs檔案中的類的派生類。(這裡需要補充資料)。
正 題
猜想
當IIS接收到aspx頁面一個請求,交給aspnet_isapi.dll處理。aspnet_isapi然後做以下的事情:
MyPage __p;
__p = new MyPage();
__p.ProcessRequest(System.Web.HttpContext.Current)
以上是我個人的估計,究竟事實是不是這樣,我會找Microsoft的工程師要答案!我在System.Web.dll中沒有找到處理產生Page例項的il,Page類的例項的建立可能是aspnet_isapi.dll作了,而aspnet_isapi.dll不是使用.net編寫的,其內部的執行究竟如何無從得知。
以下不是猜想!
ProcessRequest(HttpContext)方法
Page中的ProcessRequest(System.Web.HttpContext)方法是這樣的:
public virtual void ProcessRequest(Sunrise.Web.HttpContext context)
{
this.SetIntrinsics(context); //進行最基本初始化
this.ProcessRequest(); //處理請求
}
SetIntrinsics方法
從上面的程式碼可以看到,首先會呼叫SetIntrinsics(HttpContext context)方法,SetIntrinsics的作用是對Page的成員變數進行基本的初始化。SetIntrinsics如下:
//進行最基本初始化,設定_context、_request、_application、_cache的初值
private void SetIntrinsics(HttpContext context)
{
this._context = context;
this._request = context.Request;
this._response = context.Response;
this._application = context.Application;
this._cache = context.Cache;
if(this._clientTarget != null) {
if(this._clientTarget.Length >0) {
this._request.ClientTarget = this._clientTarget;
}
}
//調TempalateControl的HookUpAutomaticHandlers()方法初始化基本事件
//包括:Init、Load、DataBinding、PreRender、Unload、Error、
//AbortTransaction、CommitTransaction
this.HookUpAutomaticHandlers();
}
ProcessRequest方法
然後就開始執行ProcessRequest()方法,大致如下:
try
{
if(this.IsTransacted) //如果是事務狀態
{
this.ProcessRequestTransacted();
}
else //非事務狀態
{
this.ProcessRequestMain();
}
this.ProcessRequestEndTrace();
}
finally
{
//釋放資源,把_request、_response置為null,並執行UnloadRecursive(true)
this.ProcessRequestCleanup();
}
ProcessRequestMain方法
通過上面的程式碼可以知道,當一個Page是非事務模式時,響應ProcessRequestMain()。事實上,是事務模式時,也是呼叫ProcessRequestMain()。以下是關於ProcessRequestMain()方法的偽碼:
//step1 呼叫Control.InitRecursive()進行遞迴初始化 Control的OnInit()方法將會在此時被呼叫
this.InitRecursive(null);
//如果是回傳狀態
if(this.IsPostBack)
{
//step2 裝載ViewState
///裝載Page的ViewState,Control的LoadViewState()方法將會在這裡被呼叫
this.LoadPageViewState();
//step3 處理回傳的資料,如果Control實現了System.Web.UI.IPostBackDataHandler,
//LoadPostData()方法在此時被呼叫
this.ProcessPostData(this._requestValueCollection, true);
}
//step4 呼叫Control.LoadRecursive()方法裝載SubControls
//Control的Load()方法在此時被呼叫
this.LoadRecursive();
//如果是回傳狀態
if(this.IsPostBack)
{
//step 5 再次處理未處理完的回傳資料,
//注意第二次呼叫時第二個引數是false,而第一次是true
//第二次處理回傳資料的作用可能是用來處理在OnLoad中建立的Control的資料,??
this.ProcessPostData(this._leftoverPostData, false);
//step6 響應資料更改事件,
//如果Control實現了System.Web.UI.IPostBackDataHandler介面,
//Control的RaisePostDataChangedEvent()方法在此時被呼叫
this.RaiseChangedEvents();
//step7 響應回傳事件 __dopostback()
//如果Control實現了System.Web.UI.IPostBackEventHandler介面,Control的RaisePostBackEvent()方法在此時執行
this.RaisePostBackEvent(this._requestValueCollection);
//step8 呼叫Control的PreRenderRecursiveInternal()方法,
//Control的OnPreRender()方法在此時被呼叫
this.PreRenderRecursiveInternal();
}
//step9 BuiltTree 構建ViewState,
//ViewState使用通過LosFormatter對ViewState進行編碼,儲存在Trace中
this.BuildProfileTree("ROOT",this.EnableViewState);
//step10 儲存Page的ViewState
//Control的SaveViewState()方法
this.SavePageViewState();
//step11 輸出
//Control的Render()方法在此時被呼叫
this.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));
ProcessRequestMain方法圖示
Init
Load ViewState
Load PostData
Load
Load PostData
RaiseChangeEvents
RaisePostBackEvents
BuildProfileTree (viewstate 編碼)
SaveViewState
Render
Dispose
Unload
此時產生HTML程式碼
OnInit()方法被呼叫
OnLoad()方法被呼叫
處理回傳資料
再次處理回傳資料
響應資料修改事件,如TextBox的TextChagned
響應回傳事件,客戶端的__dopostback
ProcessRequestTransacted方法
事實上,在事務狀態下,也是呼叫ProcessRequestMain()的,我們來看一下ProcessRequestTransacted的程式碼:
bool V_0;
System.Web.Util.TransactedCallback V_1;
V_0 = false;
//建立事務回撥的delegate
V_1 = new System.Web.Util.TransactedCallback(this.ProcessRequestMain);
//在事務環境下呼叫ProcessRequestMain()
System.Web.Util.Transactions.InvokeTransacted(
V_1,
(System.EnterpriseServices.TransactionOption)(this._transactionMode),
ref V_0
);
if(V_0) {
this.OnAbortTransaction(System.EventArgs.Empty); //終止事務
}
Else {
this.OnCommitTransaction(System.EventArgs.Empty); //提交事務
}
InitRecursive方法
在ProcessRequestMain 方法中,首先執行Control的InitRecursive方法。在InitRecursive中,OnInit方法被執行。具體如下:
internal void InitRecursive(System.Web.UI.Control namingContainer) //ok
{
string V_0;
int V_1;
int V_2;
System.Web.UI.Control V_3;
if(this._controls != null)
{
//flag[128]表示是否實現了System.Web.UI.INamingContainer介面
if(this.flags[128]) {
namingContainer = this;
}
//設定_controls為只讀,並返回原來的_readOnlyErrorMsg,儲存在V_0
V_0 = this._controls.SetCollectionReadOnly("Parent_collections_readonly");
V_1 = this._controls.Count;
for(V_2 = 0;V_2 < V_1; V_2++)
{
V_3 = this._controls[V_2];
V_3._namingContainer = namingContainer;
if(namingContainer != null)
{
if((V_3._id == null) && (!(V_3.flags[64])) )
{
this.GenerateAutomaticID();
}
}
V_3._page = this._page;
V_3.InitRecursive(namingContainer);
}
//回覆_controls原來的狀態
this._controls.SetCollectionReadOnly(V_0);
this._controlState = Sunrise.Web.UI.ControlState.Initialized;
//OnInit方法在此時被呼叫
this.OnInit(System.EventArgs.Empty);
this.TrackViewState();
}
}
LoadPageViewState方法
在ProcessRequestMain 方法中,執行InitRecursive方法進行初始化後,如果Page的IsPostBack屬性值為True,則會接著執行LoadPageViewState方法。
LoadPageViewState應該結合來SavePageViewState來看,這樣更容易理解。具體如下:
internal void LoadPageViewState()
{
System.Web.UI.Triplet V_0;
string V_1;
//獲得使用LosFormater反序列化(Deserialize)得到的物件
V_0 = (System.Web.UI.Triplet)this.LoadPageStateFromPersistenceMedium();
if(V_0 != null)
{
V_1 = (string)V_0.First;
int.Parse(V_1,System.Globalization.NumberFormatInfo.InvariantInfo);
this.GetTypeHashCode();
//判斷V_1和Page呼叫GetTypeHashCode()方法獲得的值是否相等,
//並把比較結果儲存在_fPageLayoutChanged ??
this._fPageLayoutChanged = (int.Parse(V_1,System.Globalization.NumberFormatInfo.InvariantInfo) != this.GetTypeHashCode());
if(!(this._fPageLayoutChanged))
{
//呼叫Control.LoadViewStateRecursive()方法,
//遞迴裝載ViewState,
//Control的LoadViewState()方法將會在這裡被執行
this.LoadViewStateRecursive(V_0.Second);
this._controlsRequiringPostBack = (System.Collections.ArrayList)V_0.Third;
}
}
}
LoadViewStateRecursive方法
通過觀察LoadPageViewState的程式碼可以得知,LoadPageViewState中執行LoadViewStateRecursive來進行Control的LoadViewState過程。
LoadViewStateRecursive的過程大致是這樣,把引數savedState轉換為Triplet物件,使用Triplet物件的First做引數來執行LoadView方法,然後使用Triplet物件的Second和Third對Controls進行LoadViewStateRecursive。
這個方法應該參照SaveViewStateRecursive一起閱讀,這樣更方便理解。如下:
internal void LoadViewStateRecursive(object savedState)
{
System.Web.UI.Triplet V_0;
System.Collections.ArrayList V_1;
System.Collections.ArrayList V_2;
Sunrise.Web.UI.ControlCollection V_3;
int V_4;
int V_5;
int V_6;
int V_7;
//如果引數為null或EnableViewState為False
if((savedState == null) || (this.flags[4]) ))
//flags[4]表示EnableViewState: true時,EnableViewState為false,false時EnableViewState為true
{ return; }
//轉換為Triplet物件,Triplet有三個public的Fields:First、Second、Third,使用Triplet表示使程式碼更直觀,而且更快
V_0 = (System.Web.UI.Triplet)savedState;
//如果control的Page不為null,而且this.Page.IsPostBack為True,執行LoadViewState方法
if((this.Page != null) && (this.Page.IsPostBack) )
{ this.LoadViewState(V_0.First); }
//對this.Controls中的Control進行LoadViewState
if(V_0.Second != null)
{
V_1 = (System.Collections.ArrayList)V_0.Second; //kes
V_2 = (System.Collections.ArrayList)V_0.Third; //values
V_3 = this.Controls;
V_4 = V_3.Count;
V_5 = V_1.Count;
for(V_6=0; V_6 < V_5; V_6 ++)
{
V_7 = (int)V_1[V_6];
if(V_7 < V_4)
{ V_3[V_7].LoadViewStateRecursive(V_2[V_6]); }
else
{
if(this._controlsViewState == null)
{
this._controlsViewState = new System.Collections.Hashtable();
}
this._controlsViewState[V_7] = V_2[V_6];
}
}
}
//將_controlState設定為ViewStateLoaded
this._controlState = Sunrise.Web.UI.ControlState.ViewStateLoaded;
}
LoadViewState方法
LoadViewState方法的是Protected,當你構建自己的Control時,可以過載它。此時你應該在你寫的LoadViewState方法裡執行base. LoadViewState(something)。Control的LoadViewState程式碼如下:
protected virtual void LoadViewState(object savedState)
{
object V_0;
if(savedState != null)
{
this.ViewState.LoadViewState(savedState);
V_0 = this.ViewState["Visible"];
if(V_0 != null)
{
// flags[16]表示Visible,false時,Visible為true,值為false時,Visible為true
this.flags[16] = (!((bool)V_0));
this.flags[32] = true; // flags[32]表示是否已經LoadView ??
}
}
}
ProcessPostData方法
當ProcessRequestMain執行了LoadPageViewState方法後,接著就是ProcessPostData,這個方法是用來處理回傳資料。這個方法被執行兩遍,分別在LoadRecursive之前和之後個執行一遍。第一次執行時,引數fBeforeLoad值為True,第二次為False。
//處理回傳的資料
//當fBeforeLoad為true時,是第一次呼叫,fBeforeLoad為false時,為第二次呼叫
private void ProcessPostData(System.Collections.Specialized.NameValueCollection postData, bool fBeforeLoad) //ok
{
string V_0;
Sunrise.Web.UI.Control V_1;
System.Web.UI.IPostBackDataHandler V_2;
bool V_3;
System.Collections.ArrayList V_4;
string V_5;
System.Web.UI.IPostBackDataHandler V_6;
bool V_7;
System.Collections.IEnumerator V_8;
System.IDisposable V_9;
if(this._changedPostDataConsumers == null)
{
this._changedPostDataConsumers = new System.Collections.ArrayList();
}
if(postData != null)
{
V_8 = postData.GetEnumerator();
try
{
while(V_8.MoveNext())
{
V_0 = (string)V_8.Current;
if(V_0 != null)
{
//判斷回傳資料是否在系統PostFields內,
//s_systemPostFields包括__EVENTTARGET、__EVENTARGUMENT、__VIEWSTATE
if(!s_systemPostFields.Contains(V_0))
{
V_1 = this.FindControl(V_0); //在Page中查詢匹配的Control
if((V_1 == null) //如果找不到,將資料儲存在_leftoverPostData中
&& (fBeforeLoad) )
{
if(this._leftoverPostData == null)
{
this._leftoverPostData = new System.Collections.Specialized.NameValueCollection();
}
this._leftoverPostData.Add(V_0,null);
}
else //如果找到了……
{
//如果找到的結果實現了System.Web.UI.IPostBackDataHandler介面,
//呼叫RegisterRequiresRaiseEvent方法
//賦值給_registeredControlThatRequireRaiseEvent
if(!(V_1 is System.Web.UI.IPostBackDataHandler))
{
if(V_1 is System.Web.UI.IPostBackEventHandler)
{
this.RegisterRequiresRaiseEvent(
(System.Web.UI.IPostBackEventHandler)V_1
);
}
}
else//如果找到的結果沒有實現System.Web.UI.IPostBackDataHandler介面
{
V_2 = (System.Web.UI.IPostBackDataHandler)V_1;
V_3 = V_2.LoadPostData(V_0,this._requestValueCollection); //V_2裝載回傳資料
if(V_3) //如果V_2裝載回傳資料時發現數據已經被修改,將登記V_2在_changedPostDataConsumers中
{
this._changedPostDataConsumers.Add(V_2);
}
//在_controlsRequiringPostBack中移去V_0
//_controlsRequiringPostBack的值在LoadPageViewState()方法中被賦值
if(this._controlsRequiringPostBack != null)
{
this._controlsRequiringPostBack.Remove(V_0);
}
}
}
}
}
}
}
finally
{
V_9 = V_8 as System.IDisposable;
if(V_9 != null)
{
V_9.Dispose();
}
}
}
V_4 = null;
//處理_controlsRequiringPostBack中剩下的資料項
if(this._controlsRequiringPostBack != null)
{
V_8 = this._controlsRequiringPostBack.GetEnumerator();
try
{
while(V_8.MoveNext())
{
V_5 = (string)V_8.Current;
V_6 = (System.Web.UI.IPostBackDataHandler)this.FindControl(V_5);
if(V_6 != null)
{
//V_7裝載回傳資料
V_7 = V_6.LoadPostData(V_5,this._requestValueCollection);
//如果V_6裝載回傳資料時發現數據已經被修改,
//將登記V_6在_changedPostDataConsumers中
if(V_7) {
this._changedPostDataConsumers.Add(V_6);
}
}
else
{
if(fBeforeLoad) //如果是第一次呼叫,保留
{
if(V_4 == null)
{
V_4 = new System.Collections.ArrayList();
}
V_4.Add(V_5);
}
}
}
}
finally
{
V_9 = V_8 as System.IDisposable;
if(V_9 != null)
{
V_9.Dispose();
}
}
}
//如果是第一次呼叫,值為未處理的資料項,第二次呼叫值為null
this._controlsRequiringPostBack = V_4;
}
LoadRecursive方法
LoadRecursive方法的過程大致是這樣,先執行Load方法,然後對Controls執行LoadRecursive。
程式碼如下:
internal void LoadRecursive()
{ string V_0;
int V_1;
int V_2;
//在這裡執行OnLoad方法
this.OnLoad(System.EventArgs.Empty);
if(this._controls != null)
{
//設定_controls為只讀
V_0 = this._controls.SetCollectionReadOnly("Parent_collections_readonly");
V_1 = this._controls.Count;
for(V_2 = 0;V_2 < V_1; V_2++)
{ this._controls[V_2].LoadRecursive(); }
//回覆原來的狀態
this._controls.SetCollectionReadOnly(V_0);
}
this._controlState = Sunrise.Web.UI.ControlState.Loaded;
}
RaiseChangedEvents方法
如果Control實現了System.Web.UI.IPostBackDataHandler介面,Control的RaisePostDataChangedEvent將會在這裡被呼叫。需要的響應的RaisePostDataChangedEvent的典型例子是TextBox。TextBox的Text資料在客戶端可能被修改,如果修改了,需要獲得通知,以響應TextChanged事件。
internal void RaiseChangedEvents() //ok
{
int V_0;
System.Web.UI.IPostBackDataHandler V_1;
Sunrise.Web.UI.Control V_2;
if(this._changedPostDataConsumers != null)
{
for(V_0=0; V_0 < this._changedPostDataConsumers.Count; V_0++)
{
V_1 = (System.Web.UI.IPostBackDataHandler)this._changedPostDataConsumers[V_0];
V_2 = V_1 as Sunrise.Web.UI.Control;
//如果V_2是Page的SubControls,呼叫RaisePostDataChangedEvent()
if((V_2 != null) || (V_2.IsDescendentOf(this)))
{ V_1.RaisePostDataChangedEvent(); }
}
}
}
RaisePostBackEvent方法
當執行Client端JavaScript的__dopostback()函式時,將產生回傳事件。(此處需要補充資料)。回傳事件的原理是,Page產生的HTML程式碼中,會產生兩個隱藏域(Hidden),__EVENTTARGET和__ EVENTARGUMENT,不同的Control產生執行__dopostback()的方法不一致。呼叫的時候大多數是這樣:
<input type=hidden name=__EVENTTARGET>
<input type=hidden name=__ EVENTARGUMENT>
__dopostback(control_uniqueid, ‘’);
當Form提交的時候,RaisePostBackEvent中根據control_uniqueid來找到需要響應事件的Server Control。__ EVENTARGUMENT是事件的引數,引數在開發簡單的Server Control通常是不需要,在複雜的Server Control時才用。
例如System.Web.UI.WebControls.LinkButton。
private void RaisePostBackEvent(System.Collections.Specialized.NameValueCollection postData)
{
string V_0;
Sunrise.Web.UI.Control V_1;
string V_2;
if(this._registeredControlThatRequireRaiseEvent != null)
{
this.RaisePostBackEvent(_registeredControlThatRequireRaiseEvent, null);
return;
}
V_0 = postData["__EVENTTARGET"];
if((V_0 != null) && (V_0.Length >0))
{
V_1 = this.FindControl(V_0);
if((V_1 != null) && (V_1 is System.Web.UI.IPostBackEventHandler) )
{
V_2 = postData["__EVENTARGUMENT"];
this.RaisePostBackEvent((System.Web.UI.IPostBackEventHandler)V_1, V_2);
}
} else {
this.Validate();
}
}
PreRenderRecursiveInternal方法
Control的PrenRender方法會在這裡被執行。程式碼如下:
internal void PreRenderRecursiveInternal()
{
string V_0;
int V_1;
int V_2;
// flags[16]表示Visible,值為true時,Visible為false,值為false時,Visible為true
if(!(this.flags[16]))
{
this.EnsureChildControls();
//OnPreRender在這裡被執行
this.OnPreRender(System.EventArgs.Empty);
if(this._controls != null)
{
//設定只讀
V_0 = this._controls.SetCollectionReadOnly("Parent_collections_readonly");
V_1 = this._controls.Count;
for(V_2 = 0;V_2 < V_1; V_2++)
{ this._controls[V_2].PreRenderRecursiveInternal(); }
//回覆原來的狀態
this._controls.SetCollectionReadOnly(V_0);
}
}
this._controlState = Sunrise.Web.UI.ControlState.PreRendered;
}
BuildProfileTree 方法
構建ViewState的存放空間,放在Trace裡。(此處需要更詳細的說明)
protected void BuildProfileTree(string parentId, bool calcViewState)
{
int V_0;
int V_1;
int V_2;
calcViewState = calcViewState || (!(this.flags[4]));
if(calcViewState)
{
//計算LosFormatter會產生編碼結果的長度
V_0 = Sunrise.Web.UI.LosFormatter.EstimateSize(this.SaveViewState());
} else {
V_0 = 0;
}
this.Page.Trace.AddNewControl(
this.UniqueID,
parentId,
this.GetType().FullName,
V_0
);
if(this._controls != null)
{
V_1 = this._controls.Count;
for(V_2 =0; V_2 < V_1; V_2 ++)
{ this._controls[V_2].BuildProfileTree(this.UniqueID, calcViewState); }
}
}
SavePageViewState 方法
儲存Page的ViewState,LosFormatter的編碼結果放置於_viewStateToPersist中。此方法應該結合LoadPageViewState一起來分析。
使用Triplet和Pair儲存ViewState時常用的技巧。這裡使用了Triplet。
(需要補充關於LosFormatter的說明)
SavePageViewState和SavePageStateToPersistenceMedium的程式碼如下:
internal void SavePageViewState()
{
System.Web.UI.Triplet V_0;
int V_1;
if(!(this._needToPersistViewState))
{ return; }
V_0 = new System.Web.UI.Triplet();
V_1 = this.GetTypeHashCode();
//將數字轉換為字元
V_0.First = V_1.ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
//_registeredControlsThatRequirePostBack是一個ArrayList,
//其內容項 RegisterRequiresPostBack(System.Web.UI.Control control)來新增
V_0.Third = this._registeredControlsThatRequirePostBack;
if(this.Context.TraceIsEnabled)
{
this.Trace.AddControlViewstateSize(
this.UniqueID,
Sunrise.Web.UI.LosFormatter.EstimateSize(V_0)
);
}
//執行SaveViewStateRecursive()方法,把返回結果儲存賦給V_0.Second。
V_0.Second = this.SaveViewStateRecursive();
this.SavePageStateToPersistenceMedium(V_0);
}
protected virtual void SavePageStateToPersistenceMedium(object viewState)
{
this._viewStateToPersist = viewState;
}
SaveViewStateRecursive方法
在SavePageViewState方法中執行SaveViewStateRecursive方法來獲取ControlTree的SaveViewState的結果。
internal object SaveViewStateRecursive()
{
object V_0;
System.Collections.ArrayList V_1;
System.Collections.ArrayList V_2;
int V_3;
int V_4;
Sunrise.Web.UI.Control V_5;
object V_6;
System.Web.UI.Triplet V_7;
if(this.flags[4])
{ return(null); }
//獲得Control本身的SaveViewState()結果
V_0 = this.SaveViewState();
V_1 = null;
V_2 = null;
//獲取SubControls的SaveViewStateRecursive()結果
if(this._controls != null)
{
V_3 = this._controls.Count;
for(V_4=0; V_4<V_3; V_4++)
{
V_5 = this._controls[V_4];
V_6 = V_5.SaveViewStateRecursive();
if(V_6 != null)
{
if(V_1 == null)
{
V_1 = new System.Collections.ArrayList();
V_2 = new System.Collections.ArrayList();
V_1.Add(V_4);
V_2.Add(V_6);
}
}
}
}
V_7 = null;
//如果Control本身的SaveViewState()結果不為null,
//而且SuControls的SaveViewStateRecursive()結果不為null
if((V_0 != null) || (V_1 != null) )
{
V_7 = new System.Web.UI.Triplet(V_0, V_1, V_2);
}
return(V_7);
}
RenderControl方法
ProcessRequestMain()執行了PreRenderRecursive後,接著就執行RenderControl方法。RenderControl是在Control中定義的方法。RenderControl是執行Control的Render方法,輸出html文字。
public void RenderControl(System.Web.UI.HtmlTextWriter writer) //ok
{
System.Web.HttpContext V_0;
int V_1;
int V_2;
// flags[16]表示Visible,true時,Visible為false,值為false時,Visible為true
if(!(this.flags[16]))
{
//如果this._page不為null,……
V_0 = (this._page == null) ? null: this._page._context;
if(V_0 != null)
{
if(V_0.TraceIsEnabled)
{
V_1 =V_0.Response.GetBufferedLength();
//Render方法在這裡被執行
this.Render(writer);
V_2 = V_0.Response.GetBufferedLength();
V_0.Trace.AddControlSize(this.UniqueID, V_2 - V_1);
}
}
else
{ //Render方法在這裡被執行
this.Render(writer);
}
}
}
Render和RenderChildren
Render和RenderChildren都可以被過載。在System.Web.UI.WebControls名稱空間裡,大部分Control都是從System.Web.UI.WebControls.WebControl中派生的,WebControl過載Render方法,分拆成三個方法,RenderBeginTage、RenderContent和RenderEndTag。如果Cotnrol是從WebControl中派生,通常只需要過載RenderContent。
下面是Control中的程式碼:
protected virtual void Render(System.Web.UI.HtmlTextWriter writer)
{ this.RenderChildren(writer); }
protected virtual void RenderChildren(System.Web.UI.HtmlTextWriter writer) //ok
{ int V_0;
int V_1;
if(this._renderMethod != null)
{ this._renderMethod(writer,this); }
else
{
if(this._controls != null)
{
V_0 = this._controls.Count;
for(V_1=0; V_1<V_0; V_1 ++)
{
this._controls[V_1].RenderControl(writer);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
在以前寫個一篇關於ASP.NET頁面生命週期的草稿,最近又看了看ASP.NET,做個補充,看看頁面初始過程到底是怎麼樣的
下面是ASP.NET頁面初始的過程:
1. Page_Init();
2. Load ViewState;
3. Load Postback data;
4. Page_Load();
5. Handle control events;
6. Page_PreRender();
7. Page_Render();
8. Unload event;
9. Dispose method called;
下面對其中的一些過程作下描述:
1. Page_Init();
這個過程主要是初始化控制元件,每次頁面載入執行這個初始過程,包括第一次和以後的Postback(這裡說下Postback,其實就可以簡單理解成使用者點選SUBMIT按鈕之類的,把表單<Form>提交給伺服器,這就是一次postback),在這裡面可以訪問控制元件,但是這裡面的控制元件值不是我們期待的控制元件裡面的值,他只是一個控制元件的初始值(預設值),舉例: 比如一個TextBox1,我們填入了"哈哈",在點選SUBMIT提交了頁面後,在Page_Init()裡面,我們訪問到的TextBox1.Text不是我們的"哈哈",而是開始的""空字串,如果TextBox1在我們設計的時候提供了預設值,這裡訪問到的也就是提供的預設值,為什麼呢,這就要看下一個過程了.
對應的事件Page.Init
2. Load ViewState
這個過程是載入VIEWSTATE和Postback資料,比如我們上面的TextBox1,這時就賦了"哈哈",所以,在Post_Init()對控制元件賦值是無意義的,它都會在這個過程裡被改寫,當然第一次頁面載入例外,因為沒有VIEWSTATE資料。
沒有對應的事件
3.Load Postback data;
上面說了,Postback可以理解成使用者提交表單資料,所以這裡就是處理表單資料,當然這裡要設計到控制元件的設計,一般情況不會要我們自己處理這個過程,我們暫且略過. (在以前那篇關於ASP.NET頁面生命週期的簡單描述中,把這個過程和Load ViewState放在了一起,其實那是微軟提供的生命週期過程,這裡單獨提出來是為了讓大家明白這是一個單獨的過程)
沒有對應的事件
4. Page_Load();
這個過程也是每次頁面載入時一定會執行的,但是注意和Page_Init的區別,上面已經涉及了,這裡注意的是一般都會用到Page.IsPostBack,該值指示該頁是否正為響應客戶端回發而載入,或者它是否正被首次載入和訪問。
private void Page_Load(object sender, System.EventArgs e)
{
if(!Page.IsPostBack)
{ //第一次執行的CODE HERE }
else
{ //使用者提交FORM(即Postback)CODE HERE }
//每次這裡的都回執行CODE HERE
}
對應的事件Page.Load
5. Handle control events;
這個過程裡,相應具體的控制元件事件,比如private void ListBox1_SelectedIndexChanged(object sender, System.EventArgs e)事件等等
沒有對應的事件(我們自己的事件函式都包括在這個過程裡比如上面的ListBox1_SelectedIndexChanged)
6. Page_
預先呈遞物件,這裡是在向用戶程式呈現資料的倒數第二步,我估計提供這個過程的意義,也就是在這裡能對控制元件屬性等等要呈現給使用者的資料進行修改,這也是最後的修改,以前的修改(比如在Page_Init裡面)都可能被覆蓋.做完這了還會進行一個操作就是儲存狀態,即SaveViewState.
對應的事件時Page.PreRender
7. Page_Render();
大家可以在瀏纜器裡View->Source檢視到,每個頁面都有一個隱藏的<input>,這裡面的"__VIEWSTATE"就是我們伺服器寫回來的頁面狀態資訊,在這個之前,伺服器要呈現頁面(也就是構造HTML格式的檔案),就是從這個"__VIEWSTATE"裡面獲取的資料,當然大家也注意到了,這裡有個Page.Render事件,我們可以新增自己的處理程式碼,也就是說我們又可以更改資料,不過這裡推薦不要在這裡修改,既然提供了PreRender,就應該在裡面做最後的修改,當然這不是必須的,只是推薦!
對應的事件Page.Render
8. Unload event;
大家應該明白,當想伺服器請求一個物件的時候,就會在記憶體裡生成一個繼承頁面物件,也就是頁面的類,它繼承自System.Web.UI.Page.
當頁面物件從記憶體中解除安裝時發生,將觸發該事件.
對應的事件Page.Unload
9. Dispose method called;
銷燬所有的物件.當從記憶體釋放Page時發生,這是生存期的最後階段。可能第8和9似乎有些模糊,不過我也沒怎麼搞清楚,待研究!
對應的事件Dispose
以上就是ASP.NET頁面週期的描述。
注意上面灰色背景的文字,如果一個過程中有對應的事件,我們可以自己定義一個函式(當然先在MSDN中找到函式原型),然後在
InitializeComponent中向事件的連結串列上新增上去,像下面:
private void InitializeComponent()
{
this.Unload += new System.EventHandler(this.MainWebForm_Unload);
this.Load += new System.EventHandler(this.Page_Load);
this.Init += new System.EventHandler(this.Page_Init);
this.PreRender += new System.EventHandler(this.My_PreRender);
}
對於幾個沒有對應事件的過程,比如2.Load ViewState,我們可以過載Page的虛擬函式protected override void LoadViewState(object savedState);來新增自己的控制程式碼,不過切忌掉用基類的對應方法,比如:
protected override void LoadViewState(object savedState)
{
//自己處理VIEWSTATE
base.LoadViewState (savedState);
}