zoukankan      html  css  js  c++  java
  • 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样子的一砖一瓦。如果做一个最简单的实现,大概就是
    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
        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加载两种,细节可以在以后专题讲述。

    补充:关于一个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 OnUnload()方法,在这个方法里面,可以摘除Event,摘除与Control树关联的变量,做一些清理工作。这点是非常有用的。

  • 相关阅读:
    PAT 甲级 1126 Eulerian Path (25 分)
    PAT 甲级 1126 Eulerian Path (25 分)
    PAT 甲级 1125 Chain the Ropes (25 分)
    PAT 甲级 1125 Chain the Ropes (25 分)
    PAT 甲级 1124 Raffle for Weibo Followers (20 分)
    PAT 甲级 1124 Raffle for Weibo Followers (20 分)
    PAT 甲级 1131 Subway Map (30 分)
    PAT 甲级 1131 Subway Map (30 分)
    AcWing 906. 区间分组 区间贪心
    AcWing 907. 区间覆盖 区间贪心
  • 原文地址:https://www.cnblogs.com/ghfsusan/p/1494720.html
Copyright © 2011-2022 走看看