zoukankan      html  css  js  c++  java
  • [Joe 原创]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树关联的变量,做一些清理工作。这点是非常有用的。


    后记:从来没有往首页上发布过我的帖子。当我昨天发布了前言后发现很多人对这个话题都很感兴趣,一方面感觉高兴,一方面也感觉压力,毕竟我没有写过什么像样的技术文章,生怕辜负大家厚望。最近白天工作忙,只能晚上在家好好整理思路写出来。因为对于写WebControl的基本方法已经有很多地方介绍了(比如《道不远人》),也没有重复一遍的必要,所以我主要写写我对WebForm主要实现的理解。我认为这是我们设计一个专业的WebControl的基本功。有什么写的不清楚的地方,欢迎大家指正,我一定尽力补充。

  • 相关阅读:
    no-useless-call (Rules) – Eslint 中文开发手册
    Java 8 Stream
    CSS3 ,checked 选择器
    MySQL 数据类型
    _Alignas (C keywords) – C 中文开发手册
    C 库函数 – modf()
    JavaScript E 属性
    SyntaxError.prototype (Errors) – JavaScript 中文开发手册
    swagger和openAPI: 上传文件
    Java中HashMap的putAll()方法: HashMap.putAll()
  • 原文地址:https://www.cnblogs.com/xiangboren/p/1404475.html
Copyright © 2011-2022 走看看