Web开发学习心得7——MasterPage的实现原理

      MasterPage是Asp.net2.0引入的一个非常实用的特性,怎么用,我想不用我说,基本上大家都会,这里要讲的是,它是如何实现的。

      在深入源代码去探索MasterPage之前,我以为MasterPage的实现应该是比较复杂的,也一直纳闷为什么MasterPage类会继承于UserControl类,感觉这两者好像差得很远。昨天晚上,我专门抽出时间,阅读了部分与MasterPage有关的源代码,终于明白了是怎么回事,在那突然明白的那一刻,真有如醍醐灌顶,拍案叫绝,不得不佩服微软的那些guys。

      下面就是我的探索之旅的过程(大家也可以跳过该部分,直接看后面的真相大白部分):

      1、我首先查看的是Page.ProcessRequestMain方法,我们知道,Page类大部分特性,包括LifeCycle、PostBack、ProcessPostData等都是在该方法中实现,所以,我想,MasterPage的实现肯定在该方法中有不少的体现。然而,令我惊讶的是,我居然没有在该方法中找到任何有关MasterPage的线索,而仅仅在this.PerformPreInit()中,找到唯一一个ApplyMasterPage()方法。而该方法也出奇的简单,感觉仅仅是将递归的各级MasterPage的._masterPageApplied字段设为true而已。当时,我忽略了一个重要的东西,就是代码中对this.Master这个属性的访问,实际上,奥秘就在对这个属性的访问上(下文将叙述)。

private void PerformPreInit()
{
    
this.OnPreInit(EventArgs.Empty);
    
this.InitializeThemes();
    
this.ApplyMasterPage();
    
this._preInitWorkComplete = true;
}

 

Page.ApplyMasterPage()

 

      2、我查看了MasterPage的源代码,出奇的是,竟也如此简单,以至于我也没有从该源代码中找到多少有价值的信息。  

MasterPage

      

      3、我又查看了ContentPlaceHolder类、Content类,心想,难道奥秘在这两个控件上?打开源代码一看,彻底晕倒,这两个类简单得简直不能让人相信,ContentPlaceHolder居然是一个空类,仅仅起到一个标识的作用,而Content居然也仅仅只有ContentPlaceHolderID唯一一个string属性。

public class ContentPlaceHolder : Control, INonBindingContainer, INamingContainer
{
}

 

Content

      

      4、此时,我几乎已经没法再从Asp.net源代码中找到其他有关MasterPage的有价值的信息了。于是,我决定写一个简单的Web应用程序,该应用程序仅仅只有一个MasterPage页与Default页,并将其编译,查看编译后的代码,看看是否能找到有价值的信息。

      源代码如下:

MasterPage.master

 

Default.aspx

 

      编译后代码如下:

Code

 

Code

      

      我们首先观察default_aspx的BuildControlTree方法,该方法调用了base.AddContentTemplate("ContentPlaceHolder", new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControlContent)));而该方法实质上只有一行代码,即this._contentTemplateCollection.Add(templateName, template);其中,_contentTemplateCollection是IDictionary类型。因此,base.AddContentTemplate只有一个功能,即在Page的_contentTemplateCollection中添加一个CompliedTemplateBuilder类型的对象。那么该对象有啥作用呢?为了不岔开话题,这里不对该对象做详细描述,只给出结论:该对象实现ITemplate接口,其接口方法InstantiateIn(Control container)具体实现为为container添加BuildTemplateMethod委托所创建的控件。这话有点拗口,简单地就刚才那个例子来说,就是如果你调用该对象的InstantiateIn(Control container)方法,就为该container添加this.__BuildControlContent()方法所创建的控件做为子控件。

 

      5、我们继续来看看masterpage_master的__BuildControlContentPlaceHolder()方法,我们发现,该方法即调用了刚才讨论的那个InstantiateIn方法。哈,原来在这里,终于明白了,原来Page里Content控件中的所有内容最终都将变成其对应的MasterPage中的ContentPlaceHolder的子控件。等一下,这个结论下得有点早,难道这里的base.ContentTemplates属性就等于Page的_contentTemplateCollection字段吗?如果是,那么上面的结论就是正确的。

 

      6、我们重新回到1中的代码,查看Page.Master属性的实现,我们发现,它调用了MasterPage的CreateMaster静态方法,而该方法传送的其中一个参数就是._contentTemplateCollection字段。

Page.Master

 

      我们再回头来看看MasterPage.CreateMaster方法,在倒数第6行,我们发现,该方法果然将contentTemplateCollection赋给了MasterPage实例(child)的_contentTemplates字段。再往下看,我们还看到了owner.Controls.Add(child),什么意思呢?意思就是将该MasterPage实例作为普通控件加入到owner实例的子控件集合中。而往上,我们又可以找到owner.Controls.Clear()语句,因此,该MasterPage将作为owner的唯一子控件而存在。而该owner,常常就是Page实例。(在存在嵌套MasterPage的时候,该owner还可能是下一层次的MasterPage实例)。

      至此,真相大白,原来,MasterPage最终将作为Page的唯一子控件而存在,难怪它要继承自 UserControl,而Page中Content控件定义的各个子控件,又将作为该MasterPage的ContentPlaceHolder的子控件而存在,难怪ContentPlaceHolder无需实现任何代码,因为它仅仅是一个容器。正因为MasterPage最终成为Page的唯一子控件,那么后来的处理就与普通的控件没什么两样了,难怪ProcessRequestMain方法里无需为MasterPage单独编码,哈哈,一切都真相大白了。这里,我们还发现一个比较有趣的现象,即Content控件本身却消失不见了,这应该是Asp.net解析器所做的优化,因为ContentPlaceHolder完全没必要先装上Content控件,然后再装上Content中的那些控件。

      另外,从Page.Master属性与Page.MasterPageFile属性的实现上,我们也不难明白为什么MasterPageFile属性只能在 PreInit 事件中进行设置的原因。 

      如何证明以上所说都是正确的呢?呵呵,其实很简单,我们可以观察最终页面的控件树,就可证明上面分析是正确的。(写这篇blog时才想起看控件树,要是早想起,就能让我少走不少歪路了,唉,幸好打开控件树发现结果与预期完全一致。)

ControlTree

      

      那么嵌套MasterPage是如何实现的呢?呵呵,其实也一样,即Top1MasterPage成为Top2MasterPage的唯一子控件,Top2MasterPage成为Top3MasterPage的唯一子控件,……,直到TopNMasterPage成为Page的唯一子控件。

      

      最后我用两幅图来做总结。

      下图为初始的控件树结构:

 

      下图为最终的控件树结构:

 

posted @ 2009-06-03 11:22  草船上的稻草人  阅读(3504)  评论(5编辑  收藏  举报