Using ASP.NET Master Pages for Consistent Site Design
By Jacob J. Sanford
Creating one page (Default.aspx) that is aesthetically pleasing, easy to use by your visitors, takes various browsers into consideration, uses CSS, is one thing. Copying this code to every page in your site would be, to say the least, a maintenance nightmare. Can you imagine having to update thousands of pages because the client wants a fairly minor design tweak? What you really want is a way to create a reusable template that can be incorporated into every page of your site. This will allow for all pages to look the same throughout your project while allowing for easier maintenance as your projects mature. In a year or so when you want to redo the entire site, you would only need to change the template rather than the coding on every page of your application. With Master Pages, you can do exactly that.
A Master Page is simply a single page that holds the structure of your Web site. The files are designated with a .master file extension and are imported into content pages through the MasterPageFile property of the @Page directive of the content pages. They are meant to provide the template that all of your pages will use throughout the site. They are not really meant to hold the content of an individual page or, even, the stylistic definitions of the page. They are meant to provide a blueprint of what your site should look like and then connect that template to style rules set in detached CSS files (as appropriate).
Enough Talk; Time to Code
This article will go through the ins and outs of Master Pages. You should start a new project to practice these new concepts. So, with a new Web project open in Visual Studio 2005, click on Website and then choose "Add New Item" to get the screen depicted in Figure 1.
You will want to select "Master Page", ironically enough, as the template you want to use for your new item. You can name the page whatever you want but should leave the .master file extension. You should choose the language you code in, although this isn't particularly relevant unless you want to do any "under the hood" coding in the Master Page itself. Again, while this is possible and pertinent in some situations, for the most part, this is not necessarily relevant for most projects. It should also be noted that the language of the Master Page file will not dictate the language of the content page that inherits it. This just means that you can have a Master Page file that has a language declaration of, say, VB and a content page that inherits it that has a language of C# (or the other way around). To expand this thought, you might have a designer who is responsible for creating the look and feel of your projects and is more comfortable with VB.NET. She could easily create the Master Page in VB without any worry about the coding preferences of the rest of the team. Once the VB Master Page is completed, the developers in the group could code against this VB Master Page in their language of choice (VB or C#). Even if there are public properties or methods in the Master Page that are written in VB, C# content pages can access them. This language independence of Master Pages is one of its nicest features.
However, for this example, just choose whatever language you are more comfortable with and press the "Add" button. This will create a file in your project called "MasterPage.master" that should resemble the following:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %> <!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 runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:contentplaceholder id="ContentPlaceHolder1" runat="server"> </asp:contentplaceholder> </div> </form> </body> </html>
There are a couple of noteworthy elements to the default code provided. The first is the @Page declaration or, in this case, the lack thereof. The declaration statement for a Master Page is the @Master declaration. Other than that, though, the declaration looks pretty much identical to the @Page declaration you are probably used to seeing in other pages you may have created previously. There are Language, AutoEventWireup, CodeFile, and Inherits properties and they are set to intuitive values. In fact, if you compared the @Master declaration to a standard @Page declaration, you wouldn't see any difference except the name of the declaration and the names of the files that are used in the values of the properties just mentioned. They are, in this regard, identical. If you delve into all of the properties available to each, you will see a lot of differences. However, for now, it is sufficient to know that they have some shared properties and that, by default, Visual Studio creates new files with either a @Master or @Page declaration with the same properties in the initial declaration.
The other important thing to notice is the following code in the middle of the file:
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server"> </asp:contentplaceholder>
This creates the region in the template that will be filled with material from the content pages created by the programmers. Each content page will reference the ID of this placeholder in its code and stick all of the content from that page in this particular region.
It is worth noting that you are not limited to a single placeholder in your Master Page. You can, in reality, set up as many placeholders as you need for your page. However, in practice, you should probably limit the number used to the number you actually need. If you have, for example, one hundred placeholders in your Master Page, you are probably not centralizing the content enough and, more importantly, are creating a maintenance headache. One placeholder is the default and around two or three are fairly common to see in practice. If you are using much more than that, you should really evaluate how you are using the Master Page and whether there is a simpler way to handle the content.
However, to see how these placeholders might be used in a project, examine the following code:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %> <!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 runat="server"> <title>Untitled Page</title> </head> <body bgcolor="navy"> <form id="form1" runat="server"> <div> <table border="0" width="700"> <tr> <td colspan="2" height="150" bgcolor="gray" valign="middle" align="center"> HEADER </td> </tr> <tr> <td width="150" bgcolor="silver">SIDEBAR</td> <td width="550" height="400" bgcolor="white"> <asp:contentplaceholder id="ContentPlaceHolder1" runat="server"> </asp:contentplaceholder> </td> </tr> <tr> <td colspan="2" align="center" height="20" bgcolor="gray"> FOOTER </td> </tr> </table> </div> </form> </body> </html>
Essentially, you have created a fairly typical layout with a header, sidebar, content, and footer region. For simplicity, this example just uses tables to create the layout. If you were to look in the Design tab in Visual Studio 2005, you should see something that resembles Figure 2.
You can see that you have created a header and footer region on the top and bottom of the page respectively and then created a division for a sidebar and another one for your content. Within your content region, you have the content placeholder control. If you sent this Master Page out to all of your programmers, they could simply reference it and then fill in the content placeholder with their own content for every page they develop.
If you wanted to create separate content placeholders for the sidebar and content areas, you could simply modify the sidebar section of your code to look similar to the following:
<tr> <td width="150" bgcolor="silver"> <!-- SIDEBAR REGION --> <asp:contentplaceholder id="ContentPlaceHolder2" runat="server"> </asp:contentplaceholder> </td> <td width="550" height="400" bgcolor="white"> <!-- CONTENT REGION --> <asp:contentplaceholder id="ContentPlaceHolder1" runat="server"> </asp:contentplaceholder> </td> </tr>
This will create a content placeholder called ContentPlaceHolder1 in the content area that will be in the main content area of your page and then a separate content placeholder called ContentPlaceHolder2 that will reside in the sidebar region. The programmer will then have access to both of these regions to put in the appropriate content based on the page he is developing.
You can also, at the Master Page level, set the default content that will go in each of these placeholders. If, for example, you wanted the sidebar to say "this is the sidebar" in the absence of any code on the content page setting the content for this area, you would do this simply by inserting the content between the opening and closing tags of the content placeholder control, as such:
<asp:contentplaceholder id="ContentPlaceHolder2" runat="server"> this is the sidebar </asp:contentplaceholder>
When coding the content page that references this master page, the developer has the option of setting the content for this placeholder and, if he does, that is what will show up in the final rendered page. If, however, he decides not to set it on the content page for whatever reason, the default content from the Master Page will show up instead.
Most developers might think of content placeholders as just a place to store content displayed to the end user in the final rendered page. However, this isn't the only way they can be used. For example, if you place a content placeholder within the <head> region of your page, you can give access to that region at the Child Page level. This means that the Child Page can actually insert links to CSS documents that would be set in the <head> region of the rendered page. So, maybe the Master Page includes a blank content placeholder in the <head> region just below the linked stylesheets. By default, nothing would be added to that section. However, if you needed to, you could fill that content placeholder with a custom CSS document only relevant for that page. This allows for much more flexibility in the design of content pages as your project matures.
For now, however, your Master Page should resemble Figure 3 in the Design View.
Even though you can see the look of the page in the Design tab of Visual Studio, you should not get the wrong impression that you, then, just launch this page in a browser and it will work. That just isn't the case. Master Pages are not meant to be rendered on their own; they are only appropriate to be included as a reference to a valid .NET page. So if, for example, you try to view this Master Page in a browser, you would see an error similar to the one shown in Figure 4.
Now that you have a Master Page that you are comfortable with, it is time to implement the template in a content page. There are several ways to do this. The first way is to simply add a new item to your project (Website -> Add New Item), which will give you the screen shown in Figure 5.
As shown in Figure 5, you will want to select "Web Form" for your template, as you have probably done in previous projects. However, you want to make sure that you check the option "Select Master page" near the bottom of the screen. Doing so will do two things: it will prompt you to select which Master Page you want to use and will also set up your ASPX page to use that particular Master. The former will be done through a screen similar to Figure 6.
This screen shows the Master Pages you have available to you. Essentially, it is a directory browser that only allows you to see Master Pages. In this example, there is only one option: MasterPage.master. Select the Master Page you want to use and press the OK button. This will generate an ASPX file that is set up to use this Master with code that resembles the following:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %> <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder2" Runat="Server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server"> </asp:Content>
The first thing you will notice when looking at this file in comparison to an ASPX page that does not use a Master Page is in the @Page declaration. There is a new property here called "MasterPageFile" that references your new Master Page, "MasterPage.master". You will see that the Master Page property value starts with a tilde ("~"). This simply means that the Web application will look for the file in the root of the Web application or virtual folder. This means that, if you have a Web page with this reference sitting in a subdirectory, the Web application will look for the Master Page in the root of the Web application when it is attempting to render out the final page. Removing the tilde will force the application to look for the Master Page in whatever folder the ASPX resides. In many projects this is not that big of a deal, especially if you have all of your .NET files sitting in the root folder. However, it is important to know what the tilde is there for and how it affects your rendering.
The next thing you will notice is, possibly, the sheer simplicity of the page. There is only a @Page declaration and then content placeholders that correlate to the placeholders set up in the Master Page. There is not, for example, a form tag (or any HTML tags for that matter). However, if you browse this page exactly as it is now, you will see something similar to Figure 7.
As you can see, all of the HTML tags came through just fine. Along with the form tag and other pertinent formatting and properties, the HTML layout came down from the Master Page into the Content Page.
But wait, what about the default content in the sidebar? You know, the default content you set in the Master Page for ContentPlaceHolder2. It's not there! Does that mean there is an error in this manuscript? Or is there something that isn't necessarily intuitive going on? Hopefully, you assumed the latter.
What has happened is that you actually overrode the default value in the way Default.aspx is set up. Don't think so? Look at the code again for this content placeholder in Default.aspx.
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder2" Runat="Server"> </asp:Content>
Look what is between the opening and closing tag. Nothing? Well, nothing is what will replace the default content. That isn't to say that the default content will not be replaced; it means that the default content will be replaced with nothingness. To see the default content, just remove the content placeholder code from Default.aspx, as follows:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %> <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server"> </asp:Content>
You will see that the code is exactly the same except that the content placeholder for ContentPlaceHolder2 has been removed. Browsing the new page will yield something similar to Figure 8.
Now that the content placeholder for ContentPlaceHolder2 has been removed from Default.aspx, the default content comes through. This is an important distinction to remember when setting default content for the content placeholders in your Master Pages. If you create an empty placeholder control on your content page that correlates to a placeholder on the Master Page that has default content set up, the emptiness will override the default content. This allows you to create pages with static content on most pages (as in the left pane of the page used in this example) but gives you the option to set the content to something else if necessary. This makes for an easy way to override some of the facets of a Master Page in the content page that inherits it.
Now, to put content in a particular content placeholder, you need to simply insert the content between the opening and closing tags of that particular content placeholder. For example, modify the page from before to the following:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %> <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server"> this is the content area! </asp:Content>
You now have set up some content for ContentPlaceHolder1, which is reserved for the main content of the page. Browsing this page again will produce a page similar to Figure 9.
You now have a page that is filling the content area of the page from the content page, filling the content area of the sidebar with default content, and integrating all of the structural design for the page from the HTML coding in the Master Page.
As a final thought on this subject, you can toggle back and forth between having content in a ContentPlaceHolder on a Content Page in the Design View by clicking on the arrow in the upper right-hand corner of the ContentPlaceHolder, as you can see in Figure 10.
If you have a ContentPlaceHolder in the Master Page that you don't have set in the Content Page, you will see the option shown in Figure 10 ("Create Custom Content"). This will add a new ContentPlaceHolder region in the Content Page that you can now edit. If, however, you have content in that placeholder, your option will change to "Default to Master's Content". Choosing this option will remove the placeholder from the content page and any content that you put there ... without warning. So you need to be careful. If you have set a lot of content in that placeholder and accidentally choose this option, it's just gone.
It is also interesting to note the description of the placeholders in the Design View. Assuming that you still have the sidebar area set to default to the Master Page, its header should read something like "Content - Content1 (Master)". This simply means the placeholder has an ID of "Content1" and it is defaulting to the Master. Conversely, in the content area placeholder, its title should be something like "Content - Content2 (Custom)". Similarly, this means that the ID is "Content2" and it has custom content (rather than defaulting to the Master).
Excerpted from Chapter 7, "Master Pages," of Professional ASP.NET 2.0 Design: CSS, Themes, and Master Pages (Wrox, 2007, ISBN: 978-0-470-12448-2), by Jacob J. Sanford. Jacob is currently a Project Lead with the Rapid Application Development Team at the Florida Department of Children & Families. He began developing Web applications more than 10 years ago using primarily classic ASP with various relational databases. Over the years he has dabbled in other Web application development languages, such as ColdFusion and PHP. However, he has been working almost exclusively with the .NET Framework since its 1.0 release. He is a regular contributor to 2MinuteTips.com and makes frequent presentations at local and regional .NET events. Having worked in all aspects of the SDLC, he has been focusing his recent efforts primarily on accessible Web design (especially as it pertains to .NET applications). He also spends an inordinate amount of time "playing" with the latest (alpha and beta) Web technologies, especially Microsoft Silverlight. Jacob's other recent Wrox.com contributions are 2 code camp videos Mastering ASP.NET Themes and Drawing with .NET: System.Drawing.