Master Pages
Master Your Site Design with Visual Inheritance and Page Templates
Fritz Onion
This article is based on the March 2004 Community Technology Preview of ASP.NET 2.0. All information contained herein is subject to change.
This article discusses:
|
This article uses the following technologies: ASP.NET |
Code download available at:ASPNET20MasterPages.exe(135 KB)
Contents
Template Design
Building Templates in Classic ASP
Building Templated Sites in ASP.NET 1.x
Master Pages
Implementation Details
Details of Usage
Conclusion
Over the years Web developers have struggled with a variety of techniques to "templatize" their sites, but none have proven to be a truly generic, reusable way of maintaining a standardized appearance across an entire site. Master Pages, one of the most anticipated features coming in ASP.NET 2.0, finally provide a sanctioned and elegant way of designing pages in an application based on templates.
Template Design
It is desirable in Web site design to define a standard appearance for all pages. This may include common headers, footers, and menus that provide a core set of features and appearance throughout the site. For dynamic sites, built with technologies like ASP or ASP.NET, it is extremely useful if these common features on all pages are factored out into some type of page template, allowing each page to consist only of its own unique content and providing a central location for making site-wide changes in appearance and behavior. As a simple, concrete example of a site that would benefit from some type of page template technique, see Figure 1.
Figure 1** Simple Templated Site **
This particular page has a header at the top, a footer at the bottom, a navigation bar on the left, and an area for page-specific content filling out the remainder of the space. Ideally, the header, footer, and navigation bar should be defined only once and somehow propagated to all pages in the site.
This is precisely the problem that Master Pages in ASP.NET 2.0 solve simply and cleanly. By defining one Master Page and then creating many Content Pages based on it, you can very easily create sites with a common appearance driven by a single template (the Master Page). Before I explore the details of Master Pages, however, it is useful to understand how developers are building templated sites today in both ASP and ASP.NET 1.x.
Building Templates in Classic ASP
Many sites that were built using ASP 3.0 use the concept of templates, typically through the use of server-side include (SSI) directives. SSI directives provide the ability to insert contents of files into specific locations within an ASP page. One common technique is to use the SSI directive to bring in frequently used elements like footers, headers, and navigation bars. Figure 2 demonstrates this practice.
Figure 2** Server-side Includes in ASP **
While this technique is a step in the right direction, it still means that each page has to generate the surrounding layout elements and overall structure in addition to any page-specific content. Another more template-like approach taken by many sites is to specify two common include files, such as pageStart.asp and pageEnd.asp, which contain not only the common navigation bar, header, and footer, but also the surrounding layout elements and overall page structure. All that is left for an individual page to add is its specific content.
This approach provides the control many developers want in a centralized template for the entire site; however, it also has several drawbacks. First, there is no designer support for editing files that have SSI directives, so designers have to work on a merged rendering to see the full page appearance, only to have it later separated out again. Second, the include mechanism is rather simplistic in that it only inserts the contents of one file into another file prior to server-side evaluation by the ASP parser. This means that it is easy to make mistakes in matching HTML element closing tags and choosing the include locations correctly to prevent incorrect rendering. It is also difficult to customize portions of the included files. In the asp "technique2" sample available for download on the MSDN® Magazine Web site, one of the immediate drawbacks you will notice is that the title of each page is intentionally left blank. This happens because the title is specified only once in the top-level page_start include file and there is no clean way to have pages that include the template to specify what should appear in the title element. Finally, there is overhead associated with the use of include files because they increase the overall size of the ASP Script Engine Cache.
Based on these simple approaches using server-side include directives, a truly generic templating mechanism should ideally have the following five features:
- Some facility to let the developer create portions of a page that are defined independently and reused across multiple pages
- The ability to define the "shell" of a page which other pages then use as their template
- The ability for pages to alter various elements within an inherited template page, such as changing the title element
- The ability to specify alternate page templates declaratively within a page
- The ability to actually view the rendered version of the page using its associated template
The use of server-side include files with ASP 3.0 satisfies the first of these criteria, and arguably the second up to a point, but fall flat on the last three of the features.
Building Templated Sites in ASP.NET 1.x
ASP.NET brought a completely new object-based programming model with the potential for a newer, more elegant templating mechanism. Instead of having to rely on the rather rudimentary SSI mechanism used in ASP 3.0, ASP.NET gave you a server-side representation of each page with a complete hierarchical object model of elements that are to be rendered back to the client. The feature that most closely mirrors the functionality of SSI files is the user control. The user control provides a declarative way of creating custom controls whose content and behavior can be plugged into other pages at arbitrary locations, so using them as a generic way of defining reusable portions of a page is quite straightforward, as illustrated in Figure 3.
Figure 3** Reusable Page Portions with User Controls **
In addition to acting as simple placeholders for content, user controls are also full-fledged controls that can expose properties, methods, and events. Unlike server-side include directives, you can instrument a user control with functionality that can influence its rendering on a page. For example, you could create a property on your header control called ShowBreadcrumbs that would be able to selectively turn a breadcrumb feature on or off at your discretion.
There are, however, some limitations and drawbacks to the user control model of reuse. To begin with, many developers find it cumbersome to have to add a Register directive for each user control to every page that will use them. Ironically, many ASP.NET developers have resorted to using SSI directives to import a collection of Register directives into all their pages, replacing several lines with just one SSI directive. More importantly, however, there is no built-in way to use user controls to satisfy any of the five criteria I listed earlier for developing a templating mechanism (except the first, since you can create reusable portions of pages).
ASP.NET does provide a much richer object model, and many people have built their own templating mechanism to overcome the lack of a native one in ASP.NET 1.x. With a little ingenuity and some clever coding, you can build a generic templating mechanism. Most implementations rely on the fact that you have the opportunity to manipulate the control hierarchy that is automatically constructed for you by the Page class prior to it being sent back to the client. For example, one technique is to create a common Page-derived base class which extracts the controls in the generated page hierarchy, dynamically loads a user control which serves as the page template, and then injects the extracted controls into a well-known location within the user control hierarchy. A complete implementation of this technique is included with the code download for this article.
Using a technique like this, I can come very close to achieving all five of my desired criteria for a truly useful templating mechanism. I have user controls for defining reusable portions of pages. I have a page template mechanism with which I can define a single template and apply it to any number of pages in my system. There are ways of defining "replaceable" elements within the template using this technique, and it is possible to specify alternate templates for each page. The one missing feature is designer integration, which is not truly possible without some help from Visual Studio® .NET in recognizing this templating technique.
The other disadvantage with custom template techniques like this is that there is no one sanctioned way to do it, and there is nothing built into the .NET Framework that supports it. What this means is that each site may be using a completely different mechanism for templating.
While ASP.NET gives you a much more powerful programming model that is extremely flexible, it still does not have a built-in mechanism for templating a site. In particular, while some developers have built templating mechanisms through clever use of control hierarchy replacement and user controls, they require extra configuration steps and, more importantly, have no designer support. Enter Master Pages in ASP.NET 2.0.
Master Pages
The arrival of Master Pages in ASP.NET 2.0 represents the first templating mechanism from Microsoft that meets all of the criteria I outlined earlier. They provide site-level page templates, a mechanism for fine-grained content replacement, programmatic and declarative control over which template a page should use, and perhaps most compelling of all, integrated designer support. Technically, Master Pages are implemented in much the same way that I described custom template mechanisms in ASP.NET 1.1, but add to that designer support from Visual Studio 2005 and the fact that it is now a sanctioned and supported way to construct templated sites with visual inheritance, and you finally have a complete template solution.
The implementation of Master Pages in ASP.NET 2.0 consists of two conceptual elements: Master Pages and Content Pages. Master Pages act as the templates for Content Pages, and Content Pages provide content to populate pieces of Master Pages that require "filling out." A Master Page is essentially a standard ASP.NET page except that it uses the extension of .master and a <%@ master %> directive instead of <%@ page %>. This Master Page file serves as the template for other pages, so typically it will contain the top-level HTML elements, the main form, headers, footers, and such. Within the Master Page you add instances of the ContentPlaceHolder control at locations where you want Content Pages to supply page-specific content, as shown in Figure 4.
Figure 4 Adding Placeholder Instances
<!-- file: sitetemplate.master --> <%@ master language="C#" %> <html> <head> <title> <asp:contentplaceholder runat="server" id="_titleContent"> Standard title </asp:contentplaceholder> </title> </head> <body> <form runat="server"> <h2>Common header</h2> <asp:contentplaceholder runat="server" id="_mainContent" /> <h2>Common footer</h2> </form> </body> </html>
In contrast, Content Pages are just ordinary .aspx files that specify an associated Master Page in their page directive using the masterpagefile attribute. These pages must contain only instances of the Content control as their sole purpose is to supply content for the inherited Master Page template. Each Content control must map to a specific ContentPlaceHolder control defined in the referenced Master Page, the contents of which will be inserted into the Master Page's placeholder at rendering time. The following Content Page provides content for the sitetemplate.master Master Page shown in Figure 4:
<!-- file: default.aspx --> <%@ page language="C#" masterpagefile="sitetemplate.master" %> <asp:content contentplaceholderid="_titleContent" runat="server"> Main page </asp:content> <asp:content contentplaceholderid="_mainContent" runat="server"> This is the content for the default page. </asp:content>
Note that with this mechanism you can specify content to be placed at very specific locations in the Master Page template. The example just shown describes how the subtle problem of generating unique page titles with templates is solved easily by providing a ContentPlaceHolder control within the title element of the Master Page and a corresponding Content control with a matching ContentPlaceHolderId in the Content Page. This example also illustrates how Master Pages can supply default content for placeholders, so if the Content Page decides not to provide a Content control for a particular placeholder, it will have a default rendering.
With the fundamental mechanics of Master Pages in place, I can now revisit the templated example that I built earlier using other techniques. Recall that this example employs reusable page content elements (user controls in ASP.NET) to define the header, navigation bar, and footer. The template page lays out the page using these elements, and the Content Page supplies the inner content for the page. Figure 5 shows the rendering of this example using Master and Content Pages.
Figure 5 Page Rendering with Master Pages in ASP.NET 2.0
Figure 6** Design Support for Master Pages **
Even more compelling is the fact that Master Pages are understood by the designer in Visual Studio 2005, so that when you are visually editing a Content Page it displays the content of the inherited Master Page in a grayed out region so it is obvious what the ultimate rendering of the page will look like. Figure 6 shows my continuing example using Master Pages as it would appear when editing a Content Page that is affiliated with my Master Page.
Implementation Details
As mentioned earlier, the implementation of Master and Content Pages is quite similar to the approach taken by many developers building their own custom templating mechanism in ASP.NET 1.x. In particular, the MasterPage class derives from UserControl and thus inherits the same generic container functionality that user controls provide. Much like the custom implementation I discussed earlier, the templates defined by Master Pages are injected into the generated control hierarchy for the requested page. This injection happens just prior to the Init event of the Page class so that all of the controls will be in place prior to Init, when it is common to perform programmatic manipulation of controls.
The actual merging of the Master Page's control hierarchy and the page's control hierarchy is done in a manner similar to the approach I outlined earlier. The top-level control of the Master Page (which will be named the same as the file containing the Master Page) will be inserted as the root control in the new page hierarchy. Then the contents of each Content control in the page are injected as a collection of child controls underneath the corresponding ContentPlaceHolder control. Figure 7 shows a sample Content Page with an associated Master Page and the resulting merged control hierarchy that is created just prior to the Init event during the page processing. Note the color-coded affiliation indicating the origin of each control in the resulting hierarchy.
Figure 7 Master Page and Content Page Merged Hierarchy
One of the implications of this implementation is that the Master Page itself is just another control in your page class's hierarchy, and you can perform any of the tasks with the Master Page directly that you are used to performing on controls. The current Master Page associated with any given page is always available via the Master property accessor. As an example of interacting with the Master Page, within the default.aspx page shown in Figure 7, you could add code to programmatically access the HtmlForm that was implicitly added by the Master Page, as shown in the following code snippet:
void Page_Load(object sender, EventArgs e) { HtmlForm f = (HtmlForm)Master.FindControl("_theForm"); if (f != null) { // use f here... } }
In addition to accessing the Master Page, you can also change the Master Page affiliation of a Content Page at run time. The MasterPageFile property is exposed as a public property on the Page class and can be modified within the code for any page. Any modifications to this property must be made in a handler for the new PreInit event of the Page class for it to take effect, since the creation of and merging with the Master Page happens just prior to the firing of the Init event. The following override of the OnPreInit method could be added to any page class using a Master Page to programmatically change the Master Page affiliation:
protected override void OnPreInit(EventArgs e) { this.MasterPageFile = "othertemplate.master"; base.OnPreInit(e); }
Details of Usage
As you begin to use Master Pages in your site design, you will run into some issues that may not have occurred before if you have never used a site-level templating mechanism. The first issue is that of relative paths in referenced resources like images or stylesheets. When you are creating a Master Page you must keep in mind that the directory from which relative paths are going to be evaluated may very well change based on the page being accessed. Consider the directory structure of a site shown in Figure 8.
Figure 8** Site Directory Structure **
If you were to add a reference to the Check.gif image in the images directory from the Site.master in the masterpages directory, you might be tempted to add a simple image element, as shown here:
<img src="../images/check.gif" />
Unfortunately, this would only work for pages that were in a relative directory location similar to where the image as the Master Page was, like page1.aspx. Any other page, like default.aspx, would not correctly resolve the relative path. One solution to this is to use the root path reference syntax in ASP.NET and ensure that all relative references are made from server-side controls, which is the only place this syntax works. The image reference I mentioned earlier would become:
<img src="~/images/check.gif" runat="server" />
Another option is to rely on the fact that relative path references in server-side controls are evaluated relative to the Master Page in which they are placed. This means that it would also be sufficient to change the image reference to the following:
<img src="../images/check.gif" runat="server" />
Server-side path references in pages that reference Master Pages are still relative to the page itself, so you should not have to change any techniques you may already have in place to deal with relative references in pages.
Another common request that ASP.NET developers make when they first encounter Master Pages is that they would like to have the ability to require that all pages within an application be Content Pages that reference a specific Master Page. While no such "must-use" attribute exists, you can designate a Master Page to be used by default for all pages in an application by adding a pages element to your web.config file specifying a common Master Page, as shown in the following code snippet:
<!-- file: web.config --> <configuration> <pages masterPageFile="~/sitetemplate.master" /> </configuration>
Like any settings specified at the application level, individual pages can elect to override the default masterPageFile attribute, but adding this to your configuration file will guarantee that no pages will be added to your application accidentally without an associated Master Page.
Finally, you may find that it is useful to have a "meta" Master Page—that is, a Master Page for a set of Master Pages. Master Pages support arbitrarily deep nesting, so you can create whatever level of Master Pages you decide makes sense for your application. Just like pages that have Master Pages, Master Pages that have Master Pages must consist exclusively of ContentPlaceHolder controls at the top level. Within these ContentPlaceHolder controls, Master Pages can add additional ContentPlaceHolder controls for the actual pages to use. Note that pages that reference a Master Page which itself has a Master Page can only provide content elements for ContentPlaceHolder controls on the immediate parent Master Page. There is no way to directly populate placeholders on a Master Page two or more levels up from a particular page. As an example, take a look at the Master Page definition (metatemplate.master) that is shown in the code in Figure 9.
Figure 9 Master Page Definition
<%@ master %> <html> <head> <title> <asp:contentplaceholder runat="server" id="_titleContent"> Standard title </asp:contentplaceholder> </title> </head> <body> <form id="_theForm" runat="server"> <h2>Header</h2> <asp:contentplaceholder runat="server" id="_mainContent" /> <h2>Footer</h2> </form> </body> </html>
You can now define another Master Page which in turn specifies that Master Page as its master and provides content elements for each of the ContentPlaceHolder controls in the parent Master Page (sitetemplate.master), as shown in Figure 10.
Figure 10 Defining a Parent Master Page
<%@ master masterpagefile="~/metatemplate.master" %> <asp:content runat="server" contentplaceholderid="_titleContent"> <asp:contentplaceholder runat="server" id="_title"> Default title </asp:contentplaceholder> </asp:content> <asp:content runat="server" contentplaceholderid="_mainContent"> <table> <tr> <td><asp:contentplaceholder id="_leftContent" runat="server" /></td> <td><asp:contentplaceholder id="_rightContent" runat="server" /></td> </tr> </table> </asp:content>
Note that in order to grant pages access to the _titleContent placeholder in the parent Master Page, I had to declare a Content control and nest a new ContentPlaceHolder control within it to give pages access to that location in the parent Master Page.
Conclusion
The addition of Master Pages in ASP.NET 2.0—the long-awaited feature of site-level templates—represents a new era of easy Web site creation for Web developers. No longer do developers have to resort to clumsy server-side include directives in ASP or complicated control hierarchy-manipulating techniques in ASP.NET. Now creating a page template that all your other Web pages can be based upon is just as easy as designing any other ordinary type of Web page. I'll bet you can't wait to get started.
Fritz Onion is an independent consultant, author, and instructor specializing in ASP.NET. He is the author of Essential ASP.NET (Addison Wesley, 2003) and is working on a second edition covering ASP.NET 2.0. He writes and teaches courses for DevelopMentor and is also a regular speaker at industry conferences.