Click here to Skip to main content
15,867,308 members
Articles / Web Development / ASP.NET
Article

Page Template Framework for ASP.NET 1.1

Rate me:
Please Sign up or sign in to vote.
4.64/5 (63 votes)
16 Nov 20048 min read 296.8K   5.3K   168   85
The Page Template Framework for ASP.NET 1.1 provides a configurable solution for creating page templates in a Web application. Using this framework, page templates are stored in an XML file, and can be dynamically configured without recompiling the Web application.

Sample Image - PageFramework.jpg

Introduction

ASP.NET 2.0 introduces a great new feature called Master Pages, which provides a framework for creating page templates that can be reused across a Web application. However, since ASP.NET 2.0 is currently in beta, much of the development community cannot realize the benefits of Master Pages today. The goal of this article is to demonstrate a framework for creating page templates in ASP.NET 1.1, the Page Template Framework, which provides similar functionality to Master Pages and is organized in such a way to be easily migrated to the Master Pages framework.

There are many articles floating around the Web that offer solutions for creating page templates in ASP.NET 1.1. I spent much time looking over these articles and discovered that there were many ways to accomplish the task. However, I had yet to find a solution that fit my needs.

One of the articles I had read, Peter Provost’s ASP.NET Page Templates – Using Inheritance, prompted a proverbial “light bulb” to appear over my weary head. I have worked with ColdFusion for many years, and one of the frameworks available for ColdFusion development – Fusebox – utilizes a “circuit” to organize the structure of components within a page. This same idea could be merged with page inheritance to provide a dynamic and scalab le solution for page templates in ASP.NET 1.1.

Configurable Page Templates

Almost every article I researched when considering this framework offered a solution that required a predefined page template be developed. This meant that any change to be made to the template would require the Web application to be recompiled and re deployed to the Web server. Although this may seem a trivial task in some cases, it’s just a waste of time and effort. Considering this and other idiosyncrasies tied to developing templates, I decided to leverage a “configure vs. customize” approach.

Changes to a page template should be configured rather than customized. If page inheritance is used as the templating mechanism, the base page must be modified and recompiled to incorporate changes. This customization makes maintaining dynamic page templates an unnecessarily tedious task. Using a configurable approach, changes to a page’s template can be made in a configuration file which defines a page template declaratively.

The Page Template Framework

Based on this approach, I created the Page Template Framework. Figure 1 depicts the main components of the Page Template Framework.

Figure 1 – The Page Template Framework

Page Template Framework Model

As depicted in Figure 1, the Page Template Framework utilizes page inheritance to templates for use within a Web application. In order for a Web Form to utilize the Page Template Framework, it must inherit the PageBase class, which derive s from the Page class. This approach is quite similar to others mentioned earlier in this article. However, the configuration model used by the Page Template Framework differentiates this approach from the others.

The Page Template Framework defines page templates as a collection of User Controls to be added to a Page in a specific order, placed either before or after the content of the derived page. To do this, the Page Template Framework contains configuration components for declaratively defining page templates to be implemented by a Web application. The PageConfig class is an XML serialize-able class that represents the page template configuration for a Web application. This class is simply an API built to access the Page.config file, which is an XML file containing the page template definitions for a Web application.

The following is a sample Page.config file:

Listing A – Page.config

XML
<?xml version="1.0" encoding="utf-8"?>
<PageConfig>
  <PageTemplates>
    <PageTemplate Name="MainTemplate" IsDefault="true">  
      <Controls>
        <Control Path="~/Controls/MainTemplate/Header.ascx"
                 ControlPlacement="BeforeContent" />
        <Control Path="~/Controls/MainTemplate/Footer.ascx"
                 ControlPlacement="AfterContent" />
      </Controls>
    </PateTemplate>
    <PageTemplate Name="DivisionTemplate">
      <Controls>
        <Control Path="~/Controls/DivisionTemplate/PagePre.ascx"
                 ControlPlacement="BeforeContent" />
        <Control Path="~/Controls/DivisionTemplate/Header.ascx"
                 ControlPlacement="AfterContent" />
        <Control Path="~/Controls/DivisionTemplate/Footer.ascx"
                 ControlPlacement="AfterContent" />
      </Controls>
    </PageTemplate>
  </PageTemplates>
  <Pages>
    <Page Path="~/Default.aspx" TemplateName="MainTemplate" />
    <Page Expression="^~/Templates/.*$" TemplateName="MainTemplate" />
  </Pages>
