Web developers have long faced the challenge of maintaining a consistent look and feel across a Web site. Several different types of approaches have been developed over time to meet this challenge, including the use of server-side include files, Cascading Style Sheets (CSS), XML combined with Extensible Stylesheet Language Transformations (XSLT), and ASP.NET user controls. All these solutions can get the job done, but their flexibility varies and none of them, apart from user controls, lets you leverage object-oriented programming principles.

ASP.NET 2 (in alpha at the time this article was written) will address the challenge of maintaining a Web site's look and feel through specialized template files referred to as "master pages." This feature holds a lot of promise, but it isn't available as a production-quality release to developers creating today's cutting-edge Web sites. Nevertheless, ASP.NET 1.1 and the current version of the .NET platform provide a robust object-oriented environment you can use to create custom template solutions.

It's not hard to find ASP.NET template frameworks that leverage the concept of inheritance in .NET (see Additional Resources for links to a few of the template frameworks available currently). Each framework has its own set of pros and cons depending upon the skill level of the developer who needs to use them. As a programmer, you might use a variety of the frameworks available for ASP.NET 1.1 because they offer robust functionality. However, the nonprogrammers who use the systemthink of a "Web publisher" who only knows the basics of HTMLmight find them tough to comprehend, making it difficult to integrate a single Web page into a Web site's template framework.

To support the variety of skill levels of those who might have to update Web pages, the ASP.NET template framework discussed in this article relies on XML rules to mark up the generic structure of a given Web site. XML lets the template's creator generate the structure quickly and easily without having to worry about embedding HTML code into .NET classes and recompiling and redeploying whenever the template changes. It also prevents unnecessary nesting of Web server controls within user controls (or user controls within user controls).

Using an XML template also means you can wrap Web page content automatically with the desired header, footer, and menus without writing any additional C# or VB.NET code. You can also present these same pages in alternate formats (such as "printable" pages) by changing templates dynamically. You can parse the XML template quickly and efficiently using .NET's XmlTextReader classwithout degrading an application's performance.

Create a Template
Begin by creating a template that defines the generic structure of the site (see Listing 1). Note that this template consists mainly of standard HTML tags that follow the rules defined in the XML 1.0 specification.

The template defines where you should place page content by using an element named ContentPlaceHolder (named after the container control that will be used in ASP.NET 2.0's master pages). This element is prefixed with a local namespace prefix named template and associated with the namespace URI of www.xmlforasp.net/templates. If you're unfamiliar with XML namespace URIs, they help avoid naming collisions, and they work similarly to namespaces found in the .NET Framework.

You define ASP.NET user controls in the template by adding a <template:UserControl> element. You can also define other ASP.NET controls such as PlaceHolders and additional server controls. The ability to embed server controls into the template is useful in situations where you need to be able to reference a portion of a Web site (such as a header or footer) across multiple IIS applications. User controls tend to create problems in this scenario (without resorting to IIS virtual directory hacks), but server controls placed in the Global Assembly Cache (GAC) work quite well.

This code invokes the Xml control in the System.Web.UI.WebControls namespace and performs an XML transformation using XSLT. You can add other server controls into the template by supplying their type as well as any necessary property data.

The template framework relies upon a class named BasePage that inherits from the default ASP.NET Page class located in the System.Web.UI namespace. BasePage locates the XML template file (by looking on the QueryString, in a cookie, or in the web.config file), parses the template, and injects page content into the ContentPlaceHolder region dynamically.

ASP.NET pages that need to "inherit" the look and feel defined in an XML template file can derive from the BasePage class instead of the default Page class. This means nonprogrammers such as HTML content editors can integrate pages saved with an ASPX extension into the template framework automatically either by adding a form tag with the runat="server" attribute into the pages or by adding the Page directive to the top of each page:

<%@ Page %>

Incorporate the Base Class Page
You might be wondering how the BasePage class and associated XML template are involved, if that's all that a nonprogrammer adds into the page. The developer of the overall Web site simply adds the pageBaseType attribute into the web.config file's <pages> element to specify that ASP.NET pages in the Web application should inherit from the BasePage class rather than the default System.Web.UI.Page class:

Calling an ASP.NET page derived from BasePage on the server causes the BasePage instance to parse the template (using .NET's XmlTextReader class) and create an object hierarchy that lets you access different parts of the page. This object hierarchy relies upon additional classes (all available in the downloadable source code) named BaseHtml, BaseHead, BaseBody, and BaseForm. This approach also adds existing content within a given ASP.NET page to its proper place in the template based on the location of the ContentPlaceHolder tag. You enable programmatic access to the page content through the BaseForm class's Content property (see Listing 2).

Listing 2 shows the portion of code in the BasePage class that is responsible for locating UserControl and ContentPlaceHolder elements within the template as it is parsed. Once a UserControl element is found, its src attribute is used to dynamically load the ASP.NET user control into BasePage's control collection. Once a ContentPlaceHolder element is found in the template, a new PlaceHolder server control instance is created and all controls within the ASP.NET page being requested are added into it as its children.

The requested page's controls are added into BasePage's control collection by calling a method named AddControls() (see Listing 3). This method relies upon another method named AddControl() (see Listing 4). AddControls() iterates through the Page object's control collection and adds any controls found into the PlaceHolder control (created when the ContentPlaceHolder element is found in the template). This process "wraps" the existing page content in the structure defined by the template.

I've explained the basics of the BasePage class, but you'll glean quite a bit more about how it works by also reviewing the online code that accompanies this article. The template framework discussed here isn't the "end all" of ASP.NET template solutionsI'll leave that to ASP.NET version 2's master pages once they're officially releasedbut it does provide a flexible and easy way for programmers and nonprogrammers to give their Web site a consistent look and feel.