.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事件。上面的代码也没其他的好说了,只要记住这一点就够了。

posted @ 2008-06-25 11:18  荒芜  阅读(3801)  评论(6编辑  收藏  举报