Web Control 開發系列(一) 頁面的生命週期

Page是WebForm編程基本元素,它從TemplateControl派生,而TemplateControl又從Control派生,因此Page實際就是一個Control。同時Page也實現了IHttpHandler接口,因此它能夠接受Http請求,進行處理。
    能夠認爲一個Page是由不少的Control按照樹形結構組織的,而樹的根就是Page(一個實現了IHttphandler的Control), 整個Control樹的生命週期開始於一個Http請求,而終止於請求處理的結束。事實上在Http請求傳入到當前的Page的時候,以前已經通過了漫長的路程,若是對於整個Http請求的細節感興趣,能夠查看MSDN中關於應用程序的生命週期。另外關於頁面的生存週期也有不少的文章能夠參考,好比MSDN中ASP.NET 頁生命週期概述。
     我這裏主要講講我我的的理解,若是有什麼不妥的地方,歡迎你們指正。
     

        Page存在的目的就是對於用戶的內容進行呈現,它是一個IHttpHandler,它實現了對HttpRequest的處理,這個處理的結果就是根據請求Render出它本身,而Render的內容就是Html,CSS,Javascript,這些東西最終成爲表現Page樣子的一磚一瓦。若是作一個最簡單的實現,大概就是javascript

複製代碼
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,階段會發生一些變化,也加了說明java

複製代碼
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事件,源代碼以下:web

複製代碼
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

    b.  對子Control進行初始化瀏覽器

  • 初始化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加載兩種,細節能夠在之後專題講述。
ide

補充:關於一個Control的生和死性能

Control的生能夠分爲兩種,一種是在DesignMode下設計好,一旦一個請求到來,Page被建立,這個時候Control就已經添加到以Page爲根的Control樹了,因此它能夠經歷完整的頁面生命週期(Init, Load。。。);另外一種是在頁面生命週期的某個階段建立,例如Init的時候,或者Load的時候,這個時候.net爲了繼續保持Control能經歷頁面的整個生命週期,會在它被加入到Control樹的瞬間進行一些補充式的調用,具體實現能夠看下面的Control.AddedControl方法。ui

 

複製代碼
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的反操做。this

值得注意的是,不管是RemovedControl()仍是ProcessRequestClearUp(),它們都在Control尚未從Control樹上摘掉的時候,調用了一個很是重要的方法control.UnloadRecursive(),這個方法從最底層的子Control向上直到當前正在移除的Control,依次執行OnUnload()方法,因此作WebControl的時候,咱們能夠override OnUnload()方法,在這個方法裏面,能夠摘除Event,摘除與Control樹關聯的變量,作一些清理工做。這點是很是有用的。

 

 後記:歷來沒有往首頁上發佈過個人帖子。當我昨天發佈了前言後發現不少人對這個話題都很感興趣,一方面感受高興,一方面也感受壓力,畢竟我沒有寫過什麼像樣的技術文章,生怕辜負你們厚望。最近白天工做忙,只能晚上在家好好整理思路寫出來。由於對於寫WebControl的基本方法已經有不少地方介紹了(好比《道不遠人》),也沒有重複一遍的必要,因此我主要寫寫我對WebForm主要實現的理解。我認爲這是咱們設計一個專業的WebControl的基本功。有什麼寫的不清楚的地方,歡迎你們指正,我必定盡力補充。

相關文章
相關標籤/搜索