ASP.NET 2.0引入了一个新的功能,称为模板页面(Master Page),利用这一功能可以为每个页面定义能共享的通用区域,像页头、页脚、菜单等。模板页面能够把通用的布局代码放到一个单独的文件中,然后使其他内 容页面在外观上继承于它。一个模板页面就可以包含网站的总体布局。内容页面能从模板页面继承外观,并且把它们自己的内容放到模板页面定义的一个 ContentPlaceHolder控件中。其实,这只具有了外观继承的效果,并未真正实现OOP意义上的继承,实际上模板页面的实现基于模板模型。
模板页面的扩展名为.master,类似于用户控件。下面是一个新建模板页面的代码。 注意新建母版页中含有form元素,如果内容页面也含有表单,会发生错误。可以将母版页中的form元素删去。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>无标题页</title>
</head>
<body>
<div>
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server"></asp:contentplaceholder>
</div>
</body>
</html>
可以看出来,它与标准页面非常接近,除了在页面顶端,它使用的是一个@Master指令,而不是@Page指令。它可以申明一个或者多个 ContentPlaceHolder控件,.aspx文件可以在其中添加自己的内容。
我们在母版页中加入两幅图片作为 通用区域。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>无标题页</title>
</head>
<body>
<img src="images/Bluehills.jpg" style="width: 660px; height: 175px" />
<div>
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server"></asp:contentplaceholder>
</div>
<img src="images/Bluehills.jpg" style="width: 660px; height: 175px" />
</body>
</html>
由于在模板页面中已经定义了<html>、<head>、<body> 和<form>标记,因此在内容页面中不能再重新定义它们。内容页面只需定义要在ContentPlaceHolder控件中显示的内容。下 面摘录了一个内容页面的例子:
<asp:Content ID="content1" ContentPlaceHolderID="ContentPlaceHolder1" runat=server>
<form id="form1" runat="server">
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" />
</form>
</asp:Content>
第一个关键点是@Page指令,它设置MasterPageFile属性来指定所使用的模板页面的虚拟路径。内容放在Content 控件中,Content的ContentPlaceHolderID属性必须同模板页面中某个ContentPlaceHolder控件的ID保持一致。 在一个内容页面中,只能放置Content控件,其他用来实际进行显示的ASP控件必须组成一组放置在最外层的Content 控件中。另外要注意的是,@Page指令有一个新的属性Title,该属性允许覆盖掉模板页面中<title>元标记所给的值。如果没有在内 容页面中指定该属性,那么就用模板页面中指定的title来代替。
结果图:
下图给出了一个模板页面功能的图形说明。
当在Visual Studio中编辑一个内容页面时,它把模板页面和内容页面结合起来显示在表单设计器中,但是模板页面部分呈灰色显示。这样做的主要目的是为了提醒开发人员在编辑一个内容页面时不能修改模板页面中的内容。
要指出的是,模板页面也有一个后台代码文件,可以用于定义一些C#属性和函数,这些函数可以被.aspx或者是内容页面的后台代码文件访问。
当在一个模板页面中定义了ContentPlaceHolder后,也可以指定它的默认内容。在内容页面中没有为ContentPlaceHolder提供Content控件时,就可以使用它的默认值。
如果想为不同的一组内容页面设置不同的模板页面,页面上的MasterPageFile属性将会起到作用。但如果网站上的所有页面都使用同一个模板页面,则可以在web.config文件中通过<pages>元素直接来指定,如下所示:
<pages masterPageFile="~/Template.master" />
如果还在页面中指定了MasterPageFile属性的值,那么该值将会把web.config中所定义的值覆盖掉。
更进一步,可以把一个模板页面作为其他模板页面的内容。换言之,可以对模板页面进行嵌入,如下所示:
<asp:Content ID="child" ContentPlaceHolderID="ContentPlaceHolder1" runat=server>
<img src="images/A2.jpg" style="width: 200px; height: 175px" /> //固定通用的
<div>
<asp:contentplaceholder id="ContentPlaceHolder2" runat="server"></asp:contentplaceholder>
</div>
</asp:Content>
可以使用一个外层的模板页面定义网站总体的基本布局(经常为公司级别的布局),然后使用其他的模板页面来指定网站上特定区域的布局,像在线商店、网站管理 等。内嵌模板页面唯一的不足就是不能在Visual Studio IDE中进行即时设计(就像设计第一层模板页面时那样进行设计)。在编辑内容页面时,必须在源代码编辑器中完成代码的每一个部分,只能通过浏览器来查看结 果。不过这对于开发人员来说不是什么大问题。
在内容页面中访问模板页面
也可以从内容页面通过其Master属性访问模板页面,返回的对象是MasterPage类型的,该类型直接从UserControl继承(记得曾 经提到过模板页面同用户控件很相似)并且增加了两个属性。它展示一个控件集合,允许内容页面对模板页面上的控件进行访问。这可能是必须的,例如,在某些页 面可能需要通过编程将模板页面中的一些控件隐藏,像登录框或者标语(banner)栏。直接访问控件集合就可以完成,但是需要将Control对象转换为 正确的控件类型,需要使用弱类型方法。有一个更好的且面向对象的方法:在模板页面的后台代码类中添加自定义属性--在本例中,将一些控件的Visible 属性封装。可以这样编写:
public bool LoginBoxIsVisible |
<%@ MasterType VirtualPath="~/MasterPage.master" %> |
protected void Test_OnClick(object sender, EventArgs e) |
这里所说的"强类型化"指的是能够在该属性上使用Visual Studio的智能感知功能,当输入"this.Master."输入到第2个点的时候,就能够在智能感知下拉列表中看到新属性。
这种在内容页面中访问模板页面中对象的方式也适用于访问模板页面中的共用方法,所有的页面都可以用这种方式。如果自动输入无法访问ASP.NET在运行时创建的MasterPage对象,那么就需要通过反射来获得那些方法,但那样做速度会慢很多,使用起来不够敏捷。
对于阅读过本书第1版的读者,我想对使用OOP基页和模板页面做一个比较。在第1版中定义了一个名为ThePhile的基类,所有的内容页面都从该 基类继承。这是真实的OOP继承,但是使用起来是有限制的,因为无法继承基类中的任何外观,不得不创建一个用户控件来完成一般的视觉元素,然而在 ASP.NET 2.0中,定义一个模板页面,就可以得到完整的视觉外观继承(非OOP代码继承)。缺乏代码的继承并没有导致很多限制,因为可以通过一个 MasterType引用来访问模板页面中的代码。
4. 在运行时切换模板页面
在介绍模板页面的最后,要介绍一下内容页面在运行时动态切换模板页面的功能。可以有多个模板页面,在网站开始运行以后再挑选一个来用。可以在页面的PreInit事件处理程序中通过设置MasterPageFile属性来实现这个功能,如下所示:
protected void Page_PreInit(object sender, EventArgs e) |
PreInit事件是ASP.NET 2.0中的一个新事件,在这个事件处理程序中只能设置MasterPageFile属性。因为合并两个页面必须发生在页面生命周期的很早阶段(在Load和Init事件中就来不及了)。
当动态改变模板页面时,必须确保所有模板页面的ContentPlaceHolder控件都有相同的ID,这样无论使用哪个模板页面,内容页面的 Content控件都可以永远与它相匹配。可以构建很多个模板页面,每一个都指定了完全不同的布局,允许用户选择他们喜欢的一种。这种方法的缺点是如果在 模板页面的后台代码文件中编写了一些代码,那么需要把这些代码复制到每个页面的后台代码类中。否则,内容页面无法找到它们。另外,也无法使用强类型化的 Master属性,因为在运行时不能动态地改变模板页面的类型,只能在@MasterType 指令中设置它。由于这些原因,因此本书不准备通过使用多个模板页面来为用户提供不同的布局。而只使用一个模板页面,为它设置不同的样式表文件。因为我们已 经决定使用非表格结构的布局,所以可以通过为它应用不同的样式来改变页面的外观(字体、颜色、图片以及位置)。