现在的位置: 首页 > 综合 > 正文

.Net源码之Page类

2013年06月27日 ⁄ 综合 ⁄ 共 6655字 ⁄ 字号 评论关闭
    自.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事件。上面的代码也没其他的好说了,只要记住这一点就够了。

【上篇】
【下篇】

抱歉!评论已关闭.