This release of ASP.NET builds upon the substrate for building Web applications introduced in version 1.0. All of the architectural features of the ASP.NET 1.x runtime are still present in 2.0, but elements were added to make development of Web applications more intuitive and efficient. One of the most significant additions is the partial class codebehind model, in which instead of manually declaring control variables in your codebehind file, ASP.NET generates a sibling class that is merged with your class definition to provide control variable declarations. The events in the lifetime of a Page were augmented as well, to include many pre- and post-events to give more granular access to points in time during a Page's lifecycle. The other major change architecturally is the compilation model. It is now possible to deploy Web sites as nothing but binary assemblies, as well as all source, and many gradients in between. Developers now have many more options for both development and deployment of Web applications with ASP.NET.
2. User Interface Elements
It is very common in Web site design to define a standard "look and feel" for all pages. This may include common headers, footers, menus , and so on 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 of all pages are factored 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, consider the page displayed in Figure 2-1.
Figure 2-1. Sample 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. 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 that one master page, you can very easily create sites with a common look and feel driven by a single template (the master page).
Master pages in ASP.NET 2.0 are a general solution to site templating. 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. Figure 2-2 shows the conceptual relationship between a master page and content pages that are tied to the master page.
Figure 2-2. Master page concept
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 directive of <%@ Master %> 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 Listing 2-1.
Listing 2-1. Sample master pageSiteTemplate.master
Content pages, in contrast, 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 content page in Listing 2-2 provides content for the SiteTemplate.master master page shown earlier.
Listing 2-2. Sample content pageDefault.aspx
Note that with this mechanism we are able to specify content to be placed at very specific locations in the master page template. The example in Listing 2-2 shows how the subtle problem of generating unique page titles with templates is solved easily by using the new Title attribute of the @Page directive. This Title attribute works with any page (even one that is not using a master page) as long as the <head> element is marked with runat="server", but it is particularly useful when using master pages since content pages inherit their title from master pages by default. Listing 2-2 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, we can now revisit the templated example shown in Figure 2-1. This example defines a master page containing replaceable -content placeholder controls for the header, navigation bar, and footer. The master page template lays out the page using these elements, and the content page supplies the inner content for the page. Figure 2-3 shows the rendering of this example using master and content pages.
Figure 2-3. Page rendering with a master page
Even more compelling is the fact that master pages are understood by the designer in Visual Studio 2005. 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 2-4 shows our continuing example of using master pages as it would appear when editing a content page affiliated with our master page.
Figure 2-4. Designer support for master pages in Visual Studio 2005
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. The templates defined by master pages are injected into the generated control hierarchy for the requested page. This injection happens just before the Page class' Init event; this way, 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 performed as follows :
Figure 2-5 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.
Figure 2-5. Master page/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' hierarchy, and you can perform any of the tasks you are used to performing on controls with the master page directly. 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, in the default.aspx page shown in Figure 2-5 you could add code to programmatically access the HtmlForm that was implicitly added by the master page, as shown in Listing 2-3.
Listing 2-3. Programmatic access to master page controls
Working with Master Pages
It is a fairly common requirement to change some aspects of a master page depending on when and where it is being applied. For example, you may want to selectively enable or disable a collection of links in a master page based on which page is currently being accessed. While something like this is possible using the programmatic access to controls on the master page described earlier, it is generally better to build logic into your master page to manipulate the controls and to expose that logic as methods or properties on the master page. For example, Listing 2-4 shows a master page with a panel control containing a set of hyperlink controls, along with a corresponding property, ShowNavigationLinks, that controls the visibility of these links.
Listing 2-4. Master page exposing a property
A particular page could then access the master page via the Master property of the Page class, cast the result to the master page type, and set the ShowNavigationLinks property of the master page to true or false. Listing 2-5 shows a sample content page that disables the links via the exposed property.
Listing 2-5. Content page accessing master page property
You can take this one step further and eliminate the cast by using the MasterType directive in the content page. Adding a MasterType directive causes ASP.NET to generate a typesafe version of the Page class' Master property that is strongly typed to the master page referenced in the VirtualPath attribute. It essentially takes care of doing the cast for you, with the added advantage of IntelliSense in Visual Studio 2005 showing you all of the properties defined in your master page. Listing 2-6 shows an example of using the MasterType directive to create this strongly typed accessor and the simplified code for modifying the same property we modified before.
Listing 2-6. Strongly typed access using the MasterType directive
The MasterType directive also supports a TypeName attribute that you can use instead of the VirtualPath attribute if, for example, you don't want to create a hard-coded affiliation between your content page and its master page. You might find it useful to create multiple master pages that could be applied to a page based on some criterion (like a user preference stored in profile or the request's time of day). In this case you couldn't use the VirtualPath attribute, as the cast would fail if the master page changed. Instead, you could create a base class that inherits from MasterPage, add the necessary properties and methods to that base class, and then have all of your master pages inherit from that common master page base class. Your pages would then use the TypeName attribute in their MasterType directive to gain strongly typed access to the common base class.
Listings 2-7, 2-8, and 2-9 show a sample common base class, a master page that inherits from that base class, and a content page that uses the TypeName attribute to strongly type the Master property to the shared base class, respectively.
Listing 2-7. Common master page base class
Listing 2-8. Master page inheriting from a common master page base class
Listing 2-9. Strongly typed access to a common master page base class in a content page
Using a common base class for a master page makes the most sense if you plan on having multiple master pages that could be affiliated with a page and then adding the ability to change the affiliation at runtime. To change the master page affiliation, you can use the MasterPageFile property, which is exposed as a public property on the Page class and can be modified in the code for any page. Any modifications to this property must be made in a handler for the 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 Init event firing. Keep in mind that if you do change the master page programmatically, the new master page must have the same set of ContentPlaceHolder controls with matching identifiers as the original master page; otherwise , the mapping between Content controls and their corresponding placeholders will break.
The override of the OnPreInit method in Listing 2-10 could be added to any Page class using a master page to programmatically change the master page affiliation.
Listing 2-10. Changing the master page at runtime
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 the site shown in Figure 2-6.
Figure 2-6. Sample Web 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 like this:
<img src="../images/check.gif" />
Unfortunately, this would only work for pages that were in a similar relative directory location to 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 problem 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). So the preceding image reference 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:
<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 when ASP.NET developers first encounter master pages is to somehow enforce that all pages in an application be required to be content pages referencing a specific master page. While there is no "must use" attribute, 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:
<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 Content controls. Within these Content controls, master pages can then 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, consider the master page definition (in a file called MetaTemplate.master) in Listing 2-11.
Listing 2-11. Master page for other master pages (MetaTemplate.master)
We can now define another master page, which in turn specifies this master page as its master and provides Content elements for each of the ContentPlaceHolder controls in the parent master page, as shown in Listing 2-12.
Listing 2-12. Master page using another master page
You may also find it useful to create alternate master pages based on the user agent string (browser type) sent by the client. If you want to leverage some browser-specific features in your master page, it may make sense to create multiple versions of the master page for the variations across browser types. ASP.NET 2.0 supports device filters to do this declaratively , which are prefix strings that you can apply to the MasterPageFile attribute to indicate which browser type should map to which master page. Prefix strings map to .browser files that have regular expressions defined to determine which browser is being used to access the site from the user agent string. You can include other device filter strings by adding additional .browser files to your local App_Browsers directory. Keep in mind that, as in the earlier example of dynamic master pages, each master page must have the same set of ContentPlaceHolder controls for this technique to work properly. Listing 2-13 shows a sample content page with alternate master page files specified for Internet Explorer (IE) and Mozilla browsers.
Listing 2-13. Using device filters to declaratively select a master page