Practical ASP.NET

Build Web Sites Using Master Pages

ASP.NET 2.0 master pages enable you to set the common content for a group of Web pages quickly and easily, while also providing for each page's unique content.

Technology Toolbox: ASP.NET

Editor's Note: This article is excerpted from Dino Esposito's upcoming book, Introducing Microsoft ASP.NET 2.0 [Microsoft Press, ISBN: 0735620245]. It has been edited for length and format to fit the magazine.

Most Web sites use a similar graphical layout for all their pages. This grows out of accepted guidelines for design and usability. Consistent layout characterizes all cutting-edge Web sites, no matter how complex. For some, the layout consists of the header, body, and footer; for others, it's a more sophisticated aggregation of menus, buttons, and panels that contain and render the actual content. Of course, you want to reuse code across the Web site's pages.

ASP.NET 1.x provides a workaround: You can apply a common layout to all the pages of a Web site by wrapping common user interface (UI) widgets in user controls and reusing them in all the pages. This powerful model produces modular code, but it soon becomes unmanageable when you have hundreds of pages to work with.

A better way to build and reuse pages must fulfill three requirements. The pages have to be easy to modify. Changes shouldn't require deep recompilation and diffuse retouching of the source code. And any change must have minimal impact on the overall performance of the application. ASP.NET 2.0 satisfies these requirements with a new technology—master pages (a kind of supertemplate)—and exploits the new ASP.NET Framework's ability to merge a supertemplate with user-defined content replacements.

A master page is a distinct file that defines the template for a set of pages—much like a PowerPoint master slide. It contains the page's static layout and is referenced at the page level. Regions that you can customize on each derived page are referenced in the master page with a special placeholder control. A derived page is simply a collection of blocks that the runtime uses to fill the holes in the master.

Master pages are transparent to end users, who see and invoke only the content pages' URLs. If they request a content page, the ASP.NET runtime applies a different compilation algorithm and builds the dynamic class as the merge of the master and the content page (see Figure 1). No master file is ever downloaded.

You can start using this new technology by coding the shared user interface and functionality in the master page, which also contains named placeholders for content provided by derived pages. Master page files have a .master extension. Page syntax resembles that of a regular .aspx page, but has a top @Master directive replacing the @Page directive, and one or more ContentPlaceHolder server controls. Each embedded ContentPlaceHolder control identifies a region of markup text whose real content comes from content pages at run time. The body of a master page can contain any combination of server controls, literal text, images, HTML elements, and managed code. All this, plus the content bound to placeholders, originates the virtual .aspx source for generating the dynamic page class for the user.

