Master pages and visual inheritance in ASP.NET

This article was originally published in VSJ, which is now part of Developer Fusion.
Almost all web sites today contain pages with a similar graphical layout. This doesn’t happen by chance – it grows out of accepted guidelines for design and usability. A consistent layout is characteristic of all cutting-edge web sites, no matter how complex. For some sites, the layout consists of the header, body, and footer; for others, it is a more sophisticated aggregation of navigational menus, buttons, and panels that contain and render the actual content. Needless to say, manual duplication of code and HTML elements is simply out of the question in building these similar-looking pages. Making code automatically re-usable clearly represents a far better approach, but how do you implement it in practice? What’s the recommended approach for building similar-looking pages quickly and efficiently?

One possibility is wrapping these UI elements in user controls and referencing them in each page. Common in ASP.NET 1.x applications, this model is extremely powerful and produces highly modular code, but when you have hundreds of pages to work with, it soon becomes unmanageable. Both classic ASP and ASP.NET 1.x provide some workarounds for this type of issue, but neither tackles such a scenario openly or provides a definitive, optimal solution. ASP.NET 2.0 faces up to the task through a new technology – master pages – and basically benefits from the ASP.NET Framework’s ability to merge a “supertemplate” with user-defined content replacements.

Authoring rich pages in ASP.NET 1.x

User controls are a common trick to re-use markup in ASP.NET 1.x. User controls are aggregates of ASP.NET server controls, HTML elements, literal text, and code. The ASP.NET runtime exposes user controls to the outside world as programmable components. The idea is that you employ user controls to tailor your own user interface components and share them among the pages of the web site. For example, all the pages that need a navigational menu can reference and configure the user control that provides that feature. User controls are like embeddable pages. Turning an existing ASP.NET page into a user control requires only a few minor changes. User controls can be easily linked to any page that needs their services. Furthermore, changes to a user control’s internal implementation and layout do not affect the referencing page, and only require you (or the runtime) to recompile the user control into an assembly.

What’s a user control?

User controls and pages have so much in common that transforming a page, or a part of it, into a user control is no big deal. You copy the portion of the page of interest to a new .ascx file, and make sure the user control does not contain any of the following tags: <html>, <body>, or <form>. You complete the work by associating a code-behind file (or a <script runat=”server”> block) to code the expected behaviour of the user control. Finally, you add a @Control directive in lieu of the @Page directive. Here’s an example of a simple but functional user control:
<%@ Control CodeBehind=
	”Message.ascx.cs”
	Inherits=”Message” %>
<span style=”color:<%= ForeColor%>”>
	<%= Text%>
</span>
Here’s the related code-behind class:
public class Message :
	System.Web.UI.UserControl
{
	public string ForeColor;
	public string Text;
}
To insert a user control into an ASP.NET page, you drag it from the project onto the web form, when in design mode. Visual Studio .NET registers the user control with the page and prepares the environment for you to start adding code.
<%@ Page CodeBehind=”Test.aspx.cs”
	Inherits=”TestUserCtl” %>
<%@ Register Src=”Message.ascx”
	TagName=”Message” TagPrefix=”uc1” %>
<html><body>
	<form id=”form1” runat=”server”>
		<uc1:Message ID=”Message1”
			runat=”server” />
	</form>
</body></html>
In the page code-behind class, you work with the Message1 variable as you would do with any other server control.
protected void Page_Load(
	object sender, EventArgs e)
{
	Message1.ForeColor = “blue”;
	Message1.Text = “Hello world”;
}

What’s bad about user controls?

Imagine you have a web site with lots of pages sharing some UI widgets. If you change the internal implementation of the user control, no referencing page will be affected. However, if you alter any aspect of the control’s public interface (such as the class name, properties, methods, or events), all the pages that reference the control risk being updated. (Whether they really are to be updated depends on the specific interaction set between the page and user control.) You might need to retouch all the pages in the application that use the control. Then you must recompile these pages and deploy the assemblies. In addition, the next time a user views each page, the ASP.NET runtime will take a while to respond because the dynamic assembly for the page must be re-created.

Architecturally speaking, the solution based on user controls works just fine. In practice, though, it is not a very manageable model for large-scale applications – its effectiveness decreases as the complexity of the application (read, the number of pages involved) increases. If your site contains hundreds of pages, handling common elements through user controls can quickly become inefficient and unmanageable. In addition, user controls aren’t flawless at design-time; Visual Studio .NET, in fact, doesn’t render them in a WYSIWYG manner – something which, for a purely visual feature like this, is not a minor issue.

Visual inheritance

Let’s tackle the alternative approach, equally flawed from a WYSIWYG perspective, but closer (though, not at all identical) to the official solution of ASP.NET 2.0 – master pages.

ASP.NET pages are built as instances of special classes – code-behind classes. Because pages are ultimately classes, what happens if you stuff part of the common UI in some base class and inherit new pages from there? This approach resembles the visual inheritance feature that Windows Forms developers have been familiar with for a long time.

Pure visual inheritance à la Windows Forms is impractical in ASP.NET. This is because ASP.NET pages are made of code and markup. The markup determines the position of the controls, while code adds logic and functionality. Building pre-defined graphic templates in the base class doesn’t pose issues, but how would you import those standard UI blocks into derived pages and, more importantly, how would you merge them with controls local to the derived page?

In Windows Forms, controls have an absolute position that the designer reproduces, making it easy for developers to see where existing controls are and insert new controls anywhere. Web Forms, though, typically use relative positioning, which leads to either of the next two design choices. Option one is to supply predefined and named UI blocks in base classes and have derived classes load them in matching placeholders. Option two involves using master pages as defined in ASP.NET 2.0.

To implement the former technique, derive your page from a base class that knows how to create special UI blocks such as toolbars, headers, and footers:

<%@ Page Language=”C#”
Inherits=”Samples.CompanyBasePage” %>
Each of these UI blocks has a unique but arbitrary name. Add some <asp:placeholder> controls to the derived page whose ID matches any of the predefined names:
<asp:placeholder runat=”server”
	id=”PageHeader” />
<asp:placeholder runat=”server”
	id=”TitleBar” />
In its OnLoad method, the base class explores the page’s tree and expands placeholders with predefined UI blocks. In this way, a page that wants to import some predefined graphic blocks (for example, a menu or toolbar) has only to put a placeholder in the right location. This approach exploits inheritance, but provides no WYSIWYG facilities, and forces you to create UI blocks in code-only mode with no markup.

Master pages in ASP.NET 2.0

In ASP.NET 2.0, a master page is a distinct file referenced at the application (as well as the page) level, which contains the static layout of the page. Regions that each derived page can customise are referenced in the master page with a special placeholder control. A derived (or content) page is simply a collection of blocks the runtime will use to fill the holes in the master. True visual inheritance as found in Windows Forms is not a goal of ASP.NET 2.0 master pages. The contents of a master page are merged into the content page, and they dynamically produce a new page class that is served to the user upon request. The merge process takes place at compile time and only once. In no way do the contents of the master serve as a base class for the content page in an OOP fashion.

A master page is similar to an ordinary ASP.NET page except for the top @Master directive and the presence of one or more ContentPlaceHolder server controls. A ContentPlaceHolder control defines a region in the master page that can be customised in a derived page. A master page without content placeholders is technically correct and will be processed correctly by the ASP.NET runtime. However, such a placeholder-less master would fail in its primary goal – to be the super-template of multiple pages that look alike. A master page devoid of placeholders works like an ordinary web page but with an extra burden required to process master pages. The listing below shows a simple master page.

<%@ Master CodeFile=”Simple.master.cs” Inherits=”Simple” %>
<html>
	<head runat=”server”>
		<title>Hello, master pages</title>
	</head>
	<body>
		<form id=”form1” runat=”server”>
			<asp:Panel ID=”HeaderPanel” runat=”server”
				BackImageUrl=”Images/SkyBkgnd.png” Width=”100%”>
				<asp:Label ID=”TitleBox” runat=”server” Text=”Love ASP.NET 2.0” />
			</asp:Panel>

			<asp:contentplaceholder id=”PageBody” runat=”server”>
			</asp:contentplaceholder>

			<asp:Panel ID=”FooterPanel” runat=”server”
					BackImageUrl=”Images/SeaBkgnd.png”>
				<asp:Label ID=”SubTitleBox” runat=”server” Text=”Dino Esposito” />
			</asp:Panel>
		</form>
	</body>
</html>
As you can see, the master page looks like a standard ASP.NET page. Aside from the identifying @Master directive, the only key differences are ContentPlaceHolder controls. A page bound to this master automatically inherits all the contents of the master (the header and footer, in this case) and can attach custom markup and server controls to each defined placeholder. The content placeholder element is fully identified by its ID property and normally doesn’t require other attributes.

The @Master directive

The @Master directive distinguishes master pages from content pages and allows the ASP.NET runtime to properly handle each. A master page file is compiled to a class that derives from the MasterPage class. The MasterPage class, in turn, inherits UserControl. So, at the end of the day, a master page is treated as a special kind of ASP.NET user control.

The @Master supports quite a few attributes. The table below details the attributes that have a special meaning in a master page.

Attribute Description
ClassName Specifies the name for the class that will be created to render the master page. This value can be any valid class name but should not include a namespace. By default, the class name for simple.master is ASP.simple_master.
CodeFile Indicates the URL to the file that contains any source code associated with the master page.
Inherits Specifies a code-behind class for the master page to inherit. This can be any class derived from MasterPage.
MasterPageFile Specifies the name of the master page file that this master refers to.

