• .Net源码之Page类(一)



          自.NetFrameWork开源以来已经一段时间了,以下是我关于这些开源代码的一点理解,本篇主要讨论了Page类本身,和生命周期中的初始化与加载阶段在Page类代码中的体现.借此篇只是希望抛砖引玉,让大家能够更多的关注与源代码的研究,让我们在开发的时候能够有更深层次的理解.对于出现的Error等,我们能够更加清晰的理解这个机制.由于水平有限,不足之处望其谅解!当然希望大家能够指出.^_^!(让臭鸡蛋来的更猛烈些吧!^_^)

    我们用Asp.Net开发的所有的Web窗体页都是直接或者间接的继承之Page类。我们今天讲的就是这个Page类,在我们看这个Page类之前,我们先来看一下Asp.Net页的生命周期。因为Asp.Net页的加载的过程就是严格的根据这个生命周期的过程来执行的。

    根据MSDN对于ASP.NET页生命周期的定义,有以下8个方面:页请求,开始,页初始化,页加载,验证,回发事件,呈现,卸载。

    既然我们这个Page类是严格按照这个生命周期阶段来,那么我们这个类的入口点在哪里呢?

    现在我们先记下这8个生命周期阶段和这个问题,接下来就来看看这个Page类。我们先从MSDN对这个类的解析开始,MSDNPage类的解析有以下几个方面:

    1.             表示从 ASP.NET Web 应用程序的宿主服务器请求的 .aspx 文件

    2.             Page类与扩展名为 .aspx 的文件相关联。这些文件在运行时被编译为 Page对象,并被缓存在服务器内存中。

    3.             Page对象充当页中所有服务器控件的命名容器。

    4.             Page类是一个用作 Web 应用程序的用户界面的控件

    对于第1条我们大家都已经清楚了,第23条我们本次不讨论。单看第4条,忽略定语,就是 Page类是控件”。既然是控件,那么Page类肯定是直接或者间接的继承自Control类了。

    那么下面我们从源代码上来看一下Page类的定义是怎么样的。

    public class Page: TemplateControl, IHttpHandler

    继承了TemplateControl类,并且实现了接口IHttpHandler。现在我们可以肯定TemplateControl类肯定是直接或者间接继承自Control类的,查看源代码发现事实也是如此。现在我们不管这个TemplateControl类,如果大家感兴趣的话,可以通过MSDN,查看源代码自己去了解,这里不做解释了。我们关注的重点其实是接口IHttpHandler,关于IHttpHandler的解析大家看看MSDN吧,我们下面直接看一下它的源代码,发现定义了一个属性和一个方法,我们主要来关注一下这个方法:

    public virtual void ProcessRequest(HttpContext context) 

         MSDN对此方法的解释:通过实现 IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。对于这个IHttpHandler接口,我暂时建议大家去关注一下,这个东东非常有用,我们可以利用它来做很多事情,比如,我们可以利用它来防止图片被盗链,或者重写URL

           说明这个是个入口点,对于Web请求的处理,通过此方法开始,那么我们就知道了Page类的入口点在哪里了。^_^

           那么我们就来看看这个Page的入口点函数吧!我们查看了这个函数会发现,它将会调用另外一个函数,虽然这个入口点函数没甚么可以说的,但是有一个关键点,我们看一下如下定义: 

    public virtual void ProcessRequest(HttpContext context) 

         不知道大家发现没有,整个是虚函数,那就是我们可以在自己的页面中Override它,那么这个对我们有甚么用呢?主要的一点就是我们控制了生命周期开始的开关。如果我们不需要这个过程,我们就可以Override了,然后不调用后面的过程。对于文字描述,我们可能比较迷惑。甚么时候我们不需要它呢?现在说个例子,我们大家都会写一种页面,就是用来页面转向的,那时候我们完全不需要甚么乱七八糟的生命周期这么多阶段(不包括页请求,开始等,这些肯定有,^_^),直接可以在Override函数里面执行转向了,免的麻烦。当然这个例子有点牵强,但是也就是这个意思,当不需要其后的生命周期阶段的时候,我们可以将其Override了。

           现在我们可以来看一下这个被调用的函数了,如下:

     

     

    private void ProcessRequestWithNoAssert(HttpContext context) {
            SetIntrinsics(context); 
    //进行最基本的初始化
            ProcessRequest(); //开始处理请求
        }


    我们要在这个函数这里停顿一下,因为这里做了一些有意思的工作。就是SetIntrinsics(context)函数。这个函数会调用另外一个重载函数,我们就来看看这个重载函数实现了甚么我们需要特意来看看它。

     

     

    private void SetIntrinsics(HttpContext context, bool allowAsync) {
            _context 
    = context; 
            _request 
    = context.Request; 
            _response 
    = context.Response;
            _application 
    = context.Application; 
            _cache 
    = context.Cache;


    这些_context _request _response _application _cache其实就是对应Page.ContextPage.RequestPage.RequestPage.ResponsePage.ApplicationPage.Cache。现在我们知道了这几个Page属性是在哪里被初始化了。而且我们知道了最重要的一点就是,这几个属性原来都只是一个引用罢了(不是说是引用类型哦!)。那这个有甚么用处呢?

    我们暂且先记下这个问题。回到函数ProcessRequestWithNoAssert往下看,接着调用函数ProcessRequest()。我们也来看看这个函数:

     

     

        private void ProcessRequest() {
            
    // culture needs to be saved/restored only on synchronous pages (if at all)
            
    // save culture 
            Thread currentThread = Thread.CurrentThread;
            CultureInfo prevCulture 
    = currentThread.CurrentCulture; 
            CultureInfo prevUICulture 
    = currentThread.CurrentUICulture; //设置当前进程的本地化信息

            
    try 
                ProcessRequest(
    true /*includeStagesBeforeAsyncPoint*/true /*includeStagesAfterAsyncPoint*/);//开始处理请求,
            }

            
    finally {
                
    // restore culture 
                RestoreCultures(currentThread, prevCulture, prevUICulture);
            }
     
        }
     


    这里没甚么有意思的,就是设置了一些本地化信息,接着调用了这个重载函数。有兴趣的朋友可以去看看这个重载函数,这里就不讲了,这个重载函数调用了主要函数,我们的生命周期阶段都是在这个函数里面完成的。就是这个

    private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)

    这个函数的代码就不贴了,大家对照整个函数的代码来看吧!

    现在我们可以来回顾一下这个生命周期阶段了,对于页生命周期阶段中的页请求,开始。到这里已经过了,现在我们首先来看看这个页初始化阶段吧。我们从源代码里看,这个请求有3个阶段:预初始化,初始化,初始化完毕。对应的函数为:PerformPreInit();InitRecursive(null); OnInitComplete(EventArgs.Empty);

    我们可以在Page类代码的4060行开始发现以上这些代码,那么在这个函数中预请求之前发生了甚么事情呢?对于这些事情我大家可以自己去看看,^_^,我们现在只关心下这个生命周期阶段。这里对预请求与请求完毕都不将做叙述,他们主要触发了各自对应的事件。我们主要来看看这个请求的函数:InitRecursive(null)顾名思义,这个函数将是递归的初始化。

    而这个InitRecursive(null)函数是父类Control类的成员,并且是个虚函数,如下定义:

     

    internal virtual void InitRecursive(Control namingContainer) 

     

    而且我们Page类也没有Override。所以我们将会执行Control类中的这个函数。我们来看看这个函数的主要代码:

     internal virtual void InitRecursive(Control namingContainer) {
                ResolveAdapter();
                
    if (_occasionalFields != null && _occasionalFields.Controls != null
                    
    if (flags[isNamingContainer]) {
                        namingContainer 
    = this
                    }
     
                    
    string oldmsg = _occasionalFields.Controls.SetCollectionReadOnly(SR.Parent_collections_readonly);
     
                    
    int controlCount = _occasionalFields.Controls.Count;
                    
    for (int i = 0; i < controlCount; i++{
                        Control control 
    = _occasionalFields.Controls[i];
     
                        
    // Propagate the page and namingContainer
                        control.UpdateNamingContainer(namingContainer); 
     
                        
    if ((control._id == null&& (namingContainer != null&& !control.flags[idNotRequired]) {
                            control.GenerateAutomaticID(); 
                        }

                        control._page 
    = Page;

                        control.InitRecursive(namingContainer); 
                    }

                    _occasionalFields.Controls.SetCollectionReadOnly(oldmsg); 
     
                }

     
                
    // Only make the actual call if it hasn't already happened (ASURT 111303)
                if (_controlState < ControlState.Initialized) {
                    _controlState 
    = ControlState.ChildrenInitialized; // framework also initialized
     
                    
    if ((Page != null&& !DesignMode) {
                        
    if (Page.ContainsTheme && EnableTheming) 
                            ApplySkin(Page); 
                        }

                    }
     

                    
    if (_adapter != null{
                        _adapter.OnInit(EventArgs.Empty);
                    }
     
                    
    else {
                        OnInit(EventArgs.Empty); 
                    }
     

                    _controlState 
    = ControlState.Initialized; 
                }


                
    // track all subsequent state changes
                TrackViewState(); //这个表示跟踪视图状态,此后所作的改变将会被记录下来(动态控件刚new的时候将不会被跟踪视图状态)

    #if DEBUG 
                ControlInvariant(); 
    #endif
            }
     

    这个是个递归的过程,其中_occasionalFields.Controls表示当前控件的所有子控件集合。根据代码,我们可以发现,先遍历子控件,然后子控件再初始化,然后子控件的子控件再初始化,依次递归,直到最里层的控件为止。从中我们可以得到甚么呢?只有1条:初始化的时候是从最里层的控件开始初始化,然后再依次向外初始化。那么到最里层控件会执行甚么呢?看下面的代码,我们就清楚了

    if (_controlState < ControlState.Initialized) {
                    _controlState 
    = ControlState.ChildrenInitialized; // framework also initialized
     
                    
    if ((Page != null&& !DesignMode) {
                        
    if (Page.ContainsTheme && EnableTheming) 
                            ApplySkin(Page); 
                        }

                    }
     

                    
    if (_adapter != null{
                        _adapter.OnInit(EventArgs.Empty);
                    }
     
                    
    else {
                        OnInit(EventArgs.Empty); 
                    }
     

                    _controlState 
    = ControlState.Initialized; 
                }


                
    // track all subsequent state changes
                TrackViewState(); //这个表示跟踪视图状态,此后所作的改变将会被记录下来(动态控件刚new的时候将不会被跟踪视图状态)

    我们发现主要也就是触发这个控件的初始化事件。所以我们页面上控件的初始化事件比页面初始化事件被触发的早。

    下面开始生命周期的加载阶段。。。。。
    “艾,慢,慢,下面还有一行代码:TrackViewState,这个是啥意思啊,咋就不讲了呢?”
    ye!忘了,这行代码还是非常重要的。意思就是:从此时开始,当前控件的所作的修改(关系到视图的)都将会被记录下来。
    那甚么是当前控件?甚么是所作的修改?甚么是记录下来呢?
    这些问题我不做正面回答了。大家copy下下面这段代码,测试看看,就知道了,还不懂?留言哦。。。。。。

     DropDownList ddl1 = new DropDownList();
        DropDownList ddl2 
    = new DropDownList();
        
    protected void Page_Load(object sender, EventArgs e)
        
    {
            form1.Controls.Add(ddl2);
            
    if (!IsPostBack)
            
    {    
                ddl1.Items.Add(
    "10");
                ddl1.Items.Add(
    "11");
                ddl1.Items.Add(
    "12");
                ddl2.Items.Add(
    "20");
                ddl2.Items.Add(
    "21");
                ddl2.Items.Add(
    "22");
            }

            form1.Controls.Add(ddl1);
        }

        
    protected void button1_Click(object sender, EventArgs e)
        
    {
            
    //回传的时候比较下ddl1与ddl2有甚么不一样,为什么不一样呢?
        }


    以上就是这个生命周期阶段中初始化的整个过程,我们简单回顾一下,其实只有一句话:控件初始化的时候,是先初始化最里层的控件,再依次向外初始化。

    接下去是页面加载阶段,同样的有预加载,加载,加载完毕3个过程,我们也只是来看看这个加载阶段。但是注意1点,我们在初始化与加载之间,还有一个阶段,一个非常重要的阶段,就是LoadViewSate,载入视图阶段。我们暂且不说,先来看看这个加载阶段的函数代码。

    首先分析LoadRecursive();函数,这也是个会起到递归作用的函数,同时也是Control类的成员函数,看下面的定义,

     

    internal virtual void LoadRecursive()

     

    这是一个只供那不使用的虚函数,那么我们就没法子Override了。接这看看里面的代码。在这之前,我们先猜一下,有下面2点:

    1.这个函数是个递归的过程,那么肯定会遍历所有的控件进行加载。

    2.这个函数里面肯定会触发OnLoad事件。

    那么根据初始化的过程,我们这里面是不是也是:加载控件的时候,是先加载最里层的控件,再依次向外加载呢?

    跟着这个问题,我们来看看下面的代码:

    internal virtual void LoadRecursive() {
     
                
    // Only make the actual call if it hasn't already happened (ASURT 111303)
                if (_controlState < ControlState.Loaded) {
                    
    if(_adapter != null{
                        _adapter.OnLoad(EventArgs.Empty); 
                    }

                    
    else 
                        OnLoad(EventArgs.Empty); 
                    }

                }
     

                
    // Call Load on all our children
                if (_occasionalFields != null && _occasionalFields.Controls != null{
                    
    string oldmsg = _occasionalFields.Controls.SetCollectionReadOnly(SR.Parent_collections_readonly); 

                    
    int controlCount = _occasionalFields.Controls.Count; 
                    
    for (int i = 0; i < controlCount; i++
                        _occasionalFields.Controls[i].LoadRecursive();
                    }
     

                    _occasionalFields.Controls.SetCollectionReadOnly(oldmsg);
                }

     
                
    if (_controlState < ControlState.Loaded)
                    _controlState 
    = ControlState.Loaded; 
            }
     

    我们看这个代码,其实一眼就清楚了,跟初始化的时候的代码顺序变了一下,触发事件的在前面,遍历控件的在后面。那么这么做也就只有一个结果了,就是先触发事件,再遍历控件。总结一下就是:加载控件的时候,先加载最外层的控件,再依次下内加载控件。刚好跟初始化的相反。也就是先加载页面上的Page_Load,再去执行各个控件上的OnLoad事件。上面的代码也没其他的好说了,只要记住这一点就够了。

  • 相关阅读:
    IK分词器插件
    倒排索引
    logstash-安装、基本使用、入门
    Anaconda使用-详解
    java之反射
    Java中级路线jdbc第一天
    Java字符串及字符串的常用方法知识点总结
    Java基本类型的类包装知识点总结
    Java Class类知识点总结
    java异常类知识点总结
  • 原文地址:https://www.cnblogs.com/answercard/p/1364488.html
Copyright © 2020-2023  润新知