ZT Web Control 开发系列(一) 页面的生命周期
????可以認為一個Page是由很多的Control按照樹形結構組織的,而樹的根就是Page(一個實現了IHttphandler的Control), 整個Control樹的生命周期開始于一個Http請求,而終止于請求處理的結束。事實上在Http請求傳入到當前的Page的時候,之前已經經過了漫長的路程,如果對于整個Http請求的細節感興趣,可以查看MSDN中關于應用程序的生命周期。另外關于頁面的生存周期也有很多的文章可以參考,比如MSDN中ASP.NET 頁生命周期概述。
???? 我這里主要講講我個人的理解,如果有什么不妥的地方,歡迎大家指正。
????
??????? Page存在的目的就是對于用戶的內容進行呈現,它是一個IHttpHandler,它實現了對HttpRequest的處理,這個處理的結果就是根據請求Render出它自己,而Render的內容就是Html,CSS,Javascript,這些東西最終成為表現Page樣子的一磚一瓦。如果做一個最簡單的實現,大概就是
public?class?Page?:?IHttpHandler
{
????public?void?ProcessRequest(HttpContext?httpContext)
????{
????????Render();
?????}
}
??????? 事實并非如此,如果這樣的話,我們豈不是對于每個Page都要從零開始做一套完整的Render畫法啊,那樣的Render方法將是一個很長,很長的調用,為了支持不同的瀏覽器,為了應付復雜的業務邏輯,里面的代碼將是魔鬼,也許寫完一次沒有人愿意維護了,而且代碼的復用性也很低。那么怎么辦呢?.net為了解決這個問題,提供的是Control模型,對于一個頁面,頁面本身是一個可以呈現自己的Control,同事頁面里面也可以任意嵌套其它的Control,每個Control都具有呈現自己的能力,那樣Control將具有很好的復用性和可維護性,任何一個開發人員可以任意組合來自于第三方的Control來構建自己的Page。所以就需要有一個Control的類,而且Page是Control的一個派生類。Page的Render方法需要遞歸調用RenderChildren,這樣讓整個Control樹一層一層的呈現,最后得到整個Page的樣子。
??????? 我們知道Http請求里面有Get和Post,對于Get往往是請求一個全新的頁面,對于Post更多的是客戶端的一個響應。對于Get的請求,我們可以從零開始Render一個新的Page給客戶端。但是對于Post,我們一般需要讓頁面恢復到客戶端看到的狀態,然后再出來Post的數據。正是由于這個原因,Page在ProcessRequest的過程中不得不區分Page.IsPost or not。而如何進行場景恢復呢,這大概就是ViewState產生的真正原因了,ViewState是Control上的一個存儲結構。它負責保存Control的一些狀態。這些狀態通常會被序列化到客戶端的頁面上,當客戶端的頁面發出Post請求的時候,.net生產的腳本會自動收集這些ViewState數據,然后一起Post給服務器端。這樣Page就可以加載這些狀態進行場景恢復,然后在恢復好的場景下出來PostData。所以在ProcessRequest里面需要加入對于LoadViewState,SaveViewState,ProcessPostData的處理。
??????? 但是是否所有對于Control的設置都會序列化成ViewState呢,那樣不是很影響性能嗎,如果說我們每次在請求處理的開始階段(無論是Post還是非Post)都用代碼初始化Control,不是就不需要利用ViewState加載狀態了啊?所以在ProcessRequest里面又有了新的過程的加入Init,在Init里面可以設置Control的一些屬性和狀態,而在Init以后才通過TrackViewState來通知Control,TrackViewState階段后對Control的修改才會SaveViewState的時候保存下來,否則都不保存。
??????? 類似的例子還有很多,就像我們自己做的軟件產品,很多時候,第一個版本都是簡單的,以后隨著需求的增加,代碼越來越多,結構越來越復雜,也許未來版本的Page還會有更多的變化,總之經過很多的需求,Page對于Request的處理就變得復雜了,最后就分為下面的幾個階段,關于這些階段的介紹,我們可以在Google上搜索很多的文章,我認為我們不僅僅要了解這些階段,還要深入理解,否則想做出好的Control是很困難的。代碼中的數字列出了主要的頁面生命周期的階段,對于PostBack和CallBack,階段會發生一些變化,也加了說明
private?void?ProcessRequestMain(bool?includeStagesBeforeAsyncPoint,?bool?includeStagesAfterAsyncPoint)
????{
????????//?1.?PreInit
????????this.PerformPreInit();
????????//?2.?Init
????????this.InitRecursive(null);
????????this.OnInitComplete(EventArgs.Empty);
????????//?對于Postback,插入下面處理
????????if?(this.IsPostBack)
????????{
????????????//?加載ViewState和ControlState,進行場景恢復
????????????this.LoadAllState();
????????????//?第一次處理PostData
????????????this.ProcessPostData(this._requestValueCollection,?true);
????????}
????????//?3.?PreLoad
????????this.OnPreLoad(EventArgs.Empty);
????????//?4.?Load
????????this.LoadRecursive();
????????//?對于Postback,插入下面處理
????????if?(this.IsPostBack)
????????{
????????????//?第二次處理PostData
????????????this.ProcessPostData(this._leftoverPostData,?false);
????????????//?如果PostData表面某個Control數據發生變化,那么RaisePostDataChanged事件
????????????this.RaiseChangedEvents();
????????????//?RaisePostBackEvent
????????????this.RaisePostBackEvent(this._requestValueCollection);
????????}
????????this.OnLoadComplete(EventArgs.Empty);
????????//?對于CallBack,RaiseCallBackEvent
????????if?(this.IsPostBack?&&?this.IsCallback)
????????{
????????????this.PrepareCallback(callbackControlID);
????????}
????????else?if?(!this.IsCrossPagePostBack)
????????{
????????????//?5.?PreRender
????????????this.PreRenderRecursiveInternal();
????????}
???
????????//?對于CallBack,?Render出CallBack的結果
????????if?(this.IsCallback)
????????{
????????????this.RenderCallback();
????????}
????????else?if?(!this.IsCrossPagePostBack)
????????{
????????????this.PerformPreRenderComplete();
????????????
????????????//?6.?SaveViewStae和ControlState
????????????this.SaveAllState();
????????????this.OnSaveStateComplete(EventArgs.Empty);
????????????//?7.?Render?整個Control
????????????this.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));
????????}
????}
1. PreInit 階段
??? 這個階段是Page獨有的,在Control上是沒有的,這個階段主要是.net Framework自己在里面做了一些自己的初始化工作,比如Skin的加載,MasterPage的加載。會發Page.OnPreInit事件,源代碼如下:
private?void?PerformPreInit()
{
????this.OnPreInit(EventArgs.Empty);
????this.InitializeThemes();
????this.ApplyMasterPage();
????this._preInitWorkComplete?=?true;
}
2. Init階段
??? InitRecursive()是Page從Control繼承的方法,它主要進行Control的一些初始化和標記工作
??? a.? 對當前Control進行準確初始化,包括:
- 初始化_adapter;
- ApplySkin(this.Page);
- 設置標記this._controlState = ControlState.Initialized;
- TrackViewState()//開始跟蹤變化,這個階段以后的變化會存入ViewState
- 初始化nameContainer屬性
- 初始化page屬性
- 自動生成Id
- 遞歸調用子Control的InitRecursive()方法
????? 有個值得注意的是,如果在Control樹執行完Init之后創建一個新的Control加入到樹上,那么當它加入的時候,在父Control的AddedControl方法里面,如果發現已經Initialized,那么手動調用新加入Control的InitRecursive()方法。
??? 所以,我們Control的Init階段給Control的一些需要查找才可以得到的屬性進行直接賦值,如Page,nameContainer,這樣可以提高這些屬性的訪問速度。
3. PreLoad階段
?? 僅僅發Page獨有的事件:OnPreLoad事件
4. Load階段
?? LoadRecursive()是Page從Control繼承的方法,它比較簡單,僅僅是遞歸調用子Control的LoadRecursive()方法,然后做一個階段標記: (this._controlState = ControlState.Loaded);
5. PreRender階段
??? PreRenderRecursiveInternal()是Page從Control繼承的方法,這個方法會
- EnsureChildControls()-->調用CreateChildControls(),確保子Control創建完畢,為接下來的Render做準備
- 遞歸調用子Control的PreRenderRecursiveInternal()??
6. SaveAllState 階段
??? 主要存儲ControlState和ViewState,ControlState和ViewState唯一不同的地方在于ControlState是不可以禁用的,而ViewState可以禁用,事實上.net Framework在ControlState里面還加入了一個數據,這個數據是一個ArrayList,里面存入了所有的需要處理PostData的Control的Id,這樣在Post階段,.net Framework會根據ArrayList里面保存的Control來依次調用ProcessPostData方法,前提是這些Control最好實現IPostDataHandler接口。
7. Render階段
?? RenderControl()是Page從Control繼承的方法,這個方法會遞歸調用子Control的RenderControl (),這樣一層一層進行呈現。
總結:
??? Init,Load,PreRender,SaveState,Render這幾個階段會在整個Control樹上遞歸貫穿。在Init里面對Control的修改,一般是不會保存到ViewState里面,這個階段以后的變化會存入ViewState。
??? LoadAllState發生在Init和Load之間,因為LoadState會進行場景恢復,所以如果我們在Page_Load里面進行了一些初始化工作,那么如果在Post階段就不需要二次初始化了,所以經常會寫這樣的代碼 if (Page.IsPostBack) {...; // Init Controls},真正的原因就在這里了。
?? 另外也有一些在Load事件里面動態創建Control的做法,這個時候也要小心了。因為LoadAllState只會加載ViewState數據包,并不會創建Control(人家也不知道你的Control什么類型啊),所以無論是否IsPostBack,Control都需要創建并且加入到Controls集合。如果在Post階段,當Control一加入集合,就會被調用InitRecursive()方法進行初始化,同時還會把父Control上保存的該Control的ViewState傳給它,讓它加載。關于加載ViewState的知識也比較復雜,有安裝Control Index加載和安裝Control Id加載兩種,細節可以在以后專題講述。
補充:關于一個Control的生和死
Control的生可以分為兩種,一種是在DesignMode下設計好,一旦一個請求到來,Page被創建,這個時候Control就已經添加到以Page為根的Control樹了,所以它可以經歷完整的頁面生命周期(Init, Load。。。);另一種是在頁面生命周期的某個階段創建,例如Init的時候,或者Load的時候,這個時候.net為了繼續保持Control能經歷頁面的整個生命周期,會在它被加入到Control樹的瞬間進行一些補充式的調用,具體實現可以看下面的Control.AddedControl方法。
?
protected?internal?virtual?void?AddedControl(Control?control,?int?index){
????//?1.?初始化Page,Parent,NameContainer,ID
????control._parent?=?this;
????control._page?=?this.Page;
????control.flags.Clear(0x20000);
????Control?namingContainer?=?this.flags[0x80]???this?:?this._namingContainer;
????if?(namingContainer?!=?null)
????{
????????control.UpdateNamingContainer(namingContainer);
????????if?((control._id?==?null)?&&?!control.flags[0x40])
????????{
????????????control.GenerateAutomaticID();
????????}
????????else?if?((control._id?!=?null)?||?((control._occasionalFields?!=?null)?&&?(control._occasionalFields.Controls?!=?null)))
????????{
????????????namingContainer.DirtyNameTable();
????????}
????}
????//?2.?判斷當前Control所在的頁面生命周期階段,然后對于新加入的Control進行補充調用
????if?(this._controlState?>=?ControlState.ChildrenInitialized)
????{
????????control.InitRecursive(namingContainer);
????????if?(((control._controlState?>=?ControlState.Initialized)?&&?(control.RareFields?!=?null))?&&?control.RareFields.RequiredControlState)
????????{
????????????this.Page.RegisterRequiresControlState(control);
????????}
????????if?(this._controlState?>=?ControlState.ViewStateLoaded)
????????{
????????????object?savedState?=?null;
????????????if?((this._occasionalFields?!=?null)?&&?(this._occasionalFields.ControlsViewState?!=?null))
????????????{
????????????????savedState?=?this._occasionalFields.ControlsViewState[index];
????????????????if?(this.LoadViewStateByID)
????????????????{
????????????????????control.EnsureID();
????????????????????savedState?=?this._occasionalFields.ControlsViewState[control.ID];
????????????????????this._occasionalFields.ControlsViewState.Remove(control.ID);
????????????????}
????????????????else
????????????????{
????????????????????savedState?=?this._occasionalFields.ControlsViewState[index];
????????????????????this._occasionalFields.ControlsViewState.Remove(index);
????????????????}
????????????}
????????????control.LoadViewStateRecursive(savedState);
????????????if?(this._controlState?>=?ControlState.Loaded)
????????????{
????????????????control.LoadRecursive();
????????????????if?(this._controlState?>=?ControlState.PreRendered)
????????????????{
????????????????????control.PreRenderRecursiveInternal();
????????????????}
????????????}
????????}
????}
}
同樣,Control的死也可以分為兩種,一種就是完整的經歷一個頁面請求,.net會在所有的請求都處理完了之后,也就是在我上面講的所有的階段之后調用一個ProcessRequestClearUp()方法,另外一種就是在頁面的生命周期的某個階段調用Controls.Remove(control)方法來干掉Control,在這個調用發生后,父Control有一個叫做RemovedControl的方法會調用(和上面的AddedControl是兄弟哦),來進行清理工作,其實現基本是上面AddedControl的反操作。
值得注意的是,無論是RemovedControl()還是ProcessRequestClearUp(),它們都在Control還沒有從Control樹上摘掉的時候,調用了一個非常重要的方法control.UnloadRecursive(),這個方法從最底層的子Control向上直到當前正在移除的Control,依次執行OnUnload()方法,所以做WebControl的時候,我們可以override on_Unload()方法,在這個方法里面,可以摘除Event,摘除與Control樹關聯的變量,做一些清理工作。這點是非常有用的。
后記:從來沒有往首頁上發布過我的帖子。當我昨天發布了前言后發現很多人對這個話題都很感興趣,一方面感覺高興,一方面也感覺壓力,畢竟我沒有寫過什么像樣的技術文章,生怕辜負大家厚望。最近白天工作忙,只能晚上在家好好整理思路寫出來。因為對于寫WebControl的基本方法已經有很多地方介紹了(比如《道不遠人》),也沒有重復一遍的必要,所以我主要寫寫我對WebForm主要實現的理解。我認為這是我們設計一個專業的WebControl的基本功。有什么寫的不清楚的地方,歡迎大家指正,我一定盡力補充。
轉載于:https://www.cnblogs.com/limxc/archive/2008/07/22/1249070.html
總結
以上是生活随笔為你收集整理的ZT Web Control 开发系列(一) 页面的生命周期的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到好多孩子是什么意思
- 下一篇: 我来告诉你为什么中国民营企业管理失败的原