A master can refer to another master through the same mechanisms a page uses to attach to a master. If this attribute is set, you will have nested masters. The master page is associated with a code file that looks like the following:

public partial class Simple :
	System.Web.UI.MasterPage
{
	protected void Page_Load(
		object sender, EventArgs e)
	{
		:
	}
	:
}
The @Master directive doesn’t override attributes set at the @Page directive level. For example, you can have the master set the language to Visual Basic .NET and one of the content pages can use C#. The language set at the master page level never influences the choice of the language at the content page level. You can use other ASP.NET directives in a master page – for example, @Import. However, the scope of these directives is limited to the master file and does not extend to child pages generated from the master.

The ContentPlaceHolder container control

The ContentPlaceHolder control acts as a container placed in a master page. It marks places in the master where related pages can insert custom content. A content placeholder is uniquely identified by an ID. Here’s an example:
<asp:contentplaceholder runat=”server”
	ID=”PageBody” />
A content page is an ASP.NET page that contains only <asp:Content> server tags. This element corresponds to an instance of the Content class that provides the actual content for a particular placeholder in the master. The link between placeholders and content is established through the ID of the placeholder. The content of a particular instance of the Content server control is written to the placeholder whose ID matches the value of the ContentPlaceHolderID property, as shown here:
<asp:Content
	runat=”server”
contentplaceholderID=
		”PageBody”>
	:
</asp:Content>
In a master page, you define as many content placeholders as there are customisable regions in the page. A content page doesn’t have to fill all the placeholders defined in the bound master. However, a content page can’t do more than just fill placeholders defined in the master. A placeholder can’t be bound to more than one content region in a single content page. If you have multiple <asp:Content> server tags in a content page, each must point to a distinct placeholder in the master.

A content placeholder can be assigned default content that will show up if the content page fails to provide a replacement. Each ContentPlaceHolder control in the master page can contain default content. If a content page does not reference a given placeholder in the master, the default content will be used. The following code snippet shows how to define default content:

<asp:contentplaceholder runat=”server”
	ID=”PageBody”>
	<!- Use the following markup if no
		custom content is provided by
		the content page ->
	:
</asp:contentplaceholder>
The default content is completely ignored if the content page populates the placeholder. A ContentPlaceHolder control can be used only in a master page. Content placeholders are not valid on regular ASP.NET pages. If such a control is found in an ordinary Web page, a parser error occurs.

Writing a content page

The master page defines the skeleton of the resulting page. If you need to share layout or any UI block among all the pages, placing it in a master page will greatly simplify management of the pages in the application. You create the master and then think of your pages in terms of a change from the master. The master defines the common parts of a certain group of pages and leaves placeholders for customisable regions. Each content page, in turn, defines what the content of each region has to be for a particular ASP.NET page.

The key part of a content page is the Content control – a mere container for other controls. The Content control is used only in conjunction with a corresponding ContentPlaceHolder and is not a standalone control.

The master file that we considered earlier defines a single placeholder named PageBody. This placeholder represents the body of the page and is placed right below an HTML table that provides the page’s header. Figure 1 shows a sample content page based on this master page. Notice the layout of the master page greyed out in the background.

Figure 1
Figure 1: A sample content page in Visual Studio .NET 2005

Let’s take a look at the source code of the content page:

<%@ Page Language=”C#”
	MasterPageFile=”Simple.master”
	CodeFile=”HelloMaster.aspx.cs”
	Inherits=”HelloMaster” %>

<asp:Content ID=”Content1”
	ContentPlaceHolderID=”PageBody”
	Runat=”Server”>
	<h1>Welcome to this page!</h1>
	<h3>The rest of the page is kindly
	offered by our sponsor
	page - the master!</h3>
</asp:Content>
The replaceable part of the master is filled with the corresponding content section defined in the derived pages. A content page – that is, a page bound to a master – is a special breed of page, in that it can only contain <asp:Content> controls. A content page is not permitted to host server controls outside of an <asp:Content> tag.

In the previous example, the content page is bound to the master by using the MasterPageFile attribute in the @Page directive. The attribute points to a string representing the path to the master page. Page-level binding is just one possibility – although it is the most common one. You can also set the binding between the master and the content at the application or folder level.

Application-level binding means that you link all the pages of an application to the same master. You configure this behaviour by setting the Master attribute in the <pages> element of the principal web.config file.

<configuration>
	<system.web>
		<pages master=”MyApp.master” />
	</system.web>
</configuration>
If the same setting is expressed in a child web.config file – a web.config file stored in a site subdirectory – all ASP.NET pages in the folder are bound to a specified master page. Note that if you define binding at the application or folder level, all the web pages in the application (or the folder) must have Content controls mapped to one or more placeholders in the master page. In other words, application-level binding prevents you from having (or later adding) a page to the site that is not configured as a content page. Any classic ASP.NET page in the application (or folder) that contains server controls will throw an exception.

