![]() | |
| Figure 1. The "Begin" Portion of the Begin-and-End Method: The Visual Studio .NET IDE has problems with server control tags that have no closing tag, or are not self-closing. |
Page Template Architecture
The page template architecture presented in this article consists of three main controls: Each control can be summarized as follows:
![]() | |
| Figure 2. Page Template Architecture: This diagram demonstrates how the controls interact in the page load and rendering process. |
<mysc:NamingPlaceHolder id="innerHolder" runat="server" />
This control, which is of type NamingPlaceHolder, serves as the point where the page-level content is merged with the page template. The function of the NamingPlaceHolder control is discussed in more detail later in the article. Public Property PageBody() As PageBody
The PageBody control is an important aspect to this page templating scheme and is also discussed later. One of its major purposes is to expose properties that allow page template customization. In this example you see that PageBody contains a property named HeaderCssClass. It this model, it is the responsibility of the page template to read these properties and apply them to the page template. In the Page_Load event handler this task is accomplished: Private Sub Page_Load(...) Handles MyBase.Load
cellHeader.Attributes.Item("class") =
PageBody.HeaderCssClass
End Sub
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.
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.
<html>
<head>
<title>My Site - Home</title>
</head>
<body>
<mycontrols:PageBody HeaderCssClass="header_logged_in"
runat="server">
<!-- page-specific content -->
</mycontrols:PageBody>
</body>
</html>
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. <mysc:PageBody id="pgbTemplate" runat="server"
TemplatePath="templates/Template_1.ascx">
<!-- page level content -->
</mysc:PageBody>
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.
![]() | |
| Figure 3. The PageTemplating Assembly Diagram: This class diagram represents the reusable component can be developed for the page template architecture. |
Public Class PageBody
Inherits PageBodyBase
Private strFooterText As String
Public Property HeaderCssClass() As String
Get
Return strFooterText
End Get
Set(ByVal Value As String)
strFooterText = Value
End Set
End Property
End Class
The next thing to consider is extending the PageTemplateBase class. In this class definition, all you need to do is shadow the PageBase property so that its return type matches the PageBody type of the consuming application, rather than the PageBodyBase type. You do this so that custom properties specific to your template are exposed from this property. Public Class PageTemplate
Inherits PageTemplateBase
Public Shadows Property PageBody() As PageBody
Get
Return CType(MyBase.PageBody, PageBody)
End Get
Set(ByVal Value As PageBody)
MyBase.PageBody = Value
End Set
End Property
End Class
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 wastedyou 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 Model | ASP.NET 2.0 |
| PageBody control | Content 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. |
| DevX is a division of Jupitermedia Corporation © Copyright 2007 Jupitermedia Corporation. All Rights Reserved. Legal Notices |