Content pages and master pages work in conjunction—neither are of any use by themselves. You get an error message if you request a .master resource, because ASP.NET considers .master a forbidden type of resource. You also get an error message if you request an .aspx resource that's built as a content page but isn't bound to an existing master page. (You'll learn more about content pages in a moment.)

Master Your Own Web Pages
I'll show you how to write a sample master/content pair of pages so you can see how the whole mechanism works and what syntactical elements are involved. I'll start with placeholders. A master page without content placeholders is technically correct and gets processed correctly by the ASP.NET runtime. However, a placeholder-less master fails in its primary goal—to be the supertemplate of multiple pages that look alike. A master page without placeholders works like an ordinary Web page, but with the extra burden required to process master pages.

Here's a simple master page named booksample.master:

<%@ Master %> 
<html>
<head>
   <link rel="Stylesheet" href="styles.css" /> 
</head>
<form runat="server">
<table border="0" width="100%" bgcolor="beige" 
      style="BORDER-BOTTOM:silver 5px solid">
   <tr>
      <td><h2>Introducing ASP.NET 2.0</h2></td>
   </tr>
</table>
<br>
<asp:contentplaceholder runat="server" 
   id="PageBody" />
</form>
</body>
</html>

This master page resembles a standard ASP.NET page, apart from the identifying @Master directive and ContentPlaceHolder control. A page bound to this master inherits all the master's contents automatically and has a chance to 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 distinguishes master pages from content pages and lets the ASP.NET runtime handle each properly. The @Master directive supports many attributes, including Language, Debug, Inherits, and ClassName. These play the same role as they do in ordinary .aspx pages. The Language attribute specifies the language used in the master, while Inherits specifies the base class for the current master. ClassName specifies the name of the actual master class, and Debug ensures that debug symbols are added to the compiled page. Debug also causes the ASP.NET runtime to persist any temporary files (including the source code of the dynamic page class) created during page request processing.

The attributes supported by an @Master directive are the same as those defined in the @Control directive for user controls (see Table 1). This isn't coincidental. A master page file is compiled to a class that derives from the MasterPage class. The MasterPage class, in turn, inherits UserControl. So a master page is treated as a special kind of ASP.NET user control.

You can also create master pages programmatically. You build your own class and make it inherit MasterPage. Then create .master files in which the Inherits attribute points to the fully qualified name of your class. RAD designers, such as the one embedded in Visual Studio 2005, use this approach to create master pages.

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 have one of the content pages use C#.

You can use other ASP.NET directives in a master page, such as @Import. However, the scope of these directives is limited to the master file and doesn't extend to child pages generated from the master. If you import the System.Data namespace into a master page, you can call the DataSet class within the master, but to call the DataSet class from within a content page, you must also import the namespace into the content page.

Provide for Custom Content
The ContentPlaceHolder control inherits from the Template class and is defined in the System.Web.UI.WebControls namespace. It acts as a container placed in a master page, marking places in the master where related pages can insert custom content. A content placeholder is uniquely identified by an ID:

<asp:contentplaceholder runat="server" 
   ID="PageBody" />

A content page is an .aspx page containing 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 placeholder ID links placeholders and content. 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:

<asp:Content runat="server" 
   contentplaceholderID="PageBody">
   :
</asp:Content>

You define as many content placeholders as there are customizable regions in the master 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 fill placeholders defined in the master.

You can't bind a placeholder to more than one content region in a single content page. Every <asp:Content> server tag in a content page must point to a distinct placeholder in the master.

A content placeholder can be assigned default content that shows 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 doesn't reference a given placeholder in the master, the default content is used. Here's how to define default content:

<asp:contentplaceholder runat="server" ID="PageBody">
   <!-- Use the following markup if no custom
      content is provided by content page -->
   :
</asp:contentplaceholder>

Default content is ignored if the content page populates the placeholder, and is never merged with the content page's custom markup. You can use ContentPlaceHolder controls only in master pages or (templated) user controls—they aren't valid on .aspx pages, and they cause parser errors on ordinary Web pages.

The master page defines the skeleton of the resulting page. Placing shared layouts and navigational menus in a master page simplifies management of an application's pages. You create the master, and your pages become a kind of delta from the master. The master defines the common parts of a certain group of pages and leaves placeholders for customizable regions. Each content page, in turn, defines the content of each region for a particular .aspx page.

The key part of a content page is the Content control. The class is defined in the System.Web.UI.WebControls namespace and inherits Control. A Content control is a container for other controls placed in a content page. You only use the control in conjunction with a corresponding ContentPlaceHolder, not as a standalone control.

Your master file defines PageBody, a single placeholder representing the body of the page. It's placed below an HTML table providing the page's header. Here's a sample content page bound to the booksample.master file:

<%@ Page Language="C#" masterpagefile="booksample.master" %> 
<script runat="server">
   void OnButtonClick(object sender, 
      EventArgs e)
   {
      msg.Text = "Hello, Master Pages";
   }
</script>
<asp:content runat="server" 
   contentplaceholderID="PageBody">
<div>
   <h1>This is the body of the page</h1> 
   <asp:button runat="server" text="Click Me" 
      onclick="OnButtonClick" />
   <asp:label runat="server" id="msg" />
</div>
</asp:content>

Users See Only What They Need to See
The content page is the resource users invoke through the browser. Call it withmaster.aspx (see Figure 2). The replaceable part of the master is filled with the corresponding content section defined in the derived pages. In the previous example, the <asp:Content> section for the PageBody placeholder contains a button and a label. The server-side code associated with the button is defined in the content page. The Language attribute points to a different language in the @Master directive than it does in the @Page directive, and the page is still created and displayed correctly.

The content page in the previous example is bound to the master using the @Page directive's new MasterPageFile attribute. The attribute points to a string representing the path to the master page. A page bound to a master can't host server controls outside of an <asp:Content> tag.

Master pages work like other ASP.NET pages and controls in detecting the underlying browser's capabilities and adapting the pages' output to the specific device in use. And ASP.NET 2.0 simplifies choosing a device-specific master. You can create browser-specific versions of the same master to control how certain pages of your site appear on a particular browser. Simply define multiple bindings in the content page using the same MasterPageFile attribute, only prefixed with the identifier of the device. Here's the syntax for providing ad hoc support for Microsoft Internet Explorer and Netscape browsers, along with a generic master for other browsers users might have:

<%@ Page ie:masterpagefile="ieBase.master" 
   netscape:masterpagefile="nsBase.master"
   masterpagefile="Base.master" %>

The ieBase.master file supports Internet Explorer; the nsBase.master supports Netscape; and the device-independent master (Base.master) services anything else. The ASP.NET runtime senses the user's browser or device automatically and selects the corresponding master page when the page runs.

The prefixes used to specify browser type are defined in machine.config file's <browserCaps> section. You can distinguish between up-level and down-level browsers as well as between browsers and other devices, such as cellular phones and personal digital assistants (PDAs). You must also indicate a device-independent master if you use device-specific masters.

The use of master pages slightly changes how pages are processed and compiled. A page based on a master has a double dependency on both the .aspx source file (the content page) and the .master file (the master page). If either page changes, the dynamic page assembly is re-created. The URL users need is that of the content page, but the page served to the browser results from the master page fleshed out with any replacement provided by the content page.

Suppose a user requests an .aspx resource mapped to a content page (a page referencing a master). The ASP.NET runtime begins its job by tracking the dependency between the source .aspx file and its master. This information is persisted in a local file created in the ASP.NET temporary files folder. Next, the runtime parses the master page source code and creates a VB.NET or C# class, depending on the language set in the master page. In the previous example, the booksample.master master page is parsed to a VB.NET class (VB.NET is assumed if the Language attribute isn't specified). The class inherits MasterPage, then is compiled to an assembly.

A Master Page is a User Control
The MasterPage class is a small wrapper built around the UserControl class:

Public Class MasterPage : Inherits UserControl
:
End Class

The dynamic class the ASP.NET runtime builds from the master page source code extends MasterPage by adding any public members defined in line. For example, it adds new properties, methods, and events. The framework also adds a few protected and private members. In particular, a protected member is added for each ContentPlaceHolder server tag found in the .master source. The name of the property matches the ID of the server tag in the source file. Based on the aforementioned simple.master file, the protected property looks like this:

Protected PageBody As 
   System.Web.UI.WebControls.ContentPlaceHolder

In addition, a template member is added to represent the content dynamically bound to the placeholder. The property is of type ITemplate and is set with actual content provided by the content page for that placeholder.

The overall structure of the source code extracted out of a master page still resembles that of a classic ASP.NET page. For example, the runtime generates code to instantiate and configure the corresponding class for each control marked with a runat attribute. The same occurs with the ContentPlaceHolder class: It's instantiated, named, and bound to the matching property on the master page class—the PageBody property set previously. The final step in this procedure instantiates the template in the placeholder control:

' __ctrl is the placeholder control
' Template_PageBody is the internal template
' member representing the dynamically set 
' content of the placeholder
Template_PageBody.InstantiateIn(__ctrl)

The templated property is defined but not assigned any value in the master page class. The template is populated while the content page is processed.

If multiple .master files are found in the same directory, they're all processed at once. A dynamic assembly is generated for any master files found, even if only one of them is used by the ASP.NET page whose request triggered the compilation process. So don't leave unused master files in your Web space—they'll get compiled. Also, the compilation tax is paid only the first time a content page is accessed within the application. The master is precompiled, making subsequent responses faster when a user accesses another page that requires the second master.

Any ASP.NET page bound to a master page must have a certain structure: No server controls or literal text are allowed outside the <asp:Content> tag. As a result, the layout of the page looks like a plain collection of content elements, each bound to a particular placeholder in the master. The connection is established through the ID property.

The <asp:Content> element works like a control container, much like the Panel control of ASP.NET or the HTML <div> tag. All the markup text is compiled to a template and associated with the corresponding placeholder property on the master class.

The master page is a special kind of user control. In fact, the ASP.NET framework calls the InitializeAsUserControl method (an internal method on the UserControl class), which completes the initialization phase of user controls. The method wires automatic event handlers to the control, such as Page_Load and Page_Unload.

Construction of the final page continues with the addition of the filled master page to the control tree of the current instance of the page. No other controls are present in the final page except those brought in by the master (see Figure 3).

You Can Nest Layers of Masters
You can make the topology of the master/page relationship as complex and sophisticated as needed. You can, in fact, associate a master page with another master and form a hierarchical, nested structure. When you use nested masters, any child master is implemented as a plain content page in which extra ContentPlaceHolder controls are defined for an extra level of content pages. In this case, a child master is a content page that contains a combination of <asp:Content> and <asp:ContentPlaceHolder> elements. Like any other content page, a child master points to a master page and provides content blocks for its parent's placeholders. At the same time, it makes available new placeholders for its child pages.

There's no architectural limitation to the number of nesting levels you can implement in your Web sites. Performance-wise, the depth of the nesting has a negligible impact on the overall functionality and scalability of the solution. The final page served to the user is always compiled on demand and never modified as long as dependent files are not touched.

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. A content page will access any public properties on the bound master through the Master property on the Page class. The Master property is defined to be of type MasterPage and doesn't contain any property or method definition specific to the master you're creating. To be able to call custom members, you must cast Master to the specific class name of the actual master. The master's class name is defined by the ClassName attribute:

<%@ Master ClassName="MyMaster" %>

In the page, you cast Master to the specified class name and then call any public member:

CType(Master, MyMaster).SubTitle = "Welcome"

The preceding code shows how to set the SubTitle property of the master from within a content page.

ASP.NET 2.0 master pages offer one way of building Web pages—not the only way, or even necessarily the best way. But they'll pay off if you're employing user controls extensively to duplicate portions of your UI, or if your application lends itself to being (re)designed in terms of master and content pages.

About the Author

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube