[
Advertise | Submit Code | About us | Contact us | Link us
]
Go!
Membership Services
Login
Register

Home
C# General

General

C# Language

Design & Architecture

Algorithms

Database

Security

Active Directory

COM Interop

Remoting
C# Windows Forms

General

Combo and List boxes

Miscellaneous Controls

Button Controls

Edit Controls
Cutting Edge

ASP.NET 2.0

Visual Studio 2005

Windows Longhorn

SQL Server 2005
C# Multimedia and GDI+

General

DirectX

GDI+

Audio
Internet & Web

General

Images and multimedia

Database

Utilities

Security

ASP.NET Controls

Design and Architecture

Webservices
.NET

General

Design & Architecture

Algorithms

Database

Security

Active Directory

COM Interop

Remoting

ADO.NET

XML.NET

Tools

Enterprise

IDE
Visual Basic .NET

VB.NET General

VB.NET Controls
General Reading

.NET Books Review

Product Showcase

Book Chapters

Business Design & Strategy
Community

Discuss

Job Board

Discussion

CodeXchange
DeveloperLand

Advertise

Submit Code

About us

Contact us

Link us
Miscellaneous

Favorite Links

Downloads

Programming Sites

Top Stories
Regular Expressions

E-Mail

Date/Time
Home > Internet & Web > General
An Innovative Technique for Creating Reusuable Page Templates in ASP.NET
Posted by on Sunday, June 26, 2005 (EST)

One area where I feel that ASP.NET still leaves a bit to be desired is reusuability for ASP.NET pages at the UI level.Several techniques exist to combat this problem

This article has been viewed: 4,405 times
Technology: General.

pagetemplates.zip (88.50 KB)

Code reusuability is (or at least should be) one of the major goals of any good object-oriented programmer. Reusuable code cuts development time and also minimizes the chances of introducing new bugs into an application. Fortunately, the ASP.NET framework has made code reusuability easier and much more elegant than it was in classic ASP. I, personally, am very grateful for all of the wonderful techniques that we have at our disposal for code reusuability (inheritance, ASP.NET server controls, user controls, etc..).

One area though, where I feel that ASP.NET still leaves a bit to be desired is reusuability for ASP.NET pages at the UI level. Several techniques exist to combat this problem, one of the most common being a technique that is based on overriding a base page's OnRender() method. By inheriting all of the pages in your web application from the base page, you can ensure a consistent look and feel across the entire application.

For me though, there is two major drawbacks to this approach. A base page's OnRender() method is nested pretty far into your application, so what happens if you decide you want to change something about the UI once your application has been deployed? Well, you have to edit the base page's OnRender() method, and editing code means recompiling, and recompiling means retesting and redeploying, none of which sound much like a good time to me.

Another stike against this approach is that is 100% reliant upon a code file (be it C# or VB). In other words, there is no ASP.NET page (or Code-Front, as I like to call it) associated with the base page. Any UI components that you want to exist across your web app, must be programatically built and added to the base page, and I think this is inconvienent (at best).

What would be nice, is if there were a technique whereby every page in your web application could inherit not only the *functionality* of a base page, but also the UI of that base page. Furthermore, the UI of the base page would be as simple to modify as editing a basic HTML/ASP.NET file.

Well believe it or not, it *is* possible, and in this article, I'm going to show you my technique for doing just that.

First things first, we must decide what that "basic look & feel" for our web application is going to be. I wanted to keep things simple for this article, so I whipped up this HTML and corresponding CSS that creates a header, footer, and left-side navbar for a ficticious web site about cooking.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
 <head>
  <title>Cooking: it's more fun than you think!</title>
  
  <style type = "text/css">
   body
   {
    font-family: verdana; font-size: 11px; color: #676767;
    margin: 0px; padding: 0px; background-color: #cccccc;
   }
   
   #Container
   {
    position: relative;
    width: 760px;    
    margin: auto;
    margin-top: 10px;
    background-color: #003366;
   }
   
   #Header
   {
    height: 80px; line-height: 80px;
    background-color: #336699;
    font-size: 18px; font-weight: bold; color: #ffffff;
    text-align: center;
    border-bottom: solid 1px #cccccc;
   }
   
   #NavBar
   {
    position: absolute; top: 80px; left: 0px;    
    width: 130px; height: 300px;
    padding: 5px;
    color: #ffffff;    
   }
   
   #PageContent
   {
    background-color: #ffffff;
    position: relative; top: 0px; left: 140px;
    padding: 10px;
    width: 600px;
    height: 300px;
   }
   
   #Footer
   {
    padding-left: 10px;
    height: 30px; line-height: 30px;
    background-color: #336699;
    font-weight: bold; color: #ffffff;
    text-align: center;
    border-top: solid 1px #cccccc;
    border-bottom: solid 1px #cccccc;
    clear: both;
   }
  </style>
 </head>
  
 <body>
  <div id = "Container">
   <div id = "Header">
    Cooking: it's more fun than you think!
   </div>
 
   <div id = "NavBar">
    <b>Navigation:</b>
    <ul>
     <li></li>
     <li>Searing Chicken</li>
     <li>Roasting Pork</li>
     <li>Onion Soup</li>
    </ul>
   </div>
 
   <div id = "PageContent">
    <asp:Label id = "lblBaseLabel" runat = "server">Hi, I'm the Base Label!</asp:Label>
    
    <br />
    <asp:PlaceHolder id = "phMainContent" runat = "server" />    
   </div>
 
   <div id = "Footer">
    Copyright (C) Cooking: It's more fun than you think, Inc. 2005.  All rights reserved.
   </div>
  </div>
 </body>
</html> 
Of course, the above would be better if the CSS were placed in an external stylesheet like so:

But I wanted the reader to be able to get a glimpse of the CSS before I hid it in an external file.

The next step in building our Page Template is to place the above ASP.NET/HTML in an XML file. That's right, an XML file. Something like this would work just fine:

<?xml version="1.0" encoding="utf-8" ?>
<PageTemplate>
 
 !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
 <head>
  <title>Cooking: it's more fun than you think!</title>
  <link rel = "stylesheet" type = "text/css" href = "./_stylesheets/layout.css" />
 </head>
  
 <body>
  <div id = "Container">
   <div id = "Header">
    Cooking: it's more fun than you think!
   </div>
 
   <div id = "NavBar">
    <b>Navigation:</b>
    <ul>
     <li></li>
     <li>Searing Chicken</li>
     <li>Roasting Pork</li>
     <li>Onion Soup</li>
    </ul>
   </div>
 
   <div id = "PageContent">
    <asp:Label id = "lblBaseLabel" runat = "server">Hi, I'm the Base Label!</asp:Label>
    
    <br />
    <asp:PlaceHolder id = "phMainContent" runat = "server" />
   </div>
 
   <div id = "Footer">
    Copyright (C) Cooking: It's more fun than you think, Inc. 2005.  All rights reserved.
   </div>
  </div>
 </body>
</html> 
 ]]>
 
</PageTemplate>
You can see that I haven't done anything spectacular here. Just wrapped the whole thing inside of an XML tag <PageTemplate>. You'll also notice the CDATA block. I use this just to ensure that I won't get any XML errors later on when I'm reading the file programatically (of course, this means, it's up to *you* to ensure your HTML is XHTML compliant -- but that's another article).

Finally, notice the <asp:PlaceHolder id = "phMainContent" runat = "server" /> control. This is critical. This PlaceHolder represents the *exact spot* that any non-templated content will be displayed. In other words, the phMainContent PlaceHolder will hold all of the controls that you will eventually define on the inheriting page.

We've now got our XML template file written and ready to go. You can think of this file as the corresponding ASP.NET page (or Code-Front) to the base page class that we're going to write shortly.

[ Part 2: Creating the base page class ]

The concept I'm going to use to create this Page Template is as follows:

1. Grab all of the controls from the inheriting page (remove them), and place them in a temporary control array.
2. Read the HTML from the XML template file into a string variable.
3. Parse string variable (which contains HTML) into series of ASP.NET Literal Controls, and/or HtmlControls, and/or WebControls, and (if applicable) 3rd party controls, and add the newly parsed control to the Page's ControlCollection.
4. Take all of the controls that were placed in the temporary control array (from Step 1) and add them to the PlaceHolder, phMainContent.

I know this may seem a bit convoluted, but I'm confident that it will become clearer as we go, so let's get started by first creating a BasePge class and then implementing Step 1.

The basic shell of the BasePage class should look something like this:
using System;
using System.Xml;
using System.Web.UI;
using System.Reflection;
using System.Web.UI.WebControls;
namespace sstchur.web.Pages
{ 
 public class BasePage : System.Web.UI.Page
 {
  // Protected Variables (UI Components)
  protected Label lblBaseLabel;   
  
  // Private Variables
  private string m_strTemplateFile;
      
  // Public Properties
  public string TemplateFile
  {
   set { m_strTemplateFile = value; }
  }      
  
  // Constructor
  public BasePage()
  {
   // Initialize the TemplateFile in case one is not specified
   m_strTemplateFile = "~/_templates/standardpage.xml";   
  }  
  
  protected override void OnInit(EventArgs e)
  {
   // Load the XHTML Template
   InvokePageTemplate();
        
   // Initialize any components/variables specific to this class (or its base)
   InitializeComponent();
      
   // Let the base class do its thing
   base.OnInit (e);
  }    
   
  private void InitializeComponent()
  {
   // Wireup Page_Load Event
   this.Load += new EventHandler(Page_Load);      
  }    
  
  private void Page_Load(object sender, EventArgs e)
  {
   // Put any needed Page_Load functionality here
  }
  
  // Here is where all the tricky stuff happens
  private void InvokePageTemplate()
  {
   // Here is where the bulk of the BasePage's implementation will eventually go
  }  
 }
}
One of the first things you'll notice is a few non-orthodox using statements: namely, using System.Xml and using System.Reflection. You might have already guessed that we'd need access to Xml functionality since our TemplateFile is written in XML, but the System.Reflection namespace should be a bit of a surprise. The reason it's needed won't become clear until a little later on, so I'll postpone its discussion until we get to the code that actually make use of it.

Beyond that, there's nothing earth-shattering in the BasePage class. You can see that we inherit from the standard System.Web.UI.Page class, and we go ahead and wire up the Page_Load method using the default technique that VS.NET implements for you when you create a new WebForm (overriding the OnInit() method and calling InitializeComponent()).

You'll notice however, that prior to calling InitializeComponent(), we have a call to a custom, private, method: InvokePageTemplate() (which we have yet to implement). If you remember back to the 5 steps I outlined for creating the BasePage class, step 1 was grabbing all of the controls from the inheriting page (removing them), and placing them in a temporary control array.

private void InvokePageTemplate()
{
 // Copy off inheriting page's control into a temporary Control[] array
 Control[] controls = new Control[this.Controls.Count];
 this.Controls.CopyTo(controls, 0);
 this.Controls.Clear();
}
Step 2 was reading the HTML/XHTML from the XML template file into a string variable:
private void InvokePageTemplate()
{
 // Copy off inheriting page's control into a temporary Control[] array
 Control[] controls = new Control[this.Controls.Count];
 this.Controls.CopyTo(controls, 0);
 this.Controls.Clear();
 
 // Load the XML tempalte file into an XmlDocument object
 doc = new XmlDocument();
 doc.Load(Server.MapPath(m_strTemplateFile));
      
 // Place the content's of the  tag into a string variable
 XmlElement root = doc.DocumentElement;
 XmlNode nodeTemplate = root.SelectSingleNode("//PageTemplate");
 string strTemplate = nodeTemplate.InnerText; 
}
Step 3 was parsing the string variable (which contains HTML) into series of ASP.NET Literal Controls, and/or HtmlControls, and/or WebControls, and (if applicable) 3rd party controls, and adding the newly parsed control to the Page's ControlCollection. This is surprisingly easy to do with the Page.ParseControl() method. What surprised me the most about this method, was that it does not need to be called repeatedly. Rather, any nested controls in the string to be parsed, will become child controls of their containing controls (essentially the same way .NET parses your ASP.NET page!).

private void InvokePageTemplate()
{
 // Copy off inheriting page's control into a temporary Control[] array
 Control[] controls = new Control[this.Controls.Count];
 this.Controls.CopyTo(controls, 0);
 this.Controls.Clear();
 
 // Load the XML tempalte file into an XmlDocument object
 doc = new XmlDocument();
 doc.Load(Server.MapPath(m_strTemplateFile));
      
 // Place the content's of the  tag into a string variable
 XmlElement root = doc.DocumentElement;
 XmlNode nodeTemplate = root.SelectSingleNode("//PageTemplate");
 string strTemplate = nodeTemplate.InnerText; 
 
 // Parse the HTML/XHTML contained in the strTemplate string; add the parsed controls to the Page's ControlCollection.
 Control ctrlTemplate = Page.ParseControl(strTemplate);
 this.Controls.Add(ctrlTemplate); 
}
Step 4 was taking all of the controls that were placed in the temporary control array (from Step 1) and adding them to the PlaceHolder, phMainContent. Before we can do this though, we need a reference to the phMainContent PlaceHolder. This is straight-forward enough, so let's tackle everything at once:

private void InvokePageTemplate()
{
 // Copy off inheriting page's control into a temporary Control[] array
 Control[] controls = new Control[this.Controls.Count];
 this.Controls.CopyTo(controls, 0);
 this.Controls.Clear();
 
 // Load the XML tempalte file into an XmlDocument object
 doc = new XmlDocument();
 doc.Load(Server.MapPath(m_strTemplateFile));
      
 // Place the content's of the  tag into a string variable
 XmlElement root = doc.DocumentElement;
 XmlNode nodeTemplate = root.SelectSingleNode("//PageTemplate");
 string strTemplate = nodeTemplate.InnerText; 
 
 // Parse the HTML/XHTML contained in the strTemplate string; add the parsed controls to the Page's ControlCollection.
 Control ctrlTemplate = Page.ParseControl(strTemplate);
 this.Controls.Add(ctrlTemplate); 
 
 // Get a reference to the phMainContent PlaceHolder
 PlaceHolder phMainContent = (PlaceHolder)ctrlTemplate.FindControl("phMainContent");   
          
 // Add each control from the inheriting page (saved from Step 1) to the phMainContent's ControlCollection
 foreach (Control c in controls)
  phMainContent.Controls.Add(c);
}

At this point, we *could* be done. If you create a new ASP.NET page with literally NOTHING in it (save for an <%@ Page ... %> directive) which inherits from sstchur.web.Pages.BasePage, you'll have a working ASP.NET page that looks just like the template file we created way back at the beginning of this article. You can go ahead an try it if you want (don't forget to compile everything though).

The only problem at this point, is that we don't have access to any of the base page's components. You may have noticed that I added a Label to the template file, whose ID was lblBaseLabel and whose default Text was "Hi, I'm the Base Label!" I did this specifically for demonstration purposes. We obviously want to be able to access the base page's components (like lblBaseLabel), and with our BasePage class in its current state, we can't do that. ASP.NET will let you try alright, but you'll get a NullReferenceException, and that's never good.

There are two solutions that I can think of, both are similar, but one is much more convenient. The first way, is to simply use FindControl() to obtain a reference to a control with a given ID. In the case of our lblBaseLabel, the code (which would be placed in the inheriting page) would look something like this:

