devxlogo

Creating Usable Page Templates in ASP.NET

Creating Usable Page Templates in ASP.NET

ages in Web applications often share common UI components, such as headers, sidebars, and footers. Developers often seek to encapsulate these common components into a page template, which each page can consume and possibly customize. However, in ASP.NET, this can be a nightmare. This is because there is no blunt, intuitive way to structure an application to accomplish page templating. The task becomes even more complicated when pages are large and require nested user controls to organize them into manageable units.

This article shows you how to rise to the challenge of building an effective page templating scheme. As you will see, combining a Web Control that represents the template to the Web designer with a user control that contains the actual template code, you can achieve an elegant yet simple and reusable approach that’s easy to maintain and takes full advantage of the ASP.NET framework. This approach becomes especially important when dealing with complex sites containing large data-driven pages that require page-level customization.

Page Template Methods to Avoid
Many developers have invented ad hoc page templating schemes, with mixed results. The most common approaches are discussed below, along with reasons why you should avoid them.

Classic ASP Method. This method is the most archaic, yet most common way of handling page templating in ASP.NET. Developers create include files, containing header and footer content just as they did in classic ASP. Often, you’ll find that heavy use of include files is a side effect of migration. While include files still work, you should avoid using them because the include scheme doesn’t take advantage of the ASP.NET framework.

Begin-And-End User Control Method. As developers become more familiar with .NET, they often create two user controls, one for header content and one for footer content, usually named something like template_begin.ascx and template_end.ascx. At best, this approach bewilders and confuses the people responsible for page layout, and at worst, it causes errors. Because the opening and closing tags are separated into two files, nesting server-side controls inside the template is not possible?the ASP.NET runtime doesn’t allow this. Figure 1 shows a screenshot of the errors generated by Visual Studio .NET when trying to do so. In the end this results in treating the user controls as nothing more than glorified include files, which is really no better than the Classic ASP method.

Page Template Architecture
The page template architecture presented in this article consists of three main controls: Each control can be summarized as follows:

  • The page template, implemented as a user control.
  • The PageBody server control, which provides the plumbing between the template and the page that uses it.
  • The NamingPlaceHolder control, which extends the PlaceHolder Web Control by making it a NamingContainer.

Figure 2 shows the relationship between these controls.

Building the PageBody Server Control
The most important aspect of this model is exposing a server control to the calling page which controls the loading and customizing of the underlying page template. The purpose of doing this is to control the instantiation of all the page-specific content on the calling page, both static HTML as well as dynamic controls. By doing so, we can guarantee that all the server controls can participate in the intrinsic events, such as Init, Load, and LoadViewState. This would not be possible with the other ad hoc methods mentioned above.

Listing 5 shows the code for the PageBody server control. The control serves two major functions. First, by overriding the appropriate methods, it controls the instantiation of the child controls found on the page so that they are declared inside the placeholder of the page template. Second, it exposes public properties which are part of the customization process. In the case of the example above, the table cell containing the header requires a different CSS class attribute value when users are logged in or out. The HeaderCSSClass property holds the customizable data element:

   Public Property HeaderCssClass() As String

To understand the code you need to visualize the timeline of execution. When the browser requests an .aspx page that consumes the template the ASP.NET engine must first parse the text for tag data. When it parses the tag for the PageBody control, it instantiates that class and then populates the values mapped to the properties, if any exist in the opening tag. As the parser continues to parse the inner text, it repeatedly calls the AddParsedSubObject() method, adding each inner child control to the _ControlList ArrayList. Later, the runtime calls the CreateChildControls() method. At that point, the PageBody control adds the controls in the ArrayList into the NamingPlaceHolder found inside the page template.

One can argue that it would be cleaner and simpler to eliminate the PageBody control from this architecture and declare the page template user control itself in the calling page. There are some problems with that approach. First, this creates an ambiguity between the child controls declared in the .ascx file and the child controls contained within the nested template calling tags. Actually what happens is the ASP.NET runtime places the child controls contained within its calling tags directly after the code in the .ascx file. It would be a mess to try to figure out where this partition occurs, and would eventually require hacks to make it work. The other major problem is one of flexibility?by using this server control as a bridge, you can not only customize the template, but you can take it a step further an allow the page to choose from one or more available page templates, if your application requires it.

As pointed out previously, because the CreateChildControls() method is implemented, the template and the page-level content are merged so that the page-level controls participate in the ASP.NET intrinsic events. If you load a user control programmatically from another user control or from the page, that user control does not participate in events which precede the point at which it was loaded. In other words, if you load a user control during the Page.Load event, the events which come before it are not raised within the user control, so handlers such as Page_Init do not get executed. For more information, visit the Control Execution Lifecycle page at MSDN.

Providing Unique Control IDs
The final step is to create a naming placeholder. To do that, create a class that inherits from PlaceHolder and implements the INamingContainer interface. The following code a NamingPlaceHolder control class. The control extends the ASP.NET PlaceHolder control by guaranteeing that controls contained within have their own isolated namespace, eliminating duplicate IDs.

   ' This class extends the PlaceHolder class.    ' This implementation gives all the child controls    ' within a unique naming space, eliminating the possibility    ' of a naming collision among one or more   ' IDs between the pages and the page template.      Imports System.Web.UI   Imports System.Web.UI.WebControls      Public Class NamingPlaceHolder       Inherits PlaceHolder       Implements INamingContainer   End Class

INamingContainer is a marker interface that, when implemented by a control, creates unique IDs for its child controls. For example, when a control that implements INamingContainer has an ID of ctrlMain, its child controls will have IDs prefixed with this parent ID plus a colon, such as ctrlMain:TextBox1. This naming scheme guards against potentially dangerous naming collisions; the ASP.NET runtime will throw an exception if a server control inside the page template has the same ID as a control on the calling page. With potentially large sites, this extra step avoids the tedious task of manually ensuring that the IDs of server controls on the pages do not conflict with any IDs in any of the page templates for the site.

Putting It Together
You can see how efficient and elegant this approach is. The following code shows the TypicalPage.aspx page with page templating implemented. As you can see, the code for the page has been significantly reduced. The page can now use the PageBody server control instead of copying the same HTML code from page to page. Using the page template, designers can manage template layout and page-level content layout separately.

                My Site - Home                                 

The beauty of this architecture is that it you can extend it to allow more than one page template. Furthermore, you can encapsulate the PageBody and NamingPlaceHolder classes in its own implementation for reuse in other Web applications. Each application would then inherit from PageBody, adding the properties required by the Web designers to customize the template.

Implementing Multiple Page Templates
For some Web applications, one page template doesn’t provide enough flexibility. For instance, the look and feel of your site for customers who are not logged into your site may be drastically different from the UI for users who are, requiring multiple templates.

You can extend the single-template model you’ve already seen to a multi-template model by taking two extra steps. First, you must add a property to the PageBody class that stores a template path identifying the appropriate template to load. You can assign this TemplatePath property either inline, where you declare the PageBody control on the page, or in the code for that page. Here’s the property definition.

   Public Property TemplatePath() As String      Get         Return _TemplatePath      End Get      Set(ByVal Value As String)         If _TemplateLoaded Then            Throw New System.Web.HttpException( _               "The page template cannot be set " & _              "more than once.")         End If         _TemplatePath = Value         _TemplateLoaded = True         End If       End Set   End Property

Listing 6 shows how to modify the PageBody class. One drawback is that after you set the TemplatePath property, you cannot change it, which ensures that the page template is loaded only once. This is because after determining the appropriate template, the application must load it into memory immediately so that you can instantiate child controls inside the NamingPlaceHolder. Once that’s done the child controls cannot be removed from the NamingPlaceHolder in one template and re-instantiated in another because that would cause the intrinsic events for the child controls to fire more than once.

The next step is to declare a generic, abstract PageTemplate class that each specific page template inherits from (see Listing 7). The class defines a property that references the PageBody control class for the Web application. The base class is necessary because you don’t know which template to load until runtime. Now, the PageBody class references the page template from this base class type, as opposed to the actual page template user control class as in the one-to-one page template model shown earlier.

The calling page specifies the correct template to use. If the value of the TemplatePage property never changes for a given page in your Web application, then it may be simpler to specify it declaratively inline with the PageBody declaration:

         

Otherwise, you should specify the correct template in code:

   pgbTemplate.TemplatePath = "templates/Template_1.ascx"

Remember, it has to be one or the other. You cannot specify the template declaratively and programmatically.

Encapsulating Templates for Reuse
You can develop a reusable, extendable version from the page template architecture described in this article by creating an assembly containing three classes: one that contains an abstract definition for PageBody, one that contains an abstract definition for PageTemplate, and the NamingPlaceHolder class. Figure 3 shows the diagram for this assembly.

Migrating to ASP.NET 2.0
In October 2003, Microsoft officially outlined the specifications for ASP.NET 2.0, code-named “Whidbey”. One key feature is Master Pages, Microsoft’s better-late-than-never implementation of page templates. A master page is a new type of control, a sibling of the current Page and UserControl classes. In it, you define your page template. Then, you create content pages which reference one of the master pages contained within your application.

Master Pages are seamlessly integrated with the ASP.NET framework, and integrate well into the designer. When you’re designing a content page, the UI from the master page displays semi-transparently in the designer so you can see the completed design without having to run the application.

Work you perform to implement template pages today isn’t wasted?you can migrate the model described in this article to the new Master Pages architecture when ASP.NET 2.0 becomes available. Table 1 shows the functional equivalent for each aspect of this architecture. When you migrate to ASP.NET 2.0, you’ll need to convert your page template user controls into master pages. For multiple page templates, you can create a base implementation from the MasterPage class. Then, you can define all the customizable properties. One drawback is that you can’t set these properties declaratively, because you access the master page through the Content control. Instead, you will have to set these properties in the code-behind file using the Page.Master property.

Templating ModelASP.NET 2.0
PageBody controlContent control
NamingPlaceHolder control ContentPlaceHolder control
PageTemplate User Control Master Page
Customizable properties defined in PageBody class Customizable properties defined in MasterPage code-behind class
Author’s Note: When this article was written, it was too early to determine exactly how Microsoft will implement Master Pages in ASP.NET 2.0. Therefore, the information presented here is subject to change and should be treated lightly.

Developing an efficient page templating scheme is far from straightforward. But with some knowledge of how server controls work you can construct a scalable scheme for any Web application. Furthermore, this model can be extended to accommodate multiple-page templates within a single site. You can incorporate this page templating scheme into your ASP.NET applications without writing much code.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist

©2024 Copyright DevX - All Rights Reserved. Registration or use of this site constitutes acceptance of our Terms of Service and Privacy Policy.