• .Net源码之Page类


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

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

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

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

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

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

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

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

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

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

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

    public class Page: TemplateControl, IHttpHandler

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

    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.Context,Page.Request,Page.Request,Page.Response,Page.Application,Page.Cache。现在我们知道了这几个Page属性是在哪里被初始化了。而且我们知道了最重要的一点就是,这几个属性原来都只是一个引用罢了(不是说是引用类型哦!)。那这个有甚么用处呢?

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

    private void ProcessRequest() {

            Thread currentThread 
    = Thread.CurrentThread;

            CultureInfo prevCulture 
    = currentThread.CurrentCulture; 

            CultureInfo prevUICulture 
    = currentThread.CurrentUICulture; 

                ProcessRequest(
    true /*includeStagesBeforeAsyncPoint*/true /*includeStagesAfterAsyncPoint*/);

            }


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

    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类中的这个函数。我们来看看这个函数的主要代码:

    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表示当前控件的所有子控件集合。根据代码,我们可以发现,先遍历子控件,然后子控件再初始化,然后子控件的子控件再初始化,依次递归,直到最里层的控件为止。从中我们可以得到甚么呢?只有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; 

                }


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

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

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

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

    internal virtual void LoadRecursive()

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

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

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

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

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

    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);

                }


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

  • 相关阅读:
    你知道Synchronized底层实现原理嘛
    一篇搞定Java集合类原理
    lsp都要会的内存模型
    Sql Server 查询优化
    使用Windows的mstsc远程桌面连接到Ubuntu图形界面(AWS上安装的Ubuntu系统)
    AWS EC2实例Ubuntu系统设置root用户密码并使用root/ubuntu用户登录
    安装mysql.zip文件教程(包含常见问题修复)
    DevExpress GridControl小结
    C#开发必会
    C# 错误集锦
  • 原文地址:https://www.cnblogs.com/tommyli/p/1229687.html
Copyright © 2020-2023  润新知