Device-specific master pages

Like all ASP.NET pages and controls, master pages can detect the capabilities of the underlying browser and adapt their output to the specific device in use. ASP.NET 2.0 makes choosing a device-specific master easier than ever. If you want to control how certain pages of your site appear on a particular browser, you can build them from a common master and design the master to address the specific features of the browser. In other words, you can create multiple versions of the same master, each targeting a different type of browser.

How do you associate a particular version of the master and a particular browser? In the content page, you define multiple bindings using the same MasterPageFile attribute, but you prefix it with the identifier of the device. For example, suppose you want to provide ad hoc support for Internet Explorer and Netscape browsers and use a generic master for any other browsers that users employ to visit the site. Just use the following syntax:

<%@ Page masterpagefile=”Base.master”
	ie:masterpagefile=”ieBase.master”
	netscape6to9:masterpagefile=
		”nsBase.master”
%>
The ieBase.master file will be used for Internet Explorer; the nsBase.master, on the other hand, will be used if the browser belongs to the Netscape family, version 6.x to 9.0. In any other case, a device-independent master (Base.master) will be used. When the page runs, the ASP.NET runtime automatically determines which browser or device the user is using and selects the corresponding master page.

The prefixes you can use to indicate a particular type of browser are those defined in the ASP.NET configuration files for browsers. The table below lists the most commonly used IDs.

IDs of common browsers
Browser ID Browser Name
IE Any version of Internet Explorer
Netscape3 Netscape Navigator 3.x
Netscape4 Netscape Communicator 4.x
Netscape6to9 Any version of Netscape higher than 6.0
Mozilla FireFox
Opera Opera
Up Openwave-powered devices

Master page object model

You can use code in content pages to reference properties, methods, and controls in the master page, with some restrictions. The rule for properties and methods is that you can reference them if they are declared as public members of the master page. This includes public page-scope variables, public properties, and public methods.
public partial class MyMaster :
	System.Web.UI.MasterPage
{
	protected void Page_Load(
		object sender, EventArgs e)
	{
	}

	// TitleBox is a (protected)
	// textbox embedded
	// in the master page’s layout
	public string TitleBoxText
	{
		get { return TitleBox.Text; }
		set { TitleBox.Text = value; }
	}
}
The only way to access the master page object model that is possible for the code behind a content page is using the page’s Master property. The bad news is that the Master property is defined to be of type MasterPage – the base class; as such, it doesn’t know anything regarding any property or method definition specific to the master you’re really working with. In other words, the code below wouldn’t compile because no TitleBoxText property is defined on the MasterPage class.

The Master property of the Page class represents the master page object as compiled by the ASP.NET runtime engine. This class follows the same naming convention as regular pages – ASP.XXX_master, where XXX is the name of the master file. Developers can override the default class name by setting the ClassName attribute on the @Master directive. The attribute lets you assign a user-defined name to the master page class.

<%@ Master
	Inherits=”SimpleWithProp” ...
	Classname=”MyMaster” %>
In light of this, to be able to call custom properties or methods, you must first cast the object returned by the Master property to the actual type:
((ASP.MyMaster)Master).TitleBoxText
	= “Hello”;
To avoid casting, you can also turn to the @MasterType directive, as follows:
<%@ MasterType VirtualPath=
	”MyMaster.master” %>
More simply, in this case the Master property is declared to be the right type in the dynamically created page class, and this allows you to write strong-typed code.

Summary

Being able to create similar-looking pages quickly and effectively is a must for every web developer, and (more importantly) for any technology designed for web development like ASP.NET. In version 1.x, user controls or special base classes fit the bill, but provided no WYSIWYG features. In ASP.NET 2.0, master pages come to the rescue.

Master pages create content pages based on a pre-defined template made of graphics and, optionally, code. Master pages do not provide pure object-oriented visual inheritance à la Windows Forms; instead, they benefit from aggregation and let derived pages personalise well-known regions of the master. With full support from the Visual Studio .NET 2005 environment, master pages are a time-saving feature that brings concrete added value to ASP.NET 2.0 solutions.


Dino Esposito is a Solid Quality Learning mentor and prolific author. He is the author of the two volumes Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005), writes the Cutting Edge column for MSDN Magazine and regularly contributes to a variety of developer magazines. He is also a regular speaker at Bearpark’s annual DevWeek conference in London – the 9th annual event takes place on 20–24 February 2006.

You might also like...

Comments

About the author

Dino Esposito United Kingdom

Dino Esposito is an instructor for Solid Quality Mentors, and a trainer and consultant based in Rome. He is the author of various books, including Windows Shell Programming, Instant DHTML Script...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Engineers are all basically high-functioning autistics who have no idea how normal people do stuff.” - Cory Doctorow