真正动态的主页
以编程方式创建 ContentPlaceHolders 和 Contents
引言
母版页是 ASP.NET 2.0 中一个非常酷的功能。它们使您可以轻松地在整个网站中创建统一的外观和感觉。它们还提供了一种在运行时轻松设置/更改母版页的方法。这通过在 PreInit
事件中设置页面的 MasterPageFile
属性来实现。但是,这要求母版页具有 ContentPlaceHolders
,并且页面上的 Content
控件通过 ContentPlaceHolderID
属性引用它们。本文重点介绍如何以编程方式创建 ContentPlaceHolders
、Contents
以及将它们链接在一起。
背景
当我第一次使用 ASP.NET 2.0 时,我使用的第一个功能之一就是母版页。我很快就学会了如何在运行时在它们之间切换。过了一段时间,我在做一个小型项目,目标是尽可能多地动态创建东西(类似于一个 Web 应用程序框架)。我曾想过使用存储在数据库(作为元数据)中的母版页,然后动态构建它们。我很快发现这项任务并不像看起来那么容易。它需要的不仅仅是如何通过代码创建控件的知识。我做了一些关于如何正确完成此任务的研究,并想分享这项技术,因此本文应运而生。
Using the Code
提供的示例包含两个页面和一个母版页。它的唯一目的是演示如何操作。在起始页(Default.aspx)上,您需要输入一个数字,按一个按钮,然后让魔法开始。尽管下一页上的视觉效果并不十分吸引人(只能看到一些文本),但魔法仍在后台发生。让我们看看发生了什么。为此,您应该对 ASP.NET 页面生命周期有一定的了解。
最重要的一步是创建页面上的内容。您会惊讶地发现,无需创建 Content
类。而是应该使用 Page
类的 AddContentTemplate
方法。您可能从未听说过此方法,这是因为它应用了 [EditorBrowsable(EditorBrowsableState.Never)]
属性,因此在 IntelliSense 中不可见。MSDN 提供了一些关于它的信息,您可以在这里阅读:此处。您需要两个参数,第一个是它将链接到的 ContentPlaceHolder
的名称(您只能在标记中设置 ContentPlaceHolderID
属性),第二个是 ITemplate
。如果您从未处理过模板化控件,您可能会觉得这有点复杂,但实际上并非如此。但是,解释这一点仍然超出了本文的范围。在此示例中,所有内容都相同,但并非必须如此,而且更容易实现,并且足以用于演示目的。
protected void Page_PreInit(object sender, EventArgs e)
{
//...same check as in the constructor of the Master Page
for (int i = 1; i < num + 1; i++)
base.AddContentTemplate("ContentPlaceHolder" + i.ToString(),
new CompiledTemplateBuilder(new BuildTemplateMethod(this.Build)));
}
在 ASP.NET 中使用构造函数是一个非常不常见的情况,但在这种情况下,没有其他地方可以放置所需的代码,因为母版页的 Init
事件已经太晚了,而且没有 PreInit
事件。您只需要将稍后要创建的 ContentPlaceHolder
控件的名称添加到 Master
类的 ContentPlaceHolders
集合中。请注意,您必须使用小写名称。
public MasterPage()
{
object cphnum = HttpContext.Current.Session["cphnum"];
if (cphnum == null || !(cphnum is int))
return;
num = (int)cphnum;
for (int i = 1; i < num + 1; i++)
base.ContentPlaceHolders.Add("contentplaceholder" + i.ToString());
}
最后,您应该实例化 ContentPlaceHolders
并将它们添加到页面的控件层次结构中。这也通过一个 for
循环来实现,以便创建所需数量的控件。为此,您必须使用母版页所基于的 ContentTemplates
集合,该集合的类型为 ITemplate
。
protected void Page_Init(object sender, EventArgs e)
{
//...
for (int i = 1; i < num + 1; i++)
{
ContentPlaceHolder cph = new ContentPlaceHolder();
cph.ID = "ContentPlaceHolder" + i.ToString();
PlaceHolder1.Controls.Add(cph);
((ITemplate)base.ContentTemplates["ContentPlaceHolder" +
i.ToString()]).InstantiateIn(cph);
}
}
现在您已经了解了基础知识,您可以轻松地扩展此示例,使其比当前形式更有用。
嵌套母版页
使用此技术创建嵌套母版页也得到支持,但是需要对代码进行少量修改。理论上,嵌套母版页只不过是一个带有自己的 ContentPlaceHolders
的母版页,以及用于填充其父占位符的 Contents
的普通页面。请注意,在母版页可以使用内容填充其占位符之前,内容必须存在于内容页面上,但 init 事件的触发顺序相反,因此主母版页的事件先于嵌套母版页的事件触发,从而导致 NullReferenceException(或者在检查时,内容为空)。构造函数也太早了,不适合此目的,因为那样会破坏嵌套母版页和内容页面之间的协同作用,原因类似。因此,必须使用另一个称为 FrameworkInitialize() 的事件。它在所需的时间窗口触发,并且触发顺序正确,因此可以实现深度嵌套。此场景也有一个演示,请参阅上方下载列表中的内容。
历史
- 2007年2月12日 - 文章首次发布
- 2007年4月16日 - 添加了 VB.NET 演示
- 2012年5月7日 - 添加了关于嵌套的部分