Label baseLabel = (Label)Page.FindControl("lblBaseLabel);
baseLabel.Text = "Overridden value";

However, having to call FindControl EVERY time we want to access one of the base page's controls, is sloppy, repetitive, and I dunno... just all around bad.

A much better solution, would be one that would allow us to access the base page's components via base.{ComponentId} where {ComponentId} is the Id of the component we want to access (in our case, base.lblBaseLabel).

How do we do this? Why, with Reflection of course! While a detailed discussion of Reflection is far, far beyond the scope of this article, I'm going to try to touch on just the bare minimum required to understand the reflection-based code in our BasePage class. A good start would be to try to obtain a thorough understanding of just what it is we're trying to accomplish. Let's recap:

- We have a BasePage class which all of our ASP.NET pages will inherit from.
- Inheriting from the BasePage class mean inheriting not only functionality, but also UI components contained within the XML template file.
- If we want to be able to access any of the components from XML template file, in the BasePage, then we need to add a protected member variable to the BasePage, whose variable name is the same as the ID of the ASP.NET component in the XML template file.
- We also want each protected member variable of the BasePage to be available to inheriting pages (and not throw back NullReferenceExceptions when we try to access them).
- Finally, we already know that in order to avoid a NullReferenceException, one solution is a call to FindControl().

If we take all of the requirements that I've outlined above, the logic to implement it can be summarized as follows:

For each protected member variable in the BasePage, do the following:
-- Determine the variable's name (as far as the compiler is concerned).
-- Call FindControl(variableName) to get a reference to the control whose Id is the same as that variableName.
-- Set the value of the protected member variable equal to the control that was returned with the call to FindControl()

Sounds complicated doesn't it? Well, to be truthful, is kind of is. Fortunately though, the .NET framework wraps up a good portion of the complexity in the System.Reflection namespace, which makes what we're trying to do a lot less daunting that it might at first seem.

The first step is to determine what protected member variables have been defined in our BasePage. We can do this by using the FieldInfo class. In our case, I'm going to create an array of FieldInfo objects to hold information about each variable defined in our base page. Then, I'm going to create a Type object which is of the type sstchur.web.Pages.BasePage. Finally, I'll call GetFields(...) on my type object and assign the resulting array to the FieldInfo array I created a moment earlier. It looks something like this:
FieldInfo[] fieldInfo;
Type myType = typeof(sstchur.web.Pages.BasePage);
fieldInfo = myType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
The BindingFlags that were passed into the GetFields(...) method are just a way of specifying which types of variables we want to retrieve information about. In our case, we're only interested in NonPublic (protected actually, but we'll get to that in a minute), instance variables that have been explicitely declared.

Once we've got our fieldInfo array populated, we can loop through it and take a crack at those few remaining tasks:

FieldInfo[] fieldInfo;
Type myType = typeof(sstchur.web.Pages.BasePage);
fieldInfo = myType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
for (int i = 0; i < fieldInfo.Length; i++)
{    
 if (!fieldInfo[i].IsPrivate)
 {
  string id = fieldInfo[i].Name;
             
  Control c = ctrlTemplate.FindControl(id);
  if (c != null)
   fieldInfo[i].SetValue(this, c);
 }
}
In the above for loop, we simply check to make sure the current variable is not private (we pulled back NonPublic fields with our call to GetFields. So a variable that isn't public and isn't private, much be protected). Next, we retrieve the variable Name, and assign it to the string variable id. Finally, we attempt to get a reference to a Control object by calling FindControl(id). If the control that's returned is not null, we assign it to the current variable (field) in our for loop.

And that's all there is too it. See, now that wasn't so bad was it?

Now, I know what a lot of you much be thinking. Doesn't this add a tremendous amount of overhead to your pages? I would suspect that the additional overhead probably is significant. However, in my testing (which was far from extensive) the pages seemed to perform pretty darned well. Infact, they performed well enough, that the company I work for agreed to use this concept in a web application we're working on.

The only thing I can say with regards to overhead is to do some benchmarking of your own. If the numbers don't satisfy you, you can always elect to use a different technique for page templates. At the very least, you'll have gotten to read a stunningly interesting article by yours truely :-)

[ Part 3 - Putting it all together ]

So now we've got pretty much everything we need. Let's take a look at each of the completed files that would go into an example page:
<?xml version="1.0" encoding="utf-8" ?>
<PageTemplate>
 
 !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
 <head>
  <title>Cooking: it's more fun than you think!</title>
  <link rel = "stylesheet" type = "text/css" href = "./_stylesheets/layout.css" />
 </head>
  
 <body>
  <div id = "Container">
   <div id = "Header">
    Cooking: it's more fun than you think!
   </div>
 
   <div id = "NavBar">
    <b>Navigation:</b>
    <ul>
     <li></li>
     <li>Searing Chicken</li>
     <li>Roasting Pork</li>
     <li>Onion Soup</li>
    </ul>
   </div>
 
   <div id = "PageContent">
    <asp:Label id = "lblBaseLabel" runat = "server">Hi, I'm the Base Label!</asp:Label>
    
    <br />
    <asp:PlaceHolder id = "phMainContent" runat = "server" />
   </div>
 
   <div id = "Footer">
    Copyright (C) Cooking: It's more fun than you think, Inc. 2005.  All rights reserved.
   </div>
  </div>
 </body>
</html> 
 ]]>
 
</PageTemplate>

-- CSS file -- (note that I put all of my CSS files in a subdirectory ./_stylesheets)
body
{
 font-family: verdana; font-size: 11px; color: #676767;
 margin: 0px; padding: 0px; background-color: #cccccc;
}
#Container
{
 position: relative;
 width: 760px;    
 margin: auto;
 margin-top: 10px;
 background-color: #003366;
}
#Header
{
 height: 80px; line-height: 80px;
 background-color: #336699;
 font-size: 18px; font-weight: bold; color: #ffffff;
 text-align: center;
 border-bottom: solid 1px #cccccc;
}
#NavBar
{
 position: absolute; top: 80px; left: 0px;    
 width: 130px; height: 300px;
 padding: 5px;
 color: #ffffff;    
}
#PageContent
{
 background-color: #ffffff;
 position: relative; top: 0px; left: 140px;
 padding: 10px;
 width: 600px;
 height: 300px;
}
#Footer
{
 padding-left: 10px;
 height: 30px; line-height: 30px;
 background-color: #336699;
 font-weight: bold; color: #ffffff;
 text-align: center;
 border-top: solid 1px #cccccc;
 border-bottom: solid 1px #cccccc;
 clear: both;
}
using System;
using System.Xml;
using System.Web.UI;
using System.Reflection;
using System.Web.UI.WebControls;
namespace sstchur.web.Pages
{ 
 public class BasePage : System.Web.UI.Page
 {
  // Protected Variables (UI Components)
  protected Label lblBaseLabel;   
  
  // Private Variables
  private string m_strTemplateFile;
      
  // Public Properties
  public string TemplateFile
  {
   set { m_strTemplateFile = value; }
  }      
  
  // Constructor
  public BasePage()
  {
   // Initialize the TemplateFile in case one is not specified
   m_strTemplateFile = "~/_templates/standardpage.xml";   
  }  
  
  protected override void OnInit(EventArgs e)
  {
   // Load the XHTML Template
   InvokePageTemplate();
        
   // Initialize any components/variables specific to this class (or its base)
   InitializeComponent();
      
   // Let the base class do its thing
   base.OnInit (e);
  }    
   
  private void InitializeComponent()
  {
   // Wireup Page_Load Event
   this.Load += new EventHandler(Page_Load);      
  }    
  
  private void Page_Load(object sender, EventArgs e)
  {
   // Put any needed Page_Load functionality here
  }
  
  // Here is where all the tricky stuff happens
  private void InvokePageTemplate()
  {
   Control[] controls = new Control[this.Controls.Count];
   this.Controls.CopyTo(controls, 0);
   this.Controls.Clear();
     
   XmlDocument doc;   
   doc = new XmlDocument();
   doc.Load(Server.MapPath(m_strTemplateFile));
        
   XmlElement root = doc.DocumentElement;     
   XmlNode nodeTemplate = root.SelectSingleNode("//PageTemplate");
   string strTemplate = nodeTemplate.InnerText;       
     
   Control ctrlTemplate = Page.ParseControl(strTemplate);     
   this.Controls.Add(ctrlTemplate);   
   PlaceHolder phMainContent = (PlaceHolder)ctrlTemplate.FindControl("phMainContent");   
          
   foreach (Control c in controls)
    phMainContent.Controls.Add(c);
     
   FieldInfo[] fieldInfo;
   Type myType = typeof(sstchur.web.Pages.BasePage);
   fieldInfo = myType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
   for (int i = 0; i < fieldInfo.Length; i++)
   {    
    if (!fieldInfo[i].IsPrivate)
    {
     string id = fieldInfo[i].Name;
                
     Control c = ctrlTemplate.FindControl(id);
     if (c != null)
      fieldInfo[i].SetValue(this, c);
    }
   }   
  }  
 }
}
One more file... err, actually 2 more files remain. An ASPX example file and its corresponding code-behind. You're not going to believe how absolutely simple these files are to create:

-- Example.aspx --
<%@ Page language = "C#" AutoEventWireup = "false" Codebehind = "example.aspx.cs" Inherits = "sstchur.web.ExamplePage" %>
using System;
namespace sstchur.web
{
 public class ExamplePage : sstchur.web.Pages.BasePage
 {
  private void Page_Load(object sender, System.EventArgs e)
  {
   base.lblBaseLabel.Text = "My value has been overridden by the example.aspx page";
  }
  override protected void OnInit(EventArgs e)
  {
   InitializeComponent();
   base.OnInit(e);
  }
  
  private void InitializeComponent()
  {    
   this.TemplateFile = "~/template.xml";   
   this.Load += new System.EventHandler(this.Page_Load);
  }  
 }
}
Now, create a new web project in Visual Studio, and add each of the files that we created above to it. Compile the project, and browse to the file Example.aspx.

You should see something like this :


Of course, just for grins and giggles, it might be interesting to add a few additional components to our Example.aspx file, just to see if they do indeed act the way we'd expect them to in a normal ASP.NET page.

<%@ Page language = "C#" AutoEventWireup = "false" Codebehind = "example.aspx.cs" Inherits = "sstchur.web.ExamplePage" %>
<form id = "theForm" runat = "server">
 <p><asp:TextBox id = "txt" runat = "server" /></p>
 <p><asp:Button id = "btn" Text = "Click Me!" runat = "server" />
</form>

It's worth noting that if you happen to have any components in your base page's XML template file that require a <form runat = "server"> tag, then you'll want to make sure you put the server-side form in the XML template file, rather than in the Code-Front of the inheriting page. And of course, you then won't need the <form runat = "server"> in the Code-Front of your inheriting page anymore.

Caveats: There is two caveats I'd like to point out:

1. EVERY control you use MUST be given an ID. I suspect this has to do with timing and reflection, but it's just a good habit to name your controls, so do it!

2. Accessing the base page's components can't be done any earlier than in the Page_Load method of the inheriting page. If you try to do it in InitializeComponent() (or OnInit()) you'll be attempting to access the controls before the reflection has had a chance to works its magic. The result? A NullReferenceException.

Well, I hope this article has intrigued you and given you the confidence to start exploring the possibilities of Reusuable Page Templates using XML and reflection. The examples we've seen in this article are very very basic, but rest assured, they can be as complex as you like. The most recent web application I'm working on for example, makes use of this Page Template concept, and we're using 3rd party navigation components, and several user controls all within our page template! We even exposed method whereby certain navigational components that might not be applicable to every page, can be shown or hidden at run time.

So I encourage you to give this concept a try. At best, it will save you a ton of time, and at worst, you'll learn something!

Happy programming.

Top Go to Table of Contents

About Stephen Stchur

Programming, Cooking

Click here if you want to know more about .

Other articles that may interest you

  • Write a Word Add-In – Part 0
  • Write a Word Add-In – Part I
  • Lengthy Operations on Single Thread in .NET Application
  • Learning Draughts
  • Exceptions and Performance
  • Average Rating :

    Discussion Forums
    Got a programming related question? Hopefully someone has the answer... Want to help out other developers? Visit our discu