 |
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 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. |
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.
Charlie has over a decade of experience in website administration and technology management. As the site admin, he oversees all technical aspects of running a high-traffic online platform, ensuring optimal performance, security, and user experience.
| |