</PageConfig>

As outlined in Listing A, the Page.config file consists of two sections: <PageTemplates> and <Pages>. The <PageTemplates> section contains the declarative definitions for page templates a vailable for use within the Web application. The <Pages> section contains a list of Page paths within the Web application and the name of the template the Page will implement. Also, a Regular Expression can be used to spec ify the path for a group of pages (i.e., pages within the /Templates folder), relative to the application root ("~"). The second <Page> item in Listing A will apply to all pages within the ~/Templates/ folder of the appli cation.

Additionally, a page template can be specified as the default template for Pages that are not listed in the <Pages> section of the Page.config file, but derived from the PageBase class. The IsDefault attribute can be added to a <PageTemplate> element to specify that page template as default.

In order for a Page to implement the page template as defined in the Page.config file, it must derive from the PageBase class. During the OnInit method of every Page, controls are added to the Controls collection of that Page. The PageBase overrides the OnInit method of the Page class to implement the page template defined in the Page.config file. To implement the page te mplate, the PageBase uses the PageConfig class to determine the controls that define the page template, and adds those controls to the Controls collection exposed within the OnInit method.

Listing B – PageBase.cs – OnInit Method

C#
/// <summary>
/// Override the OnInit method of the Page class, and implement
/// the controls listed in the Page.config file
/// </summary>
/// <param name="e"></param>
protected override void OnInit(EventArgs e)
{
 // Obtain the path to the Page.config file for the current application
 string pageConfigPath = Server.MapPath(PageConfig.PageConfigFilePath);

 // Check to see if the Page.config file exists
 if (File.Exists(pageConfigPath))
 {
  // Check to see if the Page.config file has been loaded into Cache
  if (Cache[PageConfig.PageConfigCacheKey] == null)
  {
   // Create a CacheDependency on the Page.config file
   CacheDependency dependency = new 
        System.Web.Caching.CacheDependency(pageConfigPath);

   // Load the Page.config file and insert the PageConfig into Cache
   Cache.Insert(PageConfig.PageConfigCacheKey, 
                       PageConfig.Load(pageConfigPath), dependency);
  }

  // Load the Page.config file into a PageConfig object
  PageConfig pc = (PageConfig)Cache[PageConfig.PageConfigCacheKey];

  // Get a reference to the current Page from the PageConfig
  Page page = pc.FindPage(Request);
  
  // Locate the PageTemplate for the current Page object
  PageTemplate template = (page == null) ? pc.FindDefaultTemplate() 
                              : pc.FindTemplate(page.TemplateName);

  // Check to ensure a valid TemplateName attribute was specified
  if (template != null)
  {
   // Declare a Control into which a template will be loaded
   System.Web.UI.Control control = null;

   // Iterate over the Controls in the template to locate
   // the Controls with BeforeContent placement
   for (int idx = 0; idx < template.Controls.Count; idx++)
   {
    // Check to see if the current Control has BeforeContent placement
    if (((Control)template.Controls[idx]).ControlPlacement == 
                               ControlPlacement.BeforeContent)
    {
     // Load the specified Control from the Path specified in the PageConfig
     control = this.LoadControl(((Control)template.Controls[idx]).Path);

     // Check to see if the Control requires a unique name
     if (((Control)template.Controls[idx]).UniqueName != null)
     {
      // Ensure that the value is not an Empty String
      if (((Control)template.Controls[idx]).UniqueName != String.Empty)
      {
       // Set the ID property of the Control to the UniqueName attribute
       control.ID = ((Control)template.Controls[idx]).UniqueName;
      }
     }
     
     // Add the Contorl to the current Page
     this.Controls.AddAt(idx, control);
    }
   }

   // Initialize the Page content
   base.OnInit(e);

   // Iterate over the Controls in the template to locate
   // the Controls with AfterContent placement
   foreach (Control ctrl in template.Controls)
   {
    // Check to see if the current Control has AfterContent placement
    if (ctrl.ControlPlacement == ControlPlacement.AfterContent)
    {
     // Load the specified Control from the Path specified in the PageConfig
     control = this.LoadControl(ctrl.Path);

     // Check to see if the Control requires a unique name
     if (ctrl.UniqueName != null)
     {
      // Ensure that the value is not an Empty String
      if (ctrl.UniqueName != String.Empty)
      {
       // Set the ID property of the Control to the UniqueName attribute
       control.ID = ctrl.UniqueName;
      }
     }

     // Load the specified Control and add it to the current Page
     this.Controls.Add(control);
    }
   }
  }
  else
  {
   // Check to ensure that the Page as listed in the PageConfig
   if (page != null)
   {
    // The page has an invalid TemplateName attribute, throw an exception
    throw new Exception(
      String.Format("Invalid TemplateName " + 
                    "for Page \"{0}\".  There is no" + 
                    " template named \"{1}\".", 
      page.Path, 
      page.TemplateName));
   }
   else
   {
    // The pages was not listed in the PageConfig,
    // so Initialize the Page content
    base.OnInit(e);
   }
  }
 }
 else
 {
  // Initialize the Page content
  base.OnInit(e);
 }
}

