嵌套的母版页 (VB)

作者 :斯科特·米切尔

演示如何将一个母版页嵌套在另一个母版页中。

介绍

在过去的九篇教程中,我们了解了如何使用母版页实现网站范围的布局。 简言之,母版页允许我们(页面开发人员)在母版页中定义通用标记,以及可在内容页上逐页自定义的特定区域。 母版页中的 ContentPlaceHolder 控件指示可自定义的区域;ContentPlaceHolder 控件的自定义标记通过内容控件在内容页中定义。

到目前为止,我们探索的母版页技术非常出色,如果你在整个网站中使用了单个布局。 但是,许多大型网站都有一个网站布局,可在各个部分进行自定义。 例如,考虑医院工作人员用于管理患者信息、活动和计费的医疗保健应用程序。 此应用程序中可能有三种类型的网页:

  • 员工成员特定的页面,教职员工可以更新可用性、查看计划或请求休假时间。
  • 员工查看或编辑特定患者的信息的特定于患者的页面。
  • 会计人员查看当前索赔状态和财务报告的特定于计费的页面。

每个页面都可以共享一个通用布局,例如顶部的菜单和底部一系列常用链接。 但是,员工、患者和特定于计费的页面可能需要自定义此通用布局。 例如,也许所有特定于员工的页面都应包含一个日历和任务列表,其中显示了当前登录用户的可用性和每日计划。 也许所有特定于患者的页面都需要显示正在编辑其信息的患者的姓名、地址和保险信息。

可以使用嵌套母版页创建此类自定义布局。 为了实现上述方案,首先创建一个母版页,该母版页定义了网站范围的布局、菜单和页脚内容,其中 ContentPlaceHolders 定义了可自定义的区域。 然后,我们将创建三个嵌套母版页,一个用于每种类型的网页。 每个嵌套母版页将在使用母版页的内容页类型中定义内容。 换句话说,特定于患者的内容页的嵌套母版页将包含标记和编程逻辑,用于显示有关正在编辑的病人的信息。 创建新的特定于患者的页面时,我们会将其绑定到此嵌套母版页。

本教程首先强调嵌套母版页的优点。 然后,它演示如何创建和使用嵌套母版页。

注意

自 .NET Framework 版本 2.0 起,就有可能嵌套母版页。 但是,Visual Studio 2005 不包括对嵌套母版页的设计时支持。 好消息是,Visual Studio 2008 为嵌套母版页提供了丰富的设计时体验。 如果你有兴趣使用嵌套母版页,但仍使用 Visual Studio 2005,请查看 Scott Guthrie 的博客文章“ VS 2005 设计时嵌套母版页的提示”。

嵌套母版页的优点

许多网站都有一个总体网站设计,以及特定于特定类型页面的更自定义的设计。 例如,在我们的演示 Web 应用程序中,我们创建了一个基本的管理部分(文件夹中的页面 ~/Admin )。 目前, ~/Admin 文件夹中的网页使用的母版页与管理部分以外的页面相同(即 Site.master ,或者 Alternate.master,具体取决于用户的选择)。

注意

目前,假装我们的网站只有一个母版页。 Site.master 在本教程的后面部分,我们将介绍如何将嵌套母版页与两个(或更多)母版页配合使用,从“使用管理节的嵌套母版页”开始。

假设我们被要求自定义管理页面的布局,以包含其他信息或链接,否则不会出现在网站中的其他页面中。 有四种实现此要求的技术:

  1. 手动添加特定于管理的信息,并链接到文件夹中的每一个内容页 ~/Admin
  2. 更新 Site.master 母版页以包含特定于“管理”部分的信息和链接,然后将代码添加到母版页,以便根据是否访问其中一个管理页来显示或隐藏这些节。
  3. 为“管理”部分创建一个新的母版页,复制标记, Site.master添加特定于管理节的信息和链接,然后更新文件夹中的内容页 ~/Admin 以使用此新的母版页。
  4. 创建一个嵌套母版页,该 Site.master 母版页绑定到该文件夹中的内容页 ~/Admin 并使用此新的嵌套母版页。 此嵌套母版页只包含特定于管理页的其他信息和链接,并且不需要重复已在其中 Site.master定义的标记。

第一个选项是最不合时宜的选项。 使用母版页的整个要点是,不必手动复制通用标记并将其粘贴到新的 ASP.NET 页。 第二个选项是可以接受的,但使应用程序更易于维护,因为它将只偶尔显示,并且要求开发人员编辑母版页来处理此标记,并且必须记住何时(确切地说,某些标记是显示还是隐藏时)。 此方法将不太可行,因为需要此单一母版页适应越来越多的网页类型的自定义。

第三个选项删除了第二个选项所浮出水面的混乱和复杂性问题。 但是,选项三的主要缺点是,它要求我们将通用布局从 Site.master 新的“管理”部分特定的母版页复制和粘贴。 如果我们后来决定更改网站范围的布局,则必须记住在两个位置更改它。

第四个选项,嵌套母版页,给我们最好的第二和第三个选项。 网站范围的布局信息保存在一个文件(顶级母版页)中,而特定于特定区域的内容将分开到不同的文件中。

本教程首先介绍如何创建和使用简单的嵌套母版页。 我们将创建全新的顶级母版页、两个嵌套母版页和两个内容页。 从“使用管理节的嵌套母版页”开始,我们将介绍如何更新现有的母版页体系结构,以包括嵌套母版页的使用。 具体而言,我们将创建嵌套母版页,并使用它在文件夹中包含内容页 ~/Admin 的其他自定义内容。

步骤 1:创建简单的顶级母版页

基于现有母版页之一创建嵌套母版页,然后更新现有内容页以使用此新的嵌套母版页,而不是顶级母版页需要一些复杂性,因为现有内容页已经需要顶级母版页中定义的某些 ContentPlaceHolder 控件。 因此,嵌套母版页还必须包含具有相同名称的同一 ContentPlaceHolder 控件。 此外,我们的特定演示应用程序具有两个母版页(Site.masterAlternate.master),这些母版页根据用户的首选项动态分配给内容页,这进一步增加了这种复杂性。 我们将介绍更新现有应用程序以在本教程的后面部分使用嵌套母版页,但让我们首先重点介绍一个简单的嵌套母版页示例。

创建一NestedMasterPages个名为的新文件夹,然后将一个新的母版页文件添加到名为该文件夹。Simple.master (有关添加此文件夹和文件后解决方案资源管理器的屏幕截图,请参阅图 1。将AlternateStyles.css样式表文件从解决方案资源管理器拖到设计器上。 这会向元素中的<head>样式表文件添加一个<link>元素,之后母版页的<head>元素的标记应如下所示:

<head runat="server"> 
 <title>Untitled Page</title> 
 <asp:ContentPlaceHolder id="head" runat="server"> 
 </asp:ContentPlaceHolder>
 <link href="../AlternateStyles.css" rel="stylesheet" type="text/css" /> 
</head>

接下来,在 Web 窗体 Simple.master中添加以下标记:

<div id="topContent"> 
 <asp:HyperLink ID="lnkHome" runat="server" 
 NavigateUrl="~/NestedMasterPages/Default.aspx" 
 Text="Nested Master Pages Tutorial (Simple)" /> 
</div> 
<div id="mainContent"> 
 <asp:ContentPlaceHolder id="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
</div>

此标记在海军背景上以白色大字体显示页面顶部标题为“嵌套母版页(简单)”的链接。 下面是 MainContent ContentPlaceHolder。 图 1 显示 Simple.master Visual Studio 设计器中加载时的母版页。

在 Visual Studio 设计器中加载时,简单点母版页。

图 01:嵌套母版页定义特定于“管理”部分中页面的内容(单击可查看全尺寸图像

步骤 2:创建简单的嵌套母版页

Simple.master 包含两个 ContentPlaceHolder 控件: MainContent 我们在 Web 窗体中添加的 ContentPlaceHolder 以及 head 元素中的 <head> ContentPlaceHolder。 如果我们要创建一个内容页面并将其绑定到 Simple.master 内容页面,则会有两个引用 ContentPlaceHolders 的内容控件。 同样,如果我们创建嵌套母版页并将其绑定到该 Simple.master 母版页,则嵌套母版页将具有两个内容控件。

让我们向名为 <a0/> 的文件夹添加新的嵌套母版页NestedMasterPages。 右键单击 NestedMasterPages 文件夹,然后选择“添加新项”。 此时会显示图 2 中显示的“添加新项”对话框。 选择母版页模板类型并键入新母版页的名称。 若要指示新母版页应为嵌套母版页,请选中“选择母版页”复选框。

接下来,单击“添加”按钮。 这将显示将内容页绑定到母版页时看到的相同“选择母版页”对话框(请参阅图 3)。 选择文件夹中的Simple.masterNestedMasterPages母版页,然后单击“确定”。

注意

如果使用 Web 应用程序项目模型而不是网站项目模型创建了 ASP.NET 网站,则不会在图 2 中显示的“添加新项”对话框中看到“选择母版页”复选框。 若要在使用 Web 应用程序项目模型时创建嵌套母版页,必须选择嵌套母版页模板(而不是母版页模板)。 选择嵌套母版页模板并单击“添加”后,将显示图 3 中显示的“选择母版页”对话框。

选中“选择母版页”复选框以添加嵌套母版页

图 02:选中“选择母版页”复选框以添加嵌套母版页(单击以查看全尺寸图像

将嵌套母版页绑定到 Simple.master 母版页

图 03:将嵌套母版页绑定到 Simple.master 母版页(单击可查看全尺寸图像

嵌套母版页的声明性标记如下所示,包含引用顶级母版页的两个 ContentPlaceHolder 控件的两个内容控件。

<%@ Master Language="VB" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNested.master.vb" Inherits="NestedMasterPages_SimpleNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 </asp:Content>

除了 <%@ Master %> 该指令,嵌套母版页的初始声明性标记与将内容页绑定到同一顶级母版页时最初生成的标记相同。 与内容页的 <%@ Page %> 指令一样, <%@ Master %> 此处的指令包括一个 MasterPageFile 指定嵌套母版页父母版页的属性。 嵌套母版页与绑定到同一顶级母版页的内容页的主要区别在于嵌套母版页可以包含 ContentPlaceHolder 控件。 嵌套母版页的 ContentPlaceHolder 控件定义内容页可以自定义标记的区域。

更新此嵌套母版页,使其在与 ContentPlaceHolder 控件相对应的 MainContent 内容控件中显示文本“Hello,from SimpleNested!”。

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <p>Hello, from SimpleNested!</p>
</asp:Content>

添加后,保存嵌套母版页,然后将一个新的内容页添加到 NestedMasterPages 名为 Default.aspx的文件夹,并将其绑定到 SimpleNested.master 母版页。 添加此页面后,你可能会惊讶地看到它不包含内容控件(见图 4)! 内容页只能访问其 母版页的 ContentPlaceHolders。 SimpleNested.master 不包含任何 ContentPlaceHolder 控件;因此,绑定到此母版页的任何内容页都不能包含任何内容控件。

“新建内容”页不包含任何内容控件

图 04:新内容页不包含内容控件(单击可查看全尺寸图像

我们需要执行的操作是更新嵌套母版页 (SimpleNested.master) 以包含 ContentPlaceHolder 控件。 通常,你将希望嵌套母版页包含其父母版页定义的每个 ContentPlaceHolder 的 ContentPlaceHolder,从而允许其子母版页或内容页处理任何顶级母版页的 ContentPlaceHolder 控件。

更新 SimpleNested.master 母版页以在其两个内容控件中包含 ContentPlaceHolder。 为 ContentPlaceHolder 控件指定与其 ContentPlaceHolder 控件引用的名称相同。 也就是说,添加一MainContent个名为 ContentPlaceHolder 控件的 ContentPlaceHolder 控件,该控件引用MainContentSimpleNested.master了 ContentPlaceHolder in Simple.master。 在引用 head ContentPlaceHolder 的内容控件中执行相同的操作。

注意

虽然建议在嵌套母版页中命名 ContentPlaceHolder 控件与顶级母版页中的 ContentPlaceHolders 相同,但不需要此命名对称性。 可以在嵌套母版页中为 ContentPlaceHolder 控件提供所需的任何名称。 但是,如果顶级母版页和嵌套母版页使用相同的名称,则更容易记住 ContentPlaceHolders 与页面哪些区域相对应。

进行这些添加后, SimpleNested.master 母版页的声明性标记应如下所示:

<%@ Master Language="VB" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNested.master.vb" Inherits="NestedMasterPages_SimpleNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 <asp:ContentPlaceHolder ID="head" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <p>Hello, from SimpleNested!</p>
 <asp:ContentPlaceHolder ID="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content>

Default.aspx删除刚刚创建的内容页,然后重新添加它,将其SimpleNested.master绑定到母版页。 这一次,Visual Studio 会将两个内容控件添加到 Default.aspx其中,引用现在定义的 SimpleNested.master ContentPlaceHolders(见图 6)。 在引用 MainContent的内容控件中添加文本“Hello,from Default.aspx!”。

图 5 显示了此处涉及的三个实体,Simple.masterSimpleNested.master以及Default.aspx它们彼此之间的关系。 如图所示,嵌套母版页为其父的 ContentPlaceHolder 实现 Content Controls。 如果这些区域需要可供内容页访问,嵌套母版页必须将自己的 ContentPlaceHolders 添加到内容控件。

顶级母版页和嵌套母版页决定了内容页的布局

图 05:顶级母版页和嵌套母版页决定内容页的布局(单击以查看全尺寸图像

此行为说明了内容页或母版页如何仅识别其父母版页。 Visual Studio Designer 也指示此行为。 图 6 显示了用于 . 的 Default.aspx设计器。 虽然设计器清楚地显示哪些区域可从内容页编辑哪些区域,哪些部分不是,但它不会消除嵌套母版页中不可编辑的区域以及顶级母版页中哪些区域存在歧义。

内容页现在包含嵌套母版页 ContentPlaceHolders 的内容控件

图 06:内容页现在包括嵌套母版页 ContentPlaceHolders 的内容控件(单击可查看全尺寸图像

步骤 3:添加第二个简单嵌套母版页

当有多个嵌套母版页时,嵌套母版页的好处更为明显。 为了说明此好处,请在 NestedMasterPages 文件夹中创建另一个嵌套母版页;命名此新的嵌套母版页 SimpleNestedAlternate.master 并将其绑定到 Simple.master 母版页。 像在步骤 2 中一样,在嵌套母版页的两个内容控件中添加 ContentPlaceHolder 控件。 此外,在与顶级母版页的 MainContent ContentPlaceHolder 相对应的内容控件中添加文本“Hello,from SimpleNestedAlternate!”。 进行这些更改后,新的嵌套母版页的声明性标记应如下所示:

<%@ Master Language="VB" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNestedAlternate.master.vb" Inherits="NestedMasterPages_SimpleNestedAlternate" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
 <asp:ContentPlaceHolder ID="head" runat="server">
 </asp:ContentPlaceHolder> </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
 <p>Hello, from SimpleNestedAlternate!</p> 
 <asp:ContentPlaceHolder ID="MainContent" runat="server">
 </asp:ContentPlaceHolder> 
 </asp:Content>

创建文件夹中命名Alternate.aspxNestedMasterPages的内容页,并将其SimpleNestedAlternate.master绑定到嵌套母版页。 在内容控件中添加与 “Hello, from Alternate!” 对应的 MainContent文本。 图 7 显示 Alternate.aspx 通过 Visual Studio 设计器查看时。

Alternate.aspx绑定到 SimpleNestedAlternate.master 母版页

图 07:绑定到母版页(单击以查看全尺寸图像SimpleNestedAlternate.master Alternate.aspx

将图 7 中的设计器与图 6 中的设计器进行比较。 这两个内容页共享顶级母版页(Simple.master即“嵌套母版页教程(简单)”标题中定义的相同布局。 然而,两者在父母版页中都定义了不同的内容-图 6 中的文本“Hello,from SimpleNested!”和图 7 中的“Hello,来自 SimpleNestedAlternate!”。 当然,此处的这些差异是微不足道的,但你可以扩展此示例以包含更有意义的差异。 例如, SimpleNested.master 该页面可能包含一个菜单,其中包含特定于其内容页的选项,而 SimpleNestedAlternate.master 可能具有与绑定到该页面的内容页相关的信息。

现在,假设我们需要对总体网站布局进行更改。 例如,假设我们想要添加指向所有内容页面的常见链接列表。 为此,我们将更新顶级母版页。 Simple.master 任何更改都会立即反映在其嵌套母版页中,并按扩展显示其内容页。

为了演示如何轻松更改总体网站布局,请打开 Simple.master 母版页并在元素之间 topContent mainContent <div> 添加以下标记:

<div id="navContent"> 
 <asp:HyperLink ID="lnkDefault" runat="server" 
 NavigateUrl="~/NestedMasterPages/Default.aspx" 
 Text="Nested Master Page Example 1" /> 
 | 
 <asp:HyperLink ID="lnkAlternate" runat="server" 
 NavigateUrl="~/NestedMasterPages/Alternate.aspx" 
 Text="Nested Master Page Example 2" /> 
</div>

这会向绑定到 Simple.master的每个页面顶部添加两个链接; SimpleNested.master或者 SimpleNestedAlternate.master,这些更改将立即应用于所有嵌套母版页及其内容页。 图 8 显示 Alternate.aspx 通过浏览器查看时。 请注意在页面顶部添加链接(与图 7 相比)。

更改为顶级母版页后,会立即反映在其嵌套母版页及其内容页中

图 08:更改为顶级母版页后,会立即反映在其嵌套母版页及其内容页中(单击可查看全尺寸图像

使用管理节的嵌套母版页

此时,我们已了解嵌套母版页的优点,并了解了如何在 ASP.NET 应用程序中创建和使用它们。 但是,步骤 1、2 和 3 中的示例涉及创建新的顶级母版页、新的嵌套母版页和新内容页。 使用现有顶级母版页和内容页将新的嵌套母版页添加到网站该怎么办?

将嵌套母版页集成到现有网站中,并将其与现有内容页相关联需要比从头开始更加努力。 步骤 4、5、6 和 7 将探索这些挑战,因为我们将演示应用程序扩充为包含管理员说明的新嵌套母版页 AdminNested.master ,并由文件夹中的 ASP.NET 页 ~/Admin 使用。

将嵌套母版页集成到我们的演示应用程序中引入了以下障碍:

  • 文件夹中的现有内容页在其母版页中 ~/Admin 具有某些期望。 对于初学者,他们希望存在某些 ContentPlaceHolder 控件。 此外, ~/Admin/AddProduct.aspx~/Admin/Products.aspx 页面调用母版页的公共 RefreshRecentProductsGrid 方法、设置其 GridMessageText 属性或为其 PricesDoubled 事件具有事件处理程序。 因此,嵌套母版页必须提供相同的 ContentPlaceHolders 和公共成员。
  • 在前面的教程中,我们增强了 BasePage 类,以基于 Session 变量动态设置 Page 对象的 MasterPageFile 属性。 如何使用嵌套母版页时支持动态母版页?

这两个挑战将浮出水面,因为我们构建嵌套母版页并从现有内容页使用它。 我们将调查并克服这些问题,因为它们出现。

步骤 4:创建嵌套母版页

我们的第一个任务是创建嵌套母版页,供“管理”部分中的页面使用。 正如我们在步骤 2 中看到的,在添加新的嵌套母版页时,我们需要指定嵌套母版页的父母版页。 但是我们有两个顶级母版页: Site.masterAlternate.master。 回想一下,我们在 Alternate.master 前面的教程中创建并在类中 BasePage 编写代码,该类在运行时将 Page 对象的 MasterPageFile 属性设置为任一 Site.masterAlternate.master 取决于 Session 变量的值 MyMasterPage

如何配置嵌套母版页,使其使用适当的顶级母版页? 有两种做法:

  • 创建两个嵌套母版页, AdminNestedSite.masterAdminNestedAlternate.master将它们分别绑定到顶级母版页 Site.masterAlternate.master。 然后BasePage,我们将对象PageMasterPageFile设置为适当的嵌套母版页。
  • 创建单个嵌套母版页并让内容页使用此特定母版页。 然后,在运行时,我们需要在运行时将嵌套母版页 MasterPageFile 的属性设置为适当的顶级母版页。 (正如你目前可能已经确定的那样,母版页也有一个 MasterPageFile 属性。

让我们使用第二个选项。 在~/Admin名为 <a0/> 的文件夹中创建单个嵌套母版页文件。 由于这两个控件 Site.masterAlternate.master 具有相同的 ContentPlaceHolder 控件集,因此你将其绑定到的母版页无关紧要,尽管我鼓励你将其绑定到 Site.master 一致性。

将嵌套母版页添加到 ~/Admin 文件夹。

图 09:向文件夹添加嵌套母版页 ~/Admin 。 (单击可查看全尺寸图像

由于嵌套母版页绑定到包含四个 ContentPlaceHolder 控件的母版页,因此 Visual Studio 会将四个内容控件添加到新的嵌套母版页文件的初始标记中。 就像我们在步骤 2 和 3 中所做的那样,在每个内容控件中添加 ContentPlaceHolder 控件,使其与顶级母版页的 ContentPlaceHolder 控件同名。 此外,将以下标记添加到与 MainContent ContentPlaceHolder 对应的 Content 控件:

<div class="instructions"> 
 <b>Administration Instructions:</b>
 <br /> 
 The pages in the Administration section allow you, the Administrator, to 
 add new products and view existing products. 
</div>

接下来,在 CSS 文件中定义 instructions CSS 类Styles.cssAlternateStyles.css。 以下 CSS 规则导致使用类样式的 instructions HTML 元素以浅黄色背景色和黑色、纯色边框显示:

.instructions 
{
 padding: 6px; 
 border: dashed 1px black; 
 background-color: #ffb; 
 margin-bottom: 10px; 
}

由于此标记已添加到嵌套母版页中,因此它只会显示在使用此嵌套母版页(即“管理”部分中的页面)的那些页面中。

将这些添加内容添加到嵌套母版页后,其声明性标记应如下所示:

<%@ Master Language="VB" MasterPageFile="~/Site.master" AutoEventWireup="false" CodeFile="AdminNested.master.vb" Inherits="Admin_AdminNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 <asp:ContentPlaceHolder ID="head" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <div class="instructions">
 <b>Administration Instructions:</b>
 <br /> 
 The pages in the Administration section allow you, the Administrator, to 
 add new products and view existing products. 
 </div>
 <asp:ContentPlaceHolder ID="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content3" ContentPlaceHolderID="QuickLoginUI" Runat="Server"> 
 <asp:ContentPlaceHolder ID="QuickLoginUI" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content4" ContentPlaceHolderID="LeftColumnContent" Runat="Server"> 
 <asp:ContentPlaceHolder ID="LeftColumnContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content>

请注意,每个内容控件都有一个 ContentPlaceHolder 控件,并且 ContentPlaceHolder 控件 ID 的属性与顶级母版页中相应的 ContentPlaceHolder 控件分配的值相同。 此外,特定于管理部分的 MainContent 标记显示在 ContentPlaceHolder 中。

图 10 显示 AdminNested.master 通过 Visual Studio 设计器查看时的嵌套母版页。 可以在内容控件顶部的 MainContent 黄色框中看到说明。

嵌套母版页扩展顶级母版页,以包括管理员的说明。

图 10:嵌套母版页扩展顶级母版页以包括管理员的说明。 (单击可查看全尺寸图像

步骤 5:更新现有内容页以使用新的嵌套母版页

每当向“管理”部分添加新内容页时,都需要将其 AdminNested.master 绑定到刚刚创建的母版页。 但是,现有内容页呢? 目前,网站中的所有内容页面都派生自 BasePage 类,该类以编程方式在运行时设置内容页的母版页。 这不是“管理”部分中的内容页面所需的行为。 相反,我们希望这些内容页始终使用该 AdminNested.master 页。 嵌套母版页负责在运行时选择正确的顶级内容页。

实现此所需行为的最佳方法是创建一个名为扩展BasePage该类的新自定义基页类AdminBasePageAdminBasePage然后,可以重写SetMasterPageFile对象并将其设置为PageMasterPageFile硬编码值“~/Admin/AdminNested.master”。 这样,派生自 AdminBasePage 的任何页面都将使用 AdminNested.master,而派生 BasePage 自的任何页面都将 MasterPageFile 根据会话变量的值 MyMasterPage 动态设置为“~/Site.master”或“~/Alternate.master”。

首先,将一个新类文件添加到 App_Code 名为 的文件夹 AdminBasePage.vb。 具有 AdminBasePage 扩展 BasePage ,然后重写 SetMasterPageFile 方法。 在该方法中, MasterPageFile 分配值“~/Admin/AdminNested.master”。 进行这些更改后,类文件应如下所示:

Public Class AdminBasePage 
 Inherits BasePage 
 Protected Overrides Sub SetMasterPageFile() 
 Me.MasterPageFile = "~/Admin/AdminNested.master" 
 End Sub 
End Class

现在,我们需要在“管理”部分派生现有内容页,BasePage而不是从AdminBasePage中派生。 转到文件夹中每个内容页 ~/Admin 的代码隐藏类文件,并进行此更改。 例如, ~/Admin/Default.aspx 在以下代码隐藏类声明中更改:

Partial Class Admin_Default 
 Inherits BasePage

更改为:

Partial Class Admin_Default 
 Inherits AdminBasePage

图 11 描述了顶级母版页(Site.masterAlternate.master)、嵌套母版页(AdminNested.master)和“管理”部分内容页彼此的关系。

嵌套母版页定义特定于管理部分中页面的内容

图 11:嵌套母版页定义特定于管理部分中页面的内容(单击可查看全尺寸图像

步骤 6:镜像母版页的公共方法和属性

回想一下, ~/Admin/AddProduct.aspx 母版页和 ~/Admin/Products.aspx 页面以编程方式与母版页交互: ~/Admin/AddProduct.aspx 调用母版页的公共 RefreshRecentProductsGrid 方法并设置其 GridMessageText 属性; ~/Admin/Products.aspx 具有事件的 PricesDoubled 事件处理程序。 在前面的教程中,我们创建了一个MustInheritBaseMasterPage定义这些公共成员的类。

~/Admin/AddProduct.aspx~/Admin/Products.aspx页面假定其母版页派生自BaseMasterPage类。 但是,页面 AdminNested.master 当前扩展了 System.Web.UI.MasterPage 该类。 因此,当访问 ~/Admin/Products.aspx 某个 InvalidCastException 消息时,消息是:“无法将类型为”ASP.admin_adminnested_master“的对象强制转换为类型为”BaseMasterPage”。

若要解决此问题,我们需要扩展 AdminNested.master 代码隐藏类 BaseMasterPage。 从以下项更新嵌套母版页的代码隐藏类声明:

Partial Class Admin_AdminNested 
 Inherits System.Web.UI.MasterPage

更改为:

Partial Class Admin_AdminNested 
 Inherits BaseMasterPage

我们还没有完成。 我们需要重写标记为 MustOverride(即 RefreshRecentProductsGridGridMessageText) 的成员。 这些成员由顶级母版页用来更新其用户界面。 (实际上,只有 Site.master 母版页使用这些方法,尽管这两个顶级母版页都实现了这些方法,因为两者都是扩展 BaseMasterPage的。

虽然我们需要实现 AdminNested.master这些成员,但这些实现只需在嵌套母版页使用的顶级母版页中调用相同的成员即可。 例如,当“管理”部分中的内容页调用嵌套母版页的方法时,所有嵌套母版页 RefreshRecentProductsGrid 都需要执行该操作,进而调用 Site.masterAlternate.master's RefreshRecentProductsGrid 方法。

若要实现此目的,请先将以下 @MasterType 指令添加到顶部 AdminNested.master

<%@ MasterType TypeName="BaseMasterPage" %>

回想一下,该指令向 @MasterType 名为 Master 的代码隐藏类添加了强类型属性。 然后重写 RefreshRecentProductsGridGridMessageText 成员,只需委托对 Master相应方法的调用:

Partial Class Admin_AdminNested 
 Inherits BaseMasterPage 
 Public Overrides Property GridMessageText() As String 
 Get 
 Return Master.GridMessageText 
 End Get 
 Set(ByVal value As String) 
 Master.GridMessageText = value 
 End Set 
 End Property 
 Public Overrides Sub RefreshRecentProductsGrid()
 Master.RefreshRecentProductsGrid()
 End Sub 
End Class

有了此代码,你应该能够访问和使用“管理”部分中的内容页面。 图 12 显示 ~/Admin/Products.aspx 通过浏览器查看的页面。 可以看到,页面包括“管理说明”框,该框在嵌套母版页中定义。

管理部分中的内容页包括每个页面顶部的说明

图 12:“管理”部分中的内容页包括每个页面顶部的说明(单击以查看全尺寸图像

步骤 7:在运行时使用适当的顶级母版页

虽然“管理”部分中的所有内容页面都完全正常运行,但它们都使用相同的顶级母版页,并忽略用户选择 ChooseMasterPage.aspx的母版页。 此行为是由于嵌套母版页在其指令中静态设置为Site.masterMasterPageFile<%@ Master %>属性。

若要使用最终用户选择的顶级母版页,我们需要将 AdminNested.master's MasterPageFile 属性设置为 Session 变量中的 MyMasterPage 值。 由于我们设置了内容页MasterPageFile的属性,因此你可能会认为我们会在“代码隐藏”类中或AdminNested.master“代码隐藏”类中BaseMasterPage设置嵌套母版页MasterPageFileBasePage的属性。 但是,这不起作用,因为我们需要在 PreInit 阶段结束时设置 MasterPageFile 属性。 我们可以从母版页以编程方式点击页面生命周期的最早时间是 Init 阶段(在 PreInit 阶段之后发生)。

因此,我们需要从内容页设置嵌套母版页 MasterPageFile 的属性。 使用 AdminNested.master 母版页的唯一内容页派生自 AdminBasePage。 因此,我们可以将此逻辑放在其中。 在步骤 5 中,我们重写 SetMasterPageFile 了该方法,将 Page 对象的 MasterPageFile 属性设置为“~/Admin/AdminNested.master”。 更新 SetMasterPageFile 以将母版页 MasterPageFile 的属性设置为会话中存储的结果:

Public Class AdminBasePage 
 Inherits BasePage 
 Protected Overrides Sub SetMasterPageFile() 
 Me.MasterPageFile = "~/Admin/AdminNested.master" 
 Page.Master.MasterPageFile = MyBase.GetMasterPageFileFromSession() 
 End Sub 
End Class

GetMasterPageFileFromSession我们在前面的教程中添加到BasePage类的方法基于 Session 变量值返回相应的母版页文件路径。

完成此更改后,用户的母版页选择将延续到“管理”部分。 图 13 显示了与图 12 相同的页面,但在用户将其母版页选择更改为 Alternate.master之后。

嵌套管理页使用用户选择的顶级母版页

图 13:嵌套管理页使用用户选择的顶级母版页(单击以查看全尺寸图像

总结

与内容页绑定到母版页的方式类似,可以通过将子母版页绑定到父母版页来创建嵌套母版页。 子母版页可以为其父级的 ContentPlaceHolders 定义内容控件;然后,它可以将自己的 ContentPlaceHolder 控件(以及其他标记)添加到这些内容控件。 嵌套母版页在大型 Web 应用程序中非常有用,其中所有页面共享总体外观,但网站的某些部分需要唯一的自定义。

快乐编程!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

斯科特·米切尔是多个 ASP/ASP.NET 书籍的作者,4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 3.5。 斯科特可以在他的博客上 mitchell@4GuysFromRolla.com 或通过他的博客联系 http://ScottOnWriting.NET

特别感谢

本教程系列由许多有用的审阅者审阅。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请在以下位置放置我一行 mitchell@4GuysFromRolla.com