Introduction
Anyone who has developed commercial websites has run into the problem of creating a template for the site. For most sites a large percentage of the HTML is the same or similar for all pages. The header, navigation, and footer elements of a typical site layout appear on almost every page.
Some developers will put that markup in every page's source file. Anyone who's had to maintain a site put together this way knows how difficult it is to make site-wide changes. You end up depending on massive search and replace operations, which can be difficult to do without creating markup errors that require hand-tuning to repair.
In traditional ASP programming, like many other server-based programming environments, this was typically solved using an include file. The page is divided into logical sections representing the header, left navigation, body, and footer elements. A separate include is created for each of the common elements and the body section is placed in the actual ASP page. The page then includes the appropriate files to build up the look and feel of the page. This is a significant improvement over the previous approach, but still creates a few maintenance problems.
First of all, the individual ASP files must contain the code necessary to include the correct support files. This makes each page's content strongly coupled to the site's template. It also means that when you add a new page to the site, you must remember to setup all the correct includes in the right order.
An additional problem happens when you decide to make a significant look-and-feel change to the site. If you're lucky, you may be able to make all of your changes in the include files. Most of the time, however, the tight coupling between the include files and the ASP pages means that you end up having to edit each and every ASP page as well.
With the introduction of ASP.NET, developers have been giving a powerful new set of tools to help resolve these problems. ASP.NET uses an object-oriented development paradigm. In practical terms this means that every page is a class that derives from System.Web.UI.Page
. This class provides a number of services to the web developer including caching, rendering, response and request access, etc.
So the question is: How can we best take advantage of the object-oriented nature of ASP.NET when creating websites? Is there a better way to create templates than using include-files?
Web User Controls
One of the first things a developer notices when getting started with ASP.NET is that a new style of control has been introduced: Web User Controls. User Controls allow a developer to encapsulate a common chunk of HTML or server-side code into a component that can be reused on many different pages.
User controls are not used in a page using the #include directive. Instead that are either placed in the ASPX file as a custom tag (with the Register directive) or from server-side code with the LoadControl
statement.
This article is not going to go into the details of creating and using User Controls; there are many other sources that cover that topic. They are, however, and improvement over the old include-file approach and deserved mention here.
They don't solve all of the problems discussed above though. If, for example, you encapsulate your page header in a user control, you still have to remember to use it on each and every page in the site. If you decide you need to add another user control to another part of your site, then once again you end up editing every page to get that user control embedded.
Also, depending on the layout of the pages in your site, you may end up having some formatting or positioning markup in each page to make sure that the User Control is exactly where you want it on the page. This markup is common to every page and we should be able to find a way to have that code exist in only one place.
Simple Page Inheritance
If you're familiar with object-oriented programming then you are probably already seeing something familiar. When you have a chunk of code that occurs in more than one class, it is common practice to refactor that code by creating a base class and moving that code "up" one level in the inheritance chain. Why not do that here?
Since all ASPX pages derive from System.Web.UI.Page
, we should be able to create a base class that sits between our page class and the Page class. This class will be responsible for producing the template used in our site. Then our page classes will derive from the base class and will only contain the markup and server controls needed for their specific task. To illustrate this, let's create an example base class called PageBase
and then derive a page class from it. (All of the examples in this article are written in C#, but you should easily be able to convert them to VB.NET or any other .NET language.)
using System;
using System.Web.UI;
public class PageBase : System.Web.UI.Page
{
private string _pageTitle;
public string PageTitle
{
get { return _pageTitle; }
set { _pageTitle = value; }
}
protected override void Render(HtmlTextWriter writer)
{
// First we will build up the html document,
// the head section and the body section.
writer.Write( @"
<html>
<head>
<title>" + PageTitle + @"</title>
</head>
<body>" );
// Then we allow the base class to render the
// controls contained in the ASPX file.
base.Render( writer );
// Finally we render the final tags to close
// the document.
Writer.Write( @"
</body>
</html>" );
}
}
Let's take a look at a few interesting points illustrated in this base class. First of all, we expose a property called PageTitle that will contain the title for the page. This property's contents are written out in the <title>
section of the page. Also notice the '@' symbol used before the string literals. While not explicitly required for this example, the '@' symbol is a C# token that says the following string literal should not have escape sequences expanded. If we didn't do this, we would have to escape our slash characters. If you are working in VB.NET you don't have to worry about this.
Now we will create an ASPX page that derives from PageBase
. I'll be using code-behind pages, so we have two files. The first file has a file extension .ASPX and contains the markup for the page.
<%@ Page language="c#" Codebehind="SimplePageInheritance.aspx.cs" AutoEventWireup="false"
Inherits="SimplePageInheritance" %>
<form id="SimplePageInheritance" method="post" runat="server">
<h1>This is Page 1</h1>
<p>
This page demonstrates Simple Page Inheritance where the content is rendered
using the base class' Render() method. You cannot use Server Controls that
postback in the base class, they can only be used in the .ASPX page itself.
</p>
</form>
Notice that there is no markup for "standard" content like <html>
, <head>
and <body>
because they will be rendered in the base class. The code-behind class defines the SimplePageInheritance
class referenced in the Page declaration.
public class SimplePageInheritance : PageBase
{
public SimplePageInheritance()
{
PageTitle = "Simple Page Inheritance";
}
}
Pretty cool, eh? This solution solves all of the problems with include-based templates mentioned earlier. For some situations, however, it doesn't solve everything. For those we need Advanced Page Inheritance.
Advanced Page Inheritance
The biggest problem with the Simple Page Inheritance system is that you cannot put any server controls that cause a post-back in the template base class. The reason for this is related to the position of the <form> tag. In Simple Page Inheritance, the <form>
tag is included in the derived ASPX page. Therefore any markup rendered by the base class will be either before or after the ASPX file markup. This might occur if, for example, we wanted a search system included in the template.
There are many different ways we could solve this problem. For this example, we will move the <form>
tag from the ASPX file to the base class. We can't just render the <form> tag as a string literal either, because in ASPX files, the form is actually processed on the server before the markup is sent to the browser. So we have to create a Form object and insert it into the Controls collection of the page.
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
public class AdvancedPageInheritanceBase : System.Web.UI.Page
{
protected override void OnInit(System.EventArgs e)
{
BuildPage( GenerateHtmlForm() );
base.OnInit(e);
}
protected void BuildPage( HtmlForm form )
{
////////////////////////////////////////////////////////
// Build the page and include the generated form
this.Controls.AddAt( 0, new LiteralControl( @"
<html>
<head>
<title>" + PageTitle + @"</title>
</head>
<body>
") );
this.Controls.Add( form );
this.Controls.Add( new LiteralControl( @"
</body>
</html>
"));
}
private HtmlForm GenerateHtmlForm()
{
HtmlForm form = new HtmlForm();
AddSearch(form);
AddControlsFromDerivedPage(form);
return form;
}
private void AddSearch( HtmlForm form )
{
searchBox = new TextBox();
Button searchButton = new Button();
searchButton.Text = "Search";
searchButton.Click +=
new EventHandler( this.OnSearchButtonClicked );
form.Controls.Add( searchBox );
form.Controls.Add( searchButton );
form.Controls.Add( new LiteralControl("<br>") );
}
protected void OnSearchClick( object sender, EventArgs e )
{
// Do the search here
}
private void AddControlsFromDerivedPage(HtmlForm form)
{
int count = this.Controls.Count;
for( int i = 0; i<count; ++i )
{
System.Web.UI.Control ctrl = this.Controls[0];
form.Controls.Add( ctrl );
this.Controls.Remove( ctrl );
}
}
}
Remember that I said there are many ways to solve the <form>
tag placement problem. In this solution we create an HtmlForm
control, populate it with the contents of the ASPX page and then insert it into the template.
This system works very well and allows us to create ASPX pages that are completely independent of the template. Here is the ASPX file:
<%@ Page language="c#" Codebehind="AdvancedPageInheritance.aspx.cs"
AutoEventWireup="false"
Inherits="PageInheritanceSample.AdvancedPageInheritance" %>
<h1>Advanced Page Inheritance</h1>
<p>This demonstrates the Advanced Page Inheritance Technique.</p>
The code-behind file is no different than the one used in the Simple Page Inheritance example. Notice that our ASPX file doesn't have a <form> tag in it.
Performance
I was surprised to find that there is almost no performance difference when using page inheritance. Using Microsoft Application Center Test, I ran page load tests on three different versions of similar page structures:
- The first page used user controls to encapsulate the header, left navigation and footer sections. It did not use any page inheritance.
- The second page had the same look & feel and the same content but used the method outlined in Simple Page Inheritance.
- The third page had a similar look & feel (it included a search box like in the example code) and used the method outlined in Advanced Page Inheritance.
The following table shows the results after loading each page continuously for 5 minutes on my development workstation. As you can see, the differences are minimal.
Page | # of Requests | Avg. Response Time (ms) | Avg. Requests per Sec. |
---|---|---|---|
WebUserControl.aspx | 16,693 | 10.53 | 55.64 |
SimplePageInheritance.aspx | 16,636 | 10.54 | 55.45 |
AdvancedPageInheritance.aspx | 16,965 | 10.20 | 56.55 |
Conclusion
Developing websites and web applications that are easy to maintain has always been a challenge. Over time the techniques used have evolved from almost nothing to modern object-oriented techniques like those presented in this article. These techniques can be used by anyone with a basic understanding of object-oriented programming and will almost certainly help you produce better factored, easier to maintain code.