As detailed in Listing B, the PageBase class utilizes the PageConfig class to load the Page.config file into the Cache object. A CacheDependency is added to the Cache object based on the Page.config file path so that changes to the Page.config file will invalidate the value stored in the Cache object. Then, the PageBase class attempts to find the current Page (using the current HttpRequest instance) in the PageConfig. If the Page is found, its template is then located. If the Page has a valid TemplateName specified, the PageBase class will attempt to load the controls listed in the PageConfig in the order in which they are listed in the Page.config file. Their position relative to the content of the Page implementing the template is specified within each <Control> element of the Page.config file.

The Sample Application

The sample application provided demonstrates the functionality of the Page Template Framework. The only Page in the application, Default.aspx, is configured with content for Acme Business Corporation. This Page initial ly implements the MainTemplate page template, which is listed below:

Listing C – MainTemplate Page Template

XML
<PageTemplate Name="MainTemplate">
  <Controls>
    <Control Path="~/Controls/MainTemplate/Header.ascx" 
             ControlPlacement="BeforeContent" />
    <Control Path="~/Controls/MainTemplate/ContentContainerPre.ascx" 
             ControlPlacement="BeforeContent" />
    <Control Path="~/Controls/MainTemplate/NavigationContainerPre.ascx" />
             ControlPlacement="BeforeContent" 
    <Control Path="~/Controls/General/Navigation.ascx"
             ControlPlacement="BeforeContent" 
             UniqueName="GeneralNavigation" />
    <Control Path="~/Controls/MainTemplate/NavigationContainerPost.ascx" 
             ControlPlacement="BeforeContent" />
    <Control Path="~/Controls/MainTemplate/ContentContainerPost.ascx" 
             ControlPlacement="AfterContent" />
    <Control Path="~/Controls/MainTemplate/Footer.ascx"
             ControlPlacement="AfterContent" />
  </Controls>
</PageTemplate>

Note the additional attribute on the fourth <Control> element in Listing C. The UniqueName attribute of the <Control> element can be used to provide programmatic access to the page template’s controls from the Page that is implementing the template. The sample page, Default.aspx, includes code within its Page_Load method that demonstrates this functionality.

Listing D – Programmatic Access to Controls

C#
// Find the General Navigation Control by it's UniqueName attribute
PageTemplateSample.Controls.General.Navigation nav = 
    (PageTemplateSample.Controls.General.Navigation)
    this.FindControl("GeneralNavigation");

To test this functionality, change the TemplateName attribute of the <PAGE> element for Default.aspx to “DivisionTemplate”, and reload the Page.

Listing E – Changing a Page’s Template

XML
<Page Path="~/Default.aspx" TemplateName="DivisionTemplate" />

Not only will the content of Default.aspx appear within the Division page template, but the appearance of the Navigation.ascx User Control will change based on the programmatic access to the control, as outlined above.

Considerations

This framework was developed to provide functionality for creating page templates for use within ASP.NET 1.1 Web applications; therefore, it is important to note that there are some considerations to make when implementing the framework. In ASP.NET 2. 0, Master Pages will provide design-time support for the content of Master Pages. The Page Template Framework does not provide design-time support for the content of the page templates; however, you can still use the Web Forms designer to create the cont ent of a Page utilizing the framework.

The Page Template Framework, by virtue of its design, provides a system for defining page templates to be implemented in a class derived from the PageBase class. This design could be extended to enable declarative page template inheritanc e. Consider the following Page.config XML fragment:

Listing F – Page Template Inheritance

XML
<PageTemplate Name="MyTemplate">
  <Controls>
    <Control Path="~/Controls/Header.ascx" 
             ControlPlacement="BeforeContent" />
    <Control Path="~/Controls/Footer.ascx" 
             ControlPlacement="AfterContent" />
  </Controls>
</PageTemplate>
<PageTemplate Name="MyDetailTemplate" Inherits="MyTemplate">
  <Controls>
    <Control Path="~/Controls/ContentHeader.ascx"
             ControlPlacement="BeforeContent" />
    <Control Path="~/Controls/LocalNavigation.ascx" 
             ControlPlacement="AfterContent" />
   </Controls>
</PageTemplate>

As depicted in Listing F, declarative page template inheritance would allow nested page templates. Child page templates would be processed by the framework in between its parent’s Controls placed before and after the derived Page’s content. Another consideration for the framework is the notion of skinning. The Page Template Framework could be extended to provide personalized or localized versions of pages within a Web application. For example, the PageConfig class could be loaded into memory for users of the application. A user could then specify their skin preference within a settings form, which could then serialize the PageConfig instance and store it in a database or session store.

Conclusion

Although it is likely that this framework will be rendered obsolete by the release of ASP.NET 2.0, it is still a good tool to use when developing Web applications that require a flexible user interface. It is also a good exercise for developers who ar e unfamiliar with developing template-based applications, and want to get more experience.

Acknowledgements

Thanks to Sean Jones for reviewing and providing input on this article.

History

  • Nov 15, 2004 - Updated, Included VB.NET version of the source and demo.
  • Oct 26, 2004 - Updated, PageConfig.FindPage(HttpRequest request) is now case-insensitive. (Thanks to monkimodestructimo for pointing this out!)
  • Sept 14, 2004 - Updated, Page.config file is now stored in the Cache object.
  • Sept 14, 2004 - Updated, included Regular Expression support for <Page> items.
  • Sept 9, 2004 - Updated, included Default Page Template.
  • Sept 8, 2004 - Original release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Scott Van Vliet is a Principal Consultant with Neudesic based in Irvine, CA. He has been designing and developing technology solutions for the past 8 years, and has worked with Microsoft .NET technologies for over 3 years. Scott is currently developing solutions with ASP.NET 2.0, C# 2.0, Windows Presentation Foundation (codename "Avalon"), SQL Server 2000/2005, Reporting Services and SharePoint.

Scott welcomes feedback and can be reached through his Weblog at http://weblogs.asp.net/skillet/.

Comments and Discussions

 
GeneralThanks Pin
GaryWoodfine 20-Feb-07 10:53
professionalGaryWoodfine 20-Feb-07 10:53 
Questionbody on load ="MyFuction();" Pin
Sebastian Luis4-Jul-06 17:51
Sebastian Luis4-Jul-06 17:51 
GeneralPage templates not displayed, help needed.. Pin
shaistaShaikh21-Jun-06 1:42
shaistaShaikh21-Jun-06 1:42 
GeneralUnable to run the Demo Pin
mrizwansiddiqui2-Feb-06 2:18
mrizwansiddiqui2-Feb-06 2:18 
GeneralScott : Help Required - Problem using &quot;Form&quot; in this framework Pin
harmansahni2-Sep-05 4:38
harmansahni2-Sep-05 4:38 
GeneralRe: Scott : Help Required - Problem using &quot;Form&quot; in this framework Pin
Scott Van Vliet2-Sep-05 6:04
Scott Van Vliet2-Sep-05 6:04 
AnswerRe: Scott : Help Required - Problem using &amp;quot;Form&amp;quot; in this framework Pin
bjorn_i9-Nov-05 11:48
bjorn_i9-Nov-05 11:48 
For anybody having similar issues, here's a quick fix:

I used some of Peter Provost code (link to his article above)
If you create your form inside the OnInit method:
<br />
// Declare the form for the page<br />
HtmlForm form = new HtmlForm();<br />
form.ID = "nameOfForm";<br />

then use this method to add all controls that are on the page into the form:
<br />
private void AddControlsFromDerivedPage(HtmlForm form)<br />
{<br />
// preserve Validators<br />
ValidatorCollection tempVC = new ValidatorCollection();<br />
IEnumerator ienum = this.Validators.GetEnumerator();<br />
while(ienum.MoveNext())<br />
	{<br />
		tempVC.Add((IValidator)ienum.Current);<br />
	}<br />
<br />
	// Move Controls<br />
	int count = this.Controls.Count;<br />
	for( int i = 0; i< count ; ++i)<br />
	{<br />
		System.Web.UI.Control ctrl = this.Controls[0];<br />
		form.Controls.Add( ctrl );<br />
		this.Controls.Remove( ctrl );<br />
	}<br />
	//Restore Validators<br />
	ienum = tempVC.GetEnumerator();<br />
	while(ienum.MoveNext())<br />
	{<br />
		Page.Validators.Add((IValidator)ienum.Current);<br />
	}<br />
<br />
}<br />


Then before base.OnInit(e) add the form:
<br />
this.Controls.Add(form);<br />


Hope this helps
GeneralUsing master template Pin
stigditlevsen1-Jul-05 6:53
stigditlevsen1-Jul-05 6:53 
GeneralPage title Pin
Siva Jawaji27-Jun-05 15:18
Siva Jawaji27-Jun-05 15:18 
GeneralRe: Page title Pin
Siva Jawaji27-Jun-05 20:14
Siva Jawaji27-Jun-05 20:14 
GeneralRe: Page title Pin
cindyrod13-Aug-05 19:21
cindyrod13-Aug-05 19:21 
GeneralBug when deploying a project at the web root Pin
yavo21-Apr-05 22:00
yavo21-Apr-05 22:00 
GeneralRe: Bug when deploying a project at the web root Pin
Scott Van Vliet17-May-05 20:25
Scott Van Vliet17-May-05 20:25 
Generala project with an output type of class library cannot be started directly Pin
Jack Tam14-Apr-05 15:48
Jack Tam14-Apr-05 15:48 
GeneralRe: a project with an output type of class library cannot be started directly Pin
Scott Van Vliet17-May-05 20:24
Scott Van Vliet17-May-05 20:24 
GeneralfcProblems with templateColumn Pin
Member 179635111-Mar-05 5:17
Member 179635111-Mar-05 5:17 
GeneralRe: fcProblems with templateColumn Pin
Scott Van Vliet17-May-05 20:27
Scott Van Vliet17-May-05 20:27 
GeneralProblem with some server controls Pin
Member 27583525-Feb-05 0:45
Member 27583525-Feb-05 0:45 
GeneralRe: Problem with some server controls Pin
Scott Van Vliet25-Feb-05 5:20
Scott Van Vliet25-Feb-05 5:20 
GeneralRe: Problem with some server controls Pin
Member 27583528-Feb-05 8:48
Member 27583528-Feb-05 8:48 
GeneralRe-compile and crash Pin
izreal20-Jan-05 10:47
izreal20-Jan-05 10:47 
GeneralRe: Re-compile and crash Pin
Scott Van Vliet17-May-05 20:29
Scott Van Vliet17-May-05 20:29 
GeneralRe: Re-compile and crash Pin
izreal18-May-05 6:12
izreal18-May-05 6:12 
QuestionLicense for this? Pin
leoquijano20-Jan-05 6:43
leoquijano20-Jan-05 6:43 
AnswerRe: License for this? Pin
Scott Van Vliet2-Feb-05 16:06
Scott Van Vliet2-Feb-05 16:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.