[ASP.NET]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;
}

 


private void ApplyMasterPage()
{
    
if (this.Master != null)
    
{
        ArrayList appliedMasterFilePaths 
= new ArrayList();
        appliedMasterFilePaths.Add(
this._masterPageFile.VirtualPathString.ToLower(CultureInfo.InvariantCulture));
        MasterPage.ApplyMasterRecursive(
this.Master, appliedMasterFilePaths);
    }

}

 

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


[ControlBuilder(typeof(MasterPageControlBuilder)), Designer("Microsoft.VisualStudio.Web.WebForms.MasterPageWebFormDesigner, Microsoft.VisualStudio.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"typeof(IRootDesigner)), ParseChildren(false), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
public class MasterPage : UserControl
{
    
private IList _contentPlaceHolders;
    
private IDictionary _contentTemplateCollection;
    
private IDictionary _contentTemplates;
    
private MasterPage _master;
    
private bool _masterPageApplied;
    
private VirtualPath _masterPageFile;
    
internal TemplateControl _ownerControl;

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal void AddContentTemplate(string templateName, ITemplate template)
    
{
        
if (this._contentTemplateCollection == null)
        
{
            
this._contentTemplateCollection = new Hashtable(10, StringComparer.OrdinalIgnoreCase);
        }

        
try
        
{
            
this._contentTemplateCollection.Add(templateName, template);
        }

        
catch (ArgumentException)
        
{
            
throw new HttpException(SR.GetString("MasterPage_Multiple_content"new object[] { templateName }));
        }

    }


    
internal static void ApplyMasterRecursive(MasterPage master, IList appliedMasterFilePaths)
    
{
        
if (master.Master != null)
        
{
            
string str = master._masterPageFile.VirtualPathString.ToLower(CultureInfo.InvariantCulture);
            
if (appliedMasterFilePaths.Contains(str))
            
{
                
throw new InvalidOperationException(SR.GetString("MasterPage_Circular_Master_Not_Allowed"new object[] { master._masterPageFile }));
            }

            appliedMasterFilePaths.Add(str);
            ApplyMasterRecursive(master.Master, appliedMasterFilePaths);
        }

        master._masterPageApplied 
= true;
    }


    
internal static MasterPage CreateMaster(TemplateControl owner, HttpContext context, VirtualPath masterPageFile, IDictionary contentTemplateCollection)
    
{
        MasterPage child 
= null;
        
if (masterPageFile == null)
        
{
            
if ((contentTemplateCollection != null&& (contentTemplateCollection.Count > 0))
            
{
                
throw new HttpException(SR.GetString("Content_only_allowed_in_content_page"));
            }

            
return null;
        }

        VirtualPath virtualPath 
= VirtualPathProvider.CombineVirtualPathsInternal(owner.TemplateControlVirtualPath, masterPageFile);
        ITypedWebObjectFactory vPathBuildResult 
= (ITypedWebObjectFactory) BuildManager.GetVPathBuildResult(context, virtualPath);
        
if (!typeof(MasterPage).IsAssignableFrom(vPathBuildResult.InstantiatedType))
        
{
            
throw new HttpException(SR.GetString("Invalid_master_base"new object[] { masterPageFile }));
        }

        child 
= (MasterPage) vPathBuildResult.CreateInstance();
        child.TemplateControlVirtualPath 
= virtualPath;
        
if (owner.HasControls())
        
{
            
foreach (Control control in owner.Controls)
            
{
                LiteralControl control2 
= control as LiteralControl;
                
if ((control2 == null|| (Util.FirstNonWhiteSpaceIndex(control2.Text) >= 0))
                
{
                    
throw new HttpException(SR.GetString("Content_allowed_in_top_level_only"));
                }

            }

            owner.Controls.Clear();
        }

        
if (owner.Controls.IsReadOnly)
        
{
            
throw new HttpException(SR.GetString("MasterPage_Cannot_ApplyTo_ReadOnly_Collection"));
        }

        
if (contentTemplateCollection != null)
        
{
            
foreach (string str in contentTemplateCollection.Keys)
            
{
                
if (!child.ContentPlaceHolders.Contains(str.ToLower(CultureInfo.InvariantCulture)))
                
{
                    
throw new HttpException(SR.GetString("MasterPage_doesnt_have_contentplaceholder"new object[] { str, masterPageFile }));
                }

            }

            child._contentTemplates 
= contentTemplateCollection;
        }

        child._ownerControl 
= owner;
        child.InitializeAsUserControl(owner.Page);
        owner.Controls.Add(child);
        
return child;
    }


    [Browsable(
false), EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal IList ContentPlaceHolders
    
{
        
get
        
{
            
if (this._contentPlaceHolders == null)
            
{
                
this._contentPlaceHolders = new ArrayList();
            }

            
return this._contentPlaceHolders;
        }

    }


    [Browsable(
false), EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal IDictionary ContentTemplates
    
{
        
get
        
{
            
return this._contentTemplates;
        }

    }


    [WebSysDescription(
"MasterPage_MasterPage"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
    
public MasterPage Master
    
{
        
get
        
{
            
if ((this._master == null&& !this._masterPageApplied)
            
{
                
this._master = CreateMaster(thisthis.Context, this._masterPageFile, this._contentTemplateCollection);
            }

            
return this._master;
        }

    }


    [WebSysDescription(
"MasterPage_MasterPageFile"), WebCategory("Behavior"), DefaultValue("")]
    
public string MasterPageFile
    
{
        
get
        
{
            
return VirtualPath.GetVirtualPathString(this._masterPageFile);
        }

        
set
        
{
            
if (this._masterPageApplied)
            
{
                
throw new InvalidOperationException(SR.GetString("PropertySetBeforePageEvent"new object[] "MasterPageFile""Page_PreInit" }));
            }

            
if (value != VirtualPath.GetVirtualPathString(this._masterPageFile))
            
{
                
this._masterPageFile = VirtualPath.CreateAllowNull(value);
                
if ((this._master != null&& this.Controls.Contains(this._master))
                
{
                    
this.Controls.Remove(this._master);
                }

                
this._master = null;
            }

        }

    }

}

      

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

public class ContentPlaceHolder : Control, INonBindingContainer, INamingContainer
{
}

 

Content

      

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

      源代码如下:


<%@ Master Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    
<title></title>
</head>
<body>
    
<form id="form1" runat="server">
    
<div>
        
<asp:ContentPlaceHolder id="ContentPlaceHolder" runat="server">
            
<asp:Button runat="server" ID="master" Text="master"/>
        
</asp:ContentPlaceHolder>
    
</div>
    
</form>
</body>
</html>

 


<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>

<asp:Content ID="Content" ContentPlaceHolderID="ContentPlaceHolder" Runat="Server">
    
<asp:Button runat="server" ID="default" Text="default" />
</asp:Content>

 

      编译后代码如下:


[CompilerGlobalScope]
public class masterpage_master : MasterPage
{
    
private static bool __initialized;
    
private ITemplate __Template_ContentPlaceHolder;
    
protected ContentPlaceHolder ContentPlaceHolder;
    
protected HtmlForm form1;
    
protected Button master;

    [DebuggerNonUserCode]
    
public masterpage_master()
    
{
        
base.AppRelativeVirtualPath = "~/MasterPage.master";
        
if (!__initialized)
        
{
            __initialized 
= true;
        }

        
base.ContentPlaceHolders.Add("contentplaceholder");
    }


    [DebuggerNonUserCode]
    
private ContentPlaceHolder __BuildControlContentPlaceHolder()
    
{
        ContentPlaceHolder container 
= new ContentPlaceHolder();
        
this.ContentPlaceHolder = container;
        container.ID 
= "ContentPlaceHolder";
        
if (base.ContentTemplates != null)
        
{
            
this.__Template_ContentPlaceHolder = (ITemplate) base.ContentTemplates["ContentPlaceHolder"];
        }

        
if (this.__Template_ContentPlaceHolder != null)
        
{
            
this.__Template_ContentPlaceHolder.InstantiateIn(container);
            
return container;
        }

        IParserAccessor accessor 
= container;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n            "));
        Button button 
= this.__BuildControlmaster();
        accessor.AddParsedSubObject(button);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n        "));
        
return container;
    }


    [DebuggerNonUserCode]
    
private HtmlForm __BuildControlform1()
    
{
        HtmlForm form 
= new HtmlForm();
        
this.form1 = form;
        form.ID 
= "form1";
        IParserAccessor accessor 
= form;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    <div>\r\n        "));
        ContentPlaceHolder holder 
= this.__BuildControlContentPlaceHolder();
        accessor.AddParsedSubObject(holder);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    </div>\r\n    "));
        
return form;
    }


    [DebuggerNonUserCode]
    
private Button __BuildControlmaster()
    
{
        Button button 
= new Button();
        
this.master = button;
        button.ApplyStyleSheetSkin(
this.Page);
        button.ID 
= "master";
        button.Text 
= "master";
        
return button;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlTree(masterpage_master __ctrl)
    
{
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n\r\n<!DOCTYPE html PUBLIC \"//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n    <title></title>\r\n</head>\r\n<body>\r\n    "));
        HtmlForm form = this.__BuildControlform1();
        accessor.AddParsedSubObject(form);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n</body>\r\n</html>\r\n"));
    }


    [DebuggerNonUserCode]
    
protected override void FrameworkInitialize()
    
{
        
base.FrameworkInitialize();
        
this.__BuildControlTree(this);
    }


    
protected HttpApplication ApplicationInstance
    
{
        
get
        
{
            
return this.Context.ApplicationInstance;
        }

    }


    
protected DefaultProfile Profile
    
{
        
get
        
{
            
return (DefaultProfile) this.Context.Profile;
        }

    }


    
protected override bool SupportAutoEvents
    
{
        
get
        
{
            
return false;
        }

    }


    [TemplateContainer(
typeof(MasterPage)), TemplateInstance(TemplateInstance.Single)]
    
public virtual ITemplate Template_ContentPlaceHolder
    
{
        
get
        
{
            
return this.__Template_ContentPlaceHolder;
        }

        
set
        
{
            
this.__Template_ContentPlaceHolder = value;
        }

    }

}

 


[CompilerGlobalScope]
public class default_aspx : Page, IRequiresSessionState, IHttpHandler
{
    
private static object __fileDependencies;
    
private static bool __initialized;
    
protected Button @default;

    [DebuggerNonUserCode]
    
public default_aspx()
    
{
        
base.AppRelativeVirtualPath = "~/Default.aspx";
        
if (!__initialized)
        
{
            
string[] virtualFileDependencies = new string[] "~/Default.aspx""~/MasterPage.master" };
            __fileDependencies 
= base.GetWrappedFileDependencies(virtualFileDependencies);
            __initialized 
= true;
        }

        
base.Server.ScriptTimeout = 0x1c9c380;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlContent(Control __ctrl)
    
{
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    "));
        Button button 
= this.__BuildControldefault();
        accessor.AddParsedSubObject(button);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n"));
    }


    [DebuggerNonUserCode]
    
private Button __BuildControldefault()
    
{
        Button button 
= new Button();
        
this.@default = button;
        button.TemplateControl 
= this;
        button.ApplyStyleSheetSkin(
this);
        button.ID 
= "default";
        button.Text 
= "default";
        
return button;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlTree(default_aspx __ctrl)
    
{
        __ctrl.Title 
= "";
        __ctrl.MasterPageFile 
= "~/MasterPage.master";
        
this.InitializeCulture();
        
base.AddContentTemplate("ContentPlaceHolder"new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControlContent)));
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n\r\n"));
    }


    [DebuggerNonUserCode]
    
protected override void FrameworkInitialize()
    
{
        
base.FrameworkInitialize();
        
this.__BuildControlTree(this);
        
base.AddWrappedFileDependencies(__fileDependencies);
        
base.Request.ValidateInput();
    }


    [DebuggerNonUserCode]
    
public override int GetTypeHashCode()
    
{
        
return 2002306427;
    }


    [DebuggerNonUserCode]
    
public override void ProcessRequest(HttpContext context)
    
{
        
base.ProcessRequest(context);
    }


    
protected HttpApplication ApplicationInstance
    
{
        
get
        
{
            
return this.Context.ApplicationInstance;
        }

    }


    
public masterpage_master Master
    
{
        
get
        
{
            
return (masterpage_master) base.Master;
        }

    }


    
protected DefaultProfile Profile
    
{
        
get
        
{
            
return (DefaultProfile) this.Context.Profile;
        }

    }


    
protected override bool SupportAutoEvents
    
{
        
get
        
{
            
return false;
        }

    }

}

      

      我们首先观察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字段。


public MasterPage Master
{
    
get
    
{
        
if ((this._master == null&& !this._preInitWorkComplete)
        
{
            
this._master = MasterPage.CreateMaster(thisthis.Context, this._masterPageFile, this._contentTemplateCollection);
        }

        
return this._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的唯一子 控件。

      

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

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

 

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

 

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