Master Pages:: Nested Master Pages
Master Pages:: Nested Master Pages
Introduction
Over the course of the past nine tutorials we have seen how to implement a site-wide layout
with master pages. In a nutshell, master pages allow us, the page developer, to define
common markup in the master page along with specific regions that can be customized on a
content page-by-content page basis. The ContentPlaceHolder controls in a master page
indicate the customizable regions; the customized markup for the ContentPlaceHolder
controls are defined in the content page via Content controls.
The master page techniques we've explored thus far are great if you have a single layout
used across the entire site. However, many large websites have a site layout that is
customized across various sections. For example, consider a health care application used by
hospital staff to manage patient information, activities, and billing. There may be three
types of web pages in this application:
Staff member-specific pages where staff members can update availability, view
schedules, or request vacation time.
Patient-specific pages where staff members view or edit information for a specific
patient.
Billing-specific pages where accountants review current claim statuses and financial
reports.
Every page might share a common layout, such as a menu across the top and a series of
frequently used links along the bottom. But the staff-, patient-, and billing-specific pages
may need to customize this generic layout. For example, perhaps all staff-specific pages
should include a calendar and task list showing the currently logged on user's availability
and daily schedule. Perhaps all patient-specific pages need to show the name, address, and
insurance information for the patient whose information is being edited.
It's possible to create such customized layouts by using nested master pages. To implement
the above scenario, we would start by creating a master page that defined the site-wide
layout, the menu and footer content, with ContentPlaceHolders defining the customizable
regions. We would then create three nested master pages, one for each type of web page.
Each nested master page would define the content among the type of content pages that
use the master page. In other words, the nested master page for patient-specific content
pages would include markup and programmatic logic for displaying information about the
patient being edited. When creating a new patient-specific page we would bind it to this
nested master page.
This tutorial starts by highlighting the benefits of nested master pages. It then shows how
to create and use nested master pages.
Note: Nested master pages have been possible since version 2.0 of the .NET
Framework. However, Visual Studio 2005 did not include design-time support for
nested master pages. The good news is that Visual Studio 2008 offers a rich design-
time experience for nested master pages. If you are interested in using nested
master pages but are still using Visual Studio 2005, check out Scott Guthrie's blog
entry, Tips for Nested Master Pages in VS 2005 Design-Time.
The Benefits of Nested Master Pages
Many websites have an overarching site design as well as more customized designs specific
to certain types of pages. For instance, in our demo web application we have created a
rudimentary Administration section (the pages in the ~/Admin folder). Currently the web
pages in the ~/Admin folder use the same master page as those pages not in the
administration section (namely, Site.master or Alternate.master, depending on the
user's selection).
Note: For now, pretend that our site has just one master page, Site.master. We'll
address using nested master pages with two (or more) master pages starting with
"Using a Nested Master Page for the Administration Section" later in this tutorial.
Imagine that we were asked to customize the layout of the Administration pages to include
additional information or links that would not otherwise be present in other pages in the
site. There are four techniques to implement this requirement:
1. Manually add the Administration-specific information and links to every content page in
the ~/Admin folder.
2. Update the Site.master master page to include the Administration section-specific
information and links, and then add code to the master page to show or hide these
sections based on whether one of the Administration pages is being visited.
3. Create a new master page specifically for the Administration section, copy over the
markup from Site.master, add the Administration section-specific information and
links, and then update the content pages in the ~/Admin folder to use this new master
page.
4. Create a nested master page that binds to Site.master and have the content pages in
the ~/Admin folder use this new nested master page. This nested master page would
include just the additional information and links specific to the Administration pages and
would not need to repeat the markup already defined in Site.master.
The first option is the least palatable. The whole point of using master pages is to move
away from having to manually copy and paste common markup to new ASP.NET pages. The
second option is acceptable, but makes the application less maintainable as it bulks up the
master pages with markup that is only occasionally displayed and requires developers
editing the master page to work around this markup and to have to remember when,
exactly, certain markup is displayed versus when it is hidden. This approach would be less
tenable as customizations from more and more types of web pages needed to be
accommodated by this single master page.
The third option removes the clutter and complexity issues the surfaced with the second
option. However, option three's main drawback is that it requires us to copy and paste the
common layout from Site.master to the new Administration section-specific master page.
If we later decide to change the site-wide layout we have to remember to change it in two
places.
The fourth option, nested master pages, give us the best of the second and third options.
The site-wide layout information is maintained in one file - the top-level master page - while
the content specific to particular regions is separated out into different files.
This tutorial starts with a look at creating and using a simple nested master page. We create
a brand new top-level master page, two nested master pages, and two content pages.
Starting with "Using a Nested Master Page for the Administration Section," we look at
updating our existing master page architecture to include the use of nested master pages.
Specifically, we create a nested master page and use it to include additional custom content
for the content pages in the ~/Admin folder.
Next, add the following markup within the Web Form of 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>
This markup displays a link titled "Nested Master Pages (Simple)" at the top of the page in a
large white font on a navy background. Beneath that is the MainContent
ContentPlaceHolder. Figure 1 shows the Simple.master master page when loaded in the
Visual Studio Designer.
Figure 01: The Nested Master Page Defines Content Specific to the Pages in the
Administration Section
Note: If you created your ASP.NET website using the Web Application Project model
instead of the Web Site Project model you will not see the "Select master page"
checkbox in the Add New Item dialog box shown in Figure 2. To create a nested
master page when using the Web Application Project model you must choose the
Nested Master Page template (instead of the Master Page template). After selecting
the Nested Master Page template and clicking Add, the same Select a Master Page
dialog box shown in Figure 3 will appear.
Figure 02: Check the "Select master page" Checkbox to Add a Nested Master Page
Figure 03: Bind the Nested Master Page to the Simple.master Master Page
The nested master page's declarative markup, shown below, contains two Content controls
referencing the top-level master page's two ContentPlaceHolder controls.
<%@ 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>
Except for the <%@ Master %> directive, the nested master page's initial declarative markup
is identical to the markup that is initially generated when binding a content page to the
same top-level master page. Like a content page's <%@ Page %> directive, the <%@ Master
%> directive here includes a MasterPageFile attribute that specifies the nested master
page's parent master page. The main difference between the nested master page and a
content page bound to the same top-level master page is that the nested master page can
include ContentPlaceHolder controls. The nested master page's ContentPlaceHolder controls
define the regions where the content pages can customize the markup.
Update this nested master page so that it displays the text "Hello, from SimpleNested!" in
the Content control that corresponds to the MainContent ContentPlaceHolder control.
After making this addition, save the nested master page and then add a new content page
to the NestedMasterPages folder named Default.aspx, and bind it to the
SimpleNested.master master page. Upon adding this page you may be surprised to see
that it contains no Content controls (see Figure 4)! A content page can only access its
parent master page's ContentPlaceHolders. SimpleNested.master does not contain any
ContentPlaceHolder controls; therefore, any content page bound to this master page cannot
contain any Content controls.
Figure 04: The New Content Page Contains No Content Controls
What we need to do is update the nested master page (SimpleNested.master) to include
ContentPlaceHolder controls. Typically you'll want your nested master pages to include a
ContentPlaceHolder for each ContentPlaceHolder defined by its parent master page, thereby
allowing its child master page or content page to work with any of the top-level master
page's ContentPlaceHolder controls.
Update the SimpleNested.master master page to include a ContentPlaceHolder in its two
Content controls. Give the ContentPlaceHolder controls the same name as the
ContentPlaceHolder control their Content control refers to. That is, add a
ContentPlaceHolder control named MainContent to the Content control in
SimpleNested.master that references the MainContent ContentPlaceHolder in
Simple.master. Do the same thing in the Content control that references the head
ContentPlaceHolder.
Note: While I recommend naming the ContentPlaceHolder controls in the nested
master page the same as the ContentPlaceHolders in the top-level master page, this
naming symmetry is not required. You can give the ContentPlaceHolder controls in
your nested master page any name you like. However, I find it easier to remember
what ContentPlaceHolders correspond with what regions of the page if my top-level
master page and nested master pages use the same names.
After making these additions your SimpleNested.master master page's declarative markup
should look similar to the following:
<%@ 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>
Delete the Default.aspx content page we just created and then re-add it, binding it to the
SimpleNested.master master page. This time Visual Studio adds two Content controls to
the Default.aspx, referencing the ContentPlaceHolders now defined in
SimpleNested.master (see Figure 6). Add the text, "Hello, from Default.aspx!" in the
Content control that referenced MainContent.
Figure 06: The Content Page Now Includes Content Controls for the Nested Master
Page's ContentPlaceHolders
Create a content page named Alternate.aspx in the NestedMasterPages folder and bind it
to the SimpleNestedAlternate.master nested master page. Add the text, "Hello, from
Alternate!" in the Content control that corresponds to MainContent. Figure 7 shows
Alternate.aspx when viewed through the Visual Studio Designer.
<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>
This adds two links to the top of every page that binds to Simple.master,
SimpleNested.master, or SimpleNestedAlternate.master; these changes apply to all
nested master pages and their content pages immediately. Figure 8 shows Alternate.aspx
when viewed through a browser. Note the addition of the links at the top of the page
(compared to Figure 7).
Figure 08: Changed to the Top-Level Master Page are Immediately Reflected in its
Nested Master Pages and Their Content Pages
Integrating a nested master page into our demo application introduces the following
hurdles:
The existing content pages in the ~/Admin folder have certain expectations from their
master page. For starters, they expect certain ContentPlaceHolder controls to be
present. Furthermore, the ~/Admin/AddProduct.aspx and ~/Admin/Products.aspx
pages call the master page's public RefreshRecentProductsGrid method, set its
GridMessageText property, or have an event handler for its PricesDoubled event.
Consequently, our nested master page must provide the same ContentPlaceHolders and
public members.
In the preceding tutorial we enhanced the BasePage class to dynamically set the Page
object's MasterPageFile property based on a Session variable. How to we support
dynamic master pages when using nested master pages?
These two challenges will surface as we build the nested master page and use it from our
existing content pages. We'll investigate and surmount these issues as they arise.
How do we configure our nested master page so that it uses the appropriate top-level
master page? We have two options:
Let's use the second option. Create a single nested master page file in the ~/Admin folder
named AdminNested.master. Because both Site.master and Alternate.master have the
same set of ContentPlaceHolder controls, it doesn't matter what master page you bind it to,
although I encourage you to bind it to Site.master for consistency's sake.
Because the nested master page is bound to a master page with four ContentPlaceHolder
controls, Visual Studio adds four Content controls to the new nested master page file's initial
markup. Like we did in Steps 2 and 3, add a ContentPlaceHolder control in each Content
control, giving it the same name as the top-level master page's ContentPlaceHolder control.
Also add the following markup to the Content control that corresponds to the MainContent
ContentPlaceHolder:
<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>
Next, define the instructions CSS class in the Styles.css and AlternateStyles.css CSS
files. The following CSS rules cause HTML elements styled with the instructions class to
be displayed with a light yellow background color and a black, solid border:
.instructions
{
padding: 6px; border: dashed 1px black; background-color: #ffb;
margin-bottom: 10px;
}
Because this markup has been added to the nested master page, it will only appear in those
pages that use this nested master page (namely, the pages in the Administration section).
After making these additions to your nested master page, its declarative markup should
look similar to the following:
<%@ 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>
Note that each Content control has a ContentPlaceHolder control and that the
ContentPlaceHolder controls' ID properties are assigned the same values as the
corresponding ContentPlaceHolder controls in the top-level master page. Moreover, the
Administration section-specific markup appears in the MainContent ContentPlaceHolder.
Figure 10 shows the AdminNested.master nested master page when viewed through Visual
Studio's Designer. You can see the instructions in the yellow box at the top of the
MainContent Content control.
Figure 10: The Nested Master Page Extends the Top-Level Master Page to Include
Instructions for the Administrator
Step 5: Updating the Existing Content Pages
to Use the New Nested Master Page
Anytime we add a new content page to the Administration section we need to bind it to the
AdminNested.master master page we just created. But what about the existing content
pages? Currently, all content pages in the site derive from the BasePage class, which
programmatically sets the content page's master page at runtime. This is not the behavior
we want for the content pages in the Administration section. Instead, we want these
content pages to always use the AdminNested.master page. It will be the responsibility of
the nested master page to choose the right top-level content page at runtime.
To best way to achieve this desired behavior is to create a new custom base page class
named AdminBasePage that extends the BasePage class. AdminBasePage can then override
the SetMasterPageFile and set the Page object's MasterPageFile to the hard-coded value
"~/Admin/AdminNested.master". In this way, any page that derives from AdminBasePage
will use AdminNested.master, whereas any page that derives from BasePage will have its
MasterPageFile property set dynamically to either "~/Site.master" or "~/Alternate.master"
based on the value of the MyMasterPage Session variable.
Start by adding a new class file to the App_Code folder named AdminBasePage.cs. Have
AdminBasePage extend BasePage and then override the SetMasterPageFile method. In that
method assign the MasterPageFile the value "~/Admin/AdminNested.master". After
making these changes your class file should look similar to the following:
public class AdminBasePage : BasePage
{
protected override void SetMasterPageFile()
{
this.MasterPageFile = "~/Admin/AdminNested.master";
}
}
We now need to have the existing content pages in the Administration section derive from
AdminBasePage instead of BasePage. Go to the code-behind class file for each content page
in the ~/Admin folder and make this change. For example, in ~/Admin/Default.aspx you'd
change the code-behind class declaration from:
public partial class Admin_Default : BasePage
To:
public partial class Admin_Default : AdminBasePage
Figure 11 depicts how the top-level master page (Site.master or Alternate.master), the
nested master page (AdminNested.master), and the Administration section content pages
relate to one another.
Figure 11: The Nested Master Page Defines Content Specific to the Pages in the
Administration Section
To:
public partial class Admin_AdminNested : BaseMasterPage
We're not done yet. Because the BaseMasterPage class is abstract, we need to override the
abstract members, RefreshRecentProductsGrid and GridMessageText. These members
are used by the top-level master pages to update their user interfaces. (Actually, only the
Site.master master page uses these methods, although both top-level master pages
implement these methods, since both extend BaseMasterPage.)
To achieve this, start by adding the following @MasterType directive to the top of
AdminNested.master:
<%@ MasterType TypeName="BaseMasterPage" %>
Recall that the @MasterType directive adds a strongly-typed property to the code-behind
class named Master. Then override the RefreshRecentProductsGrid and GridMessageText
members and simply delegate the call to the Master's corresponding method:
public partial class Admin_AdminNested : BaseMasterPage
{
public override void RefreshRecentProductsGrid()
{
Master.RefreshRecentProductsGrid();
}
public override string GridMessageText
{
get
{
return Master.GridMessageText;
}
set
{
Master.GridMessageText = value;
}
}
}
With this code in place, you should be able to visit and use the content pages in the
Administration section. Figure 12 shows the ~/Admin/Products.aspx page when viewed
through a browser. As you can see, the page includes the Administration Instructions box,
which is defined in the nested master page.
Figure 12: The Content Pages in the Administration Section Include Instructions at
the Top of Each Page
To use the top-level master page selected by the end user we need to set the
AdminNested.master's MasterPageFile property to the value in the MyMasterPage Session
variable. Because we set the content pages' MasterPageFile properties in BasePage, you
may think that we would set the nested master page's MasterPageFile property in
BaseMasterPage or in the AdminNested.master's code-behind class. This won't work,
however, because we need to have set the MasterPageFile property by the end of the
PreInit stage. The earliest time that we can programmatically tap into the page lifecycle
from a master page is the Init stage (which occurs after the PreInit stage).
Therefore, we need to set the nested master page's MasterPageFile property from the
content pages. The only content pages that use the AdminNested.master master page
derive from AdminBasePage. Therefore, we can put this logic there. In Step 5 we overrode
the SetMasterPageFile method, setting the Page object's MasterPageFile property to
"~/Admin/AdminNested.master". Update SetMasterPageFile to also set the master page's
MasterPageFile property to the result stored in Session:
Summary
Much like how content pages can bind to a master page, it is possible to create nested
master pages by having a child master page bind to a parent master page. The child master
page may define Content controls for each of its parent's ContentPlaceHolders; it can then
add its own ContentPlaceHolder controls (as well as other markup) to these Content
controls. Nested master pages are quite useful in large web applications where all pages
share an overarching look and feel, yet certain sections of the site require unique
customizations.
Happy Programming!
Further Reading
For more information on the topics discussed in this tutorial, refer to the following
resources:
Special Thanks To
This tutorial series was reviewed by many helpful reviewers. Interested in reviewing my
upcoming MSDN articles? If so, drop me a line at [email protected]