Master Pages:: Specifying The Master Page Programmatically: Class Masterpagefile Property
Master Pages:: Specifying The Master Page Programmatically: Class Masterpagefile Property
Programmatically
Introduction
Since the inaugural example in Creating a Site-Wide Layout Using Master Pages, all content
pages have referenced their master page declaratively via the MasterPageFile attribute in
the @Page directive. For example, the following @Page directive links the content page to the
master page Site.master:
The Page Class in the System.Web.UI namespace includes a MasterPageFile Property that
returns the path to the content page's master page; it is this property that is set by the
@Page directive. This property can also be used to programmatically specify the content
page's master page. This approach is useful if you want to dynamically assign the master
page based on external factors, such as the user visiting the page.
In this tutorial we add a second master page to our website and dynamically decide which
master page to use at runtime.
From here we can set the MasterPageFile property. Update the code so that it assigns the
value "~/Site.master" to the MasterPageFile property.
If you set a breakpoint and start with debugging you'll see that whenever the Default.aspx
page is visited or whenever there's a postback to this page, the Page_PreInit event
handler executes and the MasterPageFile property is assigned to "~/Site.master".
Alternatively, you can override the Page class's OnPreInit method and set the
MasterPageFile property there. For this example, let's not set the master page in a
particular page, but rather from BasePage. Recall that we created a custom base page class
(BasePage) back in the Specifying the Title, Meta Tags, and Other HTML Headers in the
Master Page tutorial. Currently BasePage overrides the Page class's OnLoadComplete
method, where it sets the page's Title property based on the site map data. Let's update
BasePage to also override the OnPreInit method to programmatically specify the master
page.
protected override void OnPreInit(EventArgs e)
{
this.MasterPageFile = "~/Site.master";
base.OnPreInit(e);
}
Because all our content pages derive from BasePage, all of them now have their master
page programmatically assigned. At this point the PreInit event handler in
Default.aspx.cs is superfluous; feel free to remove it.
head
MainContent
QuickLoginUI
LeftColumnContent
Some of the content pages in our website include just one or two Content controls; others
include a Content control for each of the available ContentPlaceHolders. If our new master
page (Alternate.master) may ever be assigned to those content pages that have Content
controls for all of the ContentPlaceHolders in Site.master then it is essential that
Alternate.master also include the same ContentPlaceHolder controls as Site.master.
To get your Alternate.master master page to look similar to mine (see Figure 4), start by
defining the master page's styles in the AlternateStyles.css style sheet. Add the
following rules into AlternateStyles.css:
body
{
font-family: Comic Sans MS, Arial; font-size: medium; margin: 0px;
}
#topContent
{
text-align: center; background-color: Navy; color: White;
font-size: x-large; text-decoration: none; font-weight: bold;
padding: 10px; height: 50px;
}
#topContent a
{
text-decoration: none; color: White;
}
#navContent
{
font-size: small; text-align: center;
}
#footerContent
{
padding: 10px; font-size: 90%; text-align: center; border-top:
solid 1px black;
}
#mainContent
{
text-align: left; padding: 10px;
}
Next, add the following declarative markup to Alternate.master. As you can see,
Alternate.master contains four ContentPlaceHolder controls with the same ID values as
the ContentPlaceHolder controls in Site.master. Moreover, it includes a ScriptManager
control, which is necessary for those pages in our website that use the ASP.NET AJAX
framework.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1"
runat="server">
<title>Untitled Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
<link href="AlternateStyles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="MyManager" runat="server">
</asp:ScriptManager>
<div id="topContent">
<asp:HyperLink ID="lnkHome" runat="server"
NavigateUrl="~/Default.aspx" Text="Master Pages Tutorials" />
</div>
<div id="navContent">
<asp:ListView ID="LessonsList" runat="server"
DataSourceID="LessonsDataSource">
<LayoutTemplate>
<asp:PlaceHolder runat="server"
ID="itemPlaceholder" />
</LayoutTemplate>
<ItemTemplate>
<asp:HyperLink runat="server" ID="lnkLesson"
NavigateUrl='<%# Eval("Url") %>' Text='<%# Eval("Title") %>' />
</ItemTemplate>
<ItemSeparatorTemplate> | </ItemSeparatorTemplate>
</asp:ListView>
<asp:SiteMapDataSource ID="LessonsDataSource"
runat="server" ShowStartingNode="false" />
</div>
<div id="mainContent">
<asp:ContentPlaceHolder id="MainContent"
runat="server"> </asp:ContentPlaceHolder>
</div>
<div id="footerContent">
<p style="text-align: center;"> <asp:Label
ID="DateDisplay" runat="server"></asp:Label> </p>
<asp:ContentPlaceHolder ID="QuickLoginUI"
runat="server"> </asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="LeftColumnContent"
runat="server"> </asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
Testing the New Master Page
To test this new master page update the BasePage class's OnPreInit method so that the
MasterPageFile property is assigned the value "~/Alternate.maser" and then visit the
website. Every page should function without error except for two:
~/Admin/AddProduct.aspx and ~/Admin/Products.aspx. Adding a product to the
DetailsView in ~/Admin/AddProduct.aspx results in a NullReferenceException from the
line of code that attempts to set the master page's GridMessageText property. When
visiting ~/Admin/Products.aspx an InvalidCastException is thrown on page load with the
message: "Unable to cast object of type 'ASP.alternate_master' to type 'ASP.site_master'."
These errors occur because the Site.master code-behind class includes public events,
properties, and methods that are not defined in Alternate.master. The markup portion of
these two pages have a @MasterType directive that references the Site.master master
page.
<%@ MasterType VirtualPath="~/Site.master" %>
To break this tight coupling we can have Site.master and Alternate.master derive from a
common base class that contains definitions for the public members. Following that, we can
update the @MasterType directive to reference this common base type.
We also need to define the PricesDoubled event in BaseMasterPage and provide a means
by the derived classes to raise the event. The pattern used in the .NET Framework to
facilitate this behavior is to create a public event in the base class and add a protected,
virtual method named OnEventName. Derived classes can then call this method to raise the
event or can override it to execute code immediately before or after the event is raised.
Update your BaseMasterPage class so that it contains the following code:
using System; public abstract class BaseMasterPage :
System.Web.UI.MasterPage
{
public event EventHandler PricesDoubled;
protected virtual void OnPricesDoubled(EventArgs e)
{
if (PricesDoubled != null)
PricesDoubled(this, e);
}
public abstract void RefreshRecentProductsGrid();
public abstract string GridMessageText { get; set; }
}
Next, go to the Site.master code-behind class and have it derive from BaseMasterPage.
Because BaseMasterPage is abstract we need to override those abstract members here in
Site.master. Add the override keyword to the method and property definitions. Also
update the code that raises the PricesDoubled event in the DoublePrice Button's Click
event handler with a call to the base class's OnPricesDoubled method.
After these modifications the Site.master code-behind class should contain the following
code:
public partial class Site : BaseMasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
DateDisplay.Text = DateTime.Now.ToString("dddd, MMMM dd");
}
public override void RefreshRecentProductsGrid()
{
RecentProducts.DataBind();
}
public override string GridMessageText
{
get
{
return GridMessage.Text;
}
set
{
GridMessage.Text = value;
}
}
protected void DoublePrice_Click(object sender, EventArgs e)
{
// Double the prices
DoublePricesDataSource.Update();
// Refresh RecentProducts
RecentProducts.DataBind();
// Raise the PricesDoubled event
base.OnPricesDoubled(EventArgs.Empty);
}
}
Rather than referencing a file path, the @MasterType property now references the base type
(BaseMasterPage). Consequently, the strongly-typed Master property used in both pages'
code-behind classes is now of type BaseMasterPage (instead of type Site). With this
change in place revisit ~/Admin/Products.aspx. Previously, this resulted in a casting error
because the page is configured to use the Alternate.master master page, but the
@MasterType directive referenced the Site.master file. But now the page renders without
error. This is because the Alternate.master master page can be cast to an object of type
BaseMasterPage (since it extends it).
Before adding any content to the ChooseMasterPage.aspx page take a moment to update
the page's code-behind class so that it derives from BasePage (rather than
System.Web.UI.Page). Next, add a DropDownList control to the page, set its ID property to
MasterPageChoice, and add two ListItems with the Text values of "~/Site.master" and
"~/Alternate.master".
Add a Button Web control to the page and set its ID and Text properties to SaveLayout and
"Save Layout Choice", respectively. At this point your page's declarative markup should look
similar to the following:
<p> Your layout choice:
<asp:DropDownList ID="MasterPageChoice" runat="server">
<asp:ListItem>~/Site.master</asp:ListItem>
<asp:ListItem>~/Alternate.master</asp:ListItem>
</asp:DropDownList>
</p>
<p> <asp:Button ID="SaveLayout" runat="server" Text="Save Layout
Choice" />
</p>
When the page is first visited we need to display the user's currently selected master page
choice. Create a Page_Load event handler and add the following code:
The above code executes only on the first page visit (and not on subsequent postbacks). It
first checks to see if the Session variable MyMasterPage exists. If it does, it attempts to find
the matching ListItem in the MasterPageChoice DropDownList. If a matching ListItem is
found, its Selected property is set to true.
We also need code that saves the user's choice into the MyMasterPage Session variable.
Create an event handler for the SaveLayout Button's Click event and add the following
code:
protected void SaveLayout_Click(object sender, EventArgs e)
{
Session["MyMasterPage"] = MasterPageChoice.SelectedValue;
Response.Redirect("ChooseMasterPage.aspx");
}
Note: By the time the Click event handler executes on postback, the master page
has already been selected. Therefore, the user's drop-down list selection won't be in
effect until the next page visit. The Response.Redirect forces the browser to re-
request ChooseMasterPage.aspx.
With the ChooseMasterPage.aspx page complete, our final task is to have BasePage assign
the MasterPageFile property based on the value of the MyMasterPage Session variable. If
the Session variable is not set have BasePage default to Site.master.
Summary
When a content page is visited, its Content controls are fused with its master page's
ContentPlaceHolder controls. The content page's master page is denoted by the Page class's
MasterPageFile property, which is assigned to the @Page directive's MasterPageFile
attribute during the Initialization stage. As this tutorial showed, we can assign a value to the
MasterPageFile property as long as we do so before the end of the PreInit stage. Being
able to programmatically specify the master page opens the door for more advanced
scenarios, such as dynamically binding a content page to a master page based on external
factors.
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. Lead reviewer for this tutorial
was Suchi Banerjee. Interested in reviewing my upcoming MSDN articles? If so, drop me a
line at [email protected]