嵌套的母版页 (C#)

作者 :Scott Mitchell

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

简介

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

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

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

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

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

本教程首先重点介绍嵌套母版页的优点。 然后演示如何创建和使用嵌套母版页。

注意

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

嵌套母版页的优点

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

注意

现在,假装网站只有一个母版页 Site.master。 我们将讨论使用嵌套母版页和两个 (或更多个) 母版页的问题,从本教程后面部分的“使用嵌套母版页管理部分”开始。

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

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

第一个选项是最不讨人意的。 使用母版页的整个要点是,不必手动将常用标记复制并粘贴到新的 ASP.NET 页。 第二个选项是可以接受的,但应用程序会降低可维护性,因为它使用仅偶尔显示的标记来填充母版页,并且要求编辑母版页的开发人员处理此标记,并且必须记住显示某些标记的时间和隐藏时间。 此方法将不太可行,因为需要此单个母版页适应越来越多的网页类型的自定义。

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

第四个选项(嵌套母版页)为我们提供了第二个和第三个选项中最好的选项。 网站范围的布局信息在一个文件(顶级母版页)中维护,而特定于特定区域的内容则分为不同的文件。

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

步骤 1:创建简单 Top-Level 母版页

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

创建名为 NestedMasterPages 的新文件夹,然后将新的母版页文件添加到名为 Simple.master的文件夹。 (请参阅图 1,了解添加此文件夹和文件后解决方案资源管理器的屏幕截图。) 将AlternateStyles.css样式表文件从解决方案资源管理器拖到Designer。 这会向 元素中的样式表文件<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 Designer中加载时的母版页。

在 Visual Studio Designer加载时的简单点母版页。

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

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

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

让我们将新的嵌套母版页添加到 NestedMasterPages 名为 SimpleNested.master的文件夹。 右键单击文件夹, NestedMasterPages 然后选择“添加新项”。 此时会显示“添加新项”对话框,如图 2 所示。 选择母版页模板类型,然后键入新母版页的名称。 若要指示新的母版页应为嵌套母版页,检查“选择母版页”复选框。

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

注意

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

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

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

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

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

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

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNested.master.cs" 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 属性,该属性指定嵌套母版页的父母版页。 嵌套母版页与绑定到同一顶级母版页的内容页之间的main区别在于嵌套母版页可以包含 ContentPlaceHolder 控件。 嵌套母版页的 ContentPlaceHolder 控件定义内容页可在其中自定义标记的区域。

更新此嵌套母版页,使其在与 ContentPlaceHolder 控件对应的 MainContent Content 控件中显示文本“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 控件;因此,绑定到此母版页的任何内容页都不能包含任何 Content 控件。

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

图 04:“新内容”页不包含任何内容控件 (单击以查看全尺寸图像)

我们需要做的是更新嵌套母版页 (SimpleNested.master) 以包含 ContentPlaceHolder 控件。 通常,你会希望嵌套母版页为其父母版页定义的每个 ContentPlaceHolder 包含一个 ContentPlaceHolder,从而允许其子母版页或内容页与任何顶级母版页的 ContentPlaceHolder 控件一起使用。

SimpleNested.master更新母版页以在其两个 Content 控件中包含 ContentPlaceHolder。 为 ContentPlaceHolder 控件指定其 Content 控件引用的 ContentPlaceHolder 控件相同的名称。 也就是说,将名为 的 MainContent ContentPlaceHolder 控件添加到 中 SimpleNested.master 引用 MainContent ContentPlaceHolder 的 Simple.masterContentHolder 控件。 在引用 head ContentPlaceHolder 的 Content 控件中执行相同的操作。

注意

虽然我建议将嵌套母版页中的 ContentPlaceHolder 控件命名为与顶级母版页中的 ContentPlaceHolders 相同,但不需要这种命名对称性。 可以为嵌套母版页中的 ContentPlaceHolder 控件指定任何你喜欢的名称。 但是,如果顶级母版页和嵌套母版页使用相同的名称,则更容易记住 ContentPlaceHolders 与页面的哪些区域相对应。

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

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master"AutoEventWireup="false" CodeFile="SimpleNested.master.cs" 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 向 中添加两个 Content 控件 Default.aspx,引用现在在 SimpleNested.master (请参阅图 6) 中定义的 ContentPlaceHolders。 在引用 MainContent的内容控件中添加文本“Hello, from Default.aspx!”。

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

Top-Level 和嵌套母版页决定内容页的布局

图 05:Top-Level 和嵌套母版页指示内容页的布局 (单击以查看全尺寸图像)

此行为说明了内容页或母版页如何仅识别其父母版页。 此行为也由 Visual Studio Designer指示。 图 6 显示了 的Default.aspxDesigner。 虽然Designer清楚地显示哪些区域可从内容页编辑,哪些部分不可编辑,但它不会区分嵌套母版页中的不可编辑区域和顶级母版页中的区域。

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

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

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

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

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNestedAlternate.master.cs" 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>

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

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

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

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

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

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

<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.masterSimpleNested.masterSimpleNestedAlternate.master的每个页面顶部;这些更改立即应用于所有嵌套母版页及其内容页。 图 8 显示了 Alternate.aspx 通过浏览器查看时的情况。 请注意,在页面顶部添加的链接 (与图 7) 相比。

更改为 Top-Level 母版页立即反映在其嵌套母版页及其内容页中

图 08:更改为 Top-Level 母版页立即反映在其嵌套母版页及其内容页 (单击以查看全尺寸图像)

使用“管理”部分的嵌套母版页

此时,我们已经了解了嵌套母版页的优点,并了解了如何在 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,我们将 对象的 MasterPageFile 设置为Page相应的嵌套母版页。
  • 创建单个嵌套母版页,让内容页使用此特定母版页。 然后,在运行时,我们需要在运行时将嵌套母版页的 MasterPageFile 属性设置为相应的顶级母版页。 (正如你目前可能已了解的那样,母版页还具有 MasterPageFile 属性。)

让我们使用第二个选项。 在名为 AdminNested.master~/Admin文件夹中创建单个嵌套母版页文件。 由于 和 Alternate.master 具有相同Site.master的一组 ContentPlaceHolder 控件,因此你将其绑定到哪个母版页并不重要,尽管出于一致性考虑,我还是建议将其绑定到 Site.master

将嵌套母版页添加到 ~/管理员 文件夹。

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

由于嵌套母版页绑定到具有四个 ContentPlaceHolder 控件的母版页,因此 Visual Studio 将四个 Content 控件添加到新的嵌套母版页文件的初始标记中。 正如我们在步骤 2 和步骤 3 中所做的那样,在每个 Content 控件中添加一个 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>

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

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

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

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

<%@ Master Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="false" CodeFile="AdminNested.master.cs" 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>

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

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

嵌套母版页扩展 Top-Level 母版页,以包含管理员的说明。

图 10:嵌套母版页将 Top-Level 母版页扩展为包含管理员的说明。 (单击以查看全尺寸图像)

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

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

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

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

public class AdminBasePage : BasePage 
{ 
    protected override void SetMasterPageFile() 
    { 
        this.MasterPageFile = "~/Admin/AdminNested.master"; 
    } 
}

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

public partial class Admin_Default : BasePage

到:

public partial class Admin_Default : AdminBasePage

图 11 描述了顶级母版页 (Site.masterAlternate.master) 、嵌套母版页 (AdminNested.master) 以及管理部分内容页之间的关系。

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

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

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

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

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

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

public partial class Admin_AdminNested : System.Web.UI.MasterPage

到:

public partial class Admin_AdminNested : BaseMasterPage

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

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

为此,首先将以下 @MasterType 指令添加到 的 AdminNested.master顶部:

<%@ MasterType TypeName="BaseMasterPage" %>

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

public partial class Admin_AdminNested : BaseMasterPage 
{ 
    public override void RefreshRecentProductsGrid() 
    { 
        Master.RefreshRecentProductsGrid();
    } 
    public override string GridMessageText
    { 
        get 
        {
            return Master.GridMessageText;
        } 
        set
        { 
            Master.GridMessageText = value; 
        } 
    }
}

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

“管理”部分中的内容页包括每页顶部的说明

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

步骤 7:在运行时使用适当的 Top-Level 母版页

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

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

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

public class AdminBasePage : BasePage 
{ 
    protected override void SetMasterPageFile() 
    { 
        this.MasterPageFile = "~/Admin/AdminNested.master"; 
        Page.Master.MasterPageFile = base.GetMasterPageFileFromSession(); 
    } 
}

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

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

嵌套管理页使用用户选择的 Top-Level 母版页

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

总结

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

编程快乐!

深入阅读

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

关于作者

Scott Mitchell 是多本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 3.5。 可以在 上联系 mitchell@4GuysFromRolla.com 斯科特,也可以通过他的博客在 联系 http://ScottOnWriting.NET

特别感谢

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