Introduction
When developing the presentation of a web application, we typically follow a template structure for all the web pages. The template usually contains some sort of company logo and navigation in a header section, and some legal info and contact info in the footer section, etc. The HTML codes for these header and footer section are typically the same throughout the entire site, and may therefore need to be copied and pasted in every single web page. To avoid the HTML code for the header and footer section being replicated all over the place, we use the "include
" directive to include a chunk of HTML code in every page.
Problem with using #Include Directive
There are however some drawbacks with this "include
" approach as most of the web developers, including me, have already experienced:
- We need to remember the exact location in every single ASP page to insert the
include
directives.
- There is no logic to group the header include file and footer include file, and if we ever need to have multiple sets of headers and footers for different sections of the site, such as an admin section, an authenticated section, and a public section, we end up having six different header and footer files sitting around that are totally unrelated to each other.
- We can't really dynamically include different files during runtime.
Alternative Approach using Controls
This simple demonstration illustrates an alternative way of adding a header and footer user control to all web pages in your web application. The general idea is to use a HTTP module to insert the controls programmatically in the right location in the page.
Here's an outline of the steps for implementing the HTTP module:
- Create an HTTP module that will participate in the page execution cycle.
- Register an event handler for the page init event.
- In the page init event handler, look for the "
Form
" control.
- Instantiate the header and footer control and add it to the "
Form
" control.
- Register the HTTP Module in the Web.Config.
Implementation details
Create an HTTP module
To create an HTTP module, we simply write a class that implements the System.Web.IHttpModule
interface. The interface requires two methods to be implemented: Init
and Dispose
. Typically, when implementing a HTTP Module, we will register event handlers in the Init
method so that they get executed when the processing of the HTTP request reaches a certain stage. In our example, we will register the event handler at the stage when the instance of the handler for the HTTP request is ready, namely, the PreRequestHandlerExecute
event.
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
-----------------------------------------
public class HeaderFooterModule : IHttpModule{
public HeaderFooterModule()
{
}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute +=
new EventHandler(context_PreRequestHandlerExecute);
}
private void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
}
public void Dispose()
{
}
}
Register an Event Handler for the Page Init event
When the Request handling reaches the stage of "Pre request handler execute", the handler instance is ready. The handler can be anything that implements IHttpHandler
, this could be a web page or some other Request handlers. Therefore, we need to filter out the HTTP Request directed to a page. To do this, we check the type of the request handler and make sure it's a HTTP page. (Later on, we can add some other additional logic for adding control based on the page type). Once we determine that the handler is a Page
object, we need to register another event handler in its init
event to insert the control in the right location. In a page execution cycle, the init
event is where the page has its controls initiated. This is where we locate the "Form
" control and put additional code to place our own header and footer controls so that it will become available for the rest of the page execution stages. To locate the form
control, we use a simple iteration of the Page
's Controls
collection. The form
control should be within the first few elements in the Page
's Controls
collection, so iterating through them shouldn't be a big problem.
private void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext oContext = ((HttpApplication)sender).Context;
if (oContext.Handler is Page)
{
Page objPage = (Page)oContext.Handler;
objPage.Init += new EventHandler(Page_Init);
}
}
private void Page_Init(object sender, EventArgs e)
{
}
Implement the event handler for the Page init event handler
In the page init event handler, we will need to look for the "Form
" element, instantiate the header and footer controls (using a user control .ascx file), and place the controls in the page. The controls we use for the header and footer are user controls created using Visual Studio .NET. This includes an ascx file and a code behind. You can put other server controls in your user control such as a login button, and put the code that handles the login button click in the code behind of the user control.
private void Page_Init(object sender, EventArgs e)
{
foreach (Control objControl in ((Page)sender).Controls)
{
if (objControl is HtmlForm)
{
HtmlForm objForm = (HtmlForm)objControl;
objForm.Controls.AddAt(0,
objForm.Page.LoadControl("Header.ascx"));
objForm.Controls.Add(objForm.Page.LoadControl("Footer.ascx"));
break;
}
}
}
Registering the module in the web.config file
To register an HTTP module, simply add the following config segment in the system.web
element of the web.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<httpModules>
<add name="HeaderFooterModule"
type="MyNamespace.HeaderFooterModule,MyAssembly"/>
</httpModules>
</system.web>
</configuration>
Variations
For sites with different sections, such as an admin section and a public page section, we might want to use different header and footer design. For this case, one possible scenario is to have these pages in these two different sections implement different tag interface. A tag interface is basically an interface with no method defined in it. It's particularly useful if you simply want to identify is a page is of a certain category. For example, we can replace the Page_Init
handler implementation above with the following code so that it checks for the page type and determines the appropriate header and footer ascx files to use:
private void Page_Init(object sender, EventArgs e)
{
foreach (Control objControl in ((Page)sender).Controls)
{
if (objControl is HtmlForm)
{
HtmlForm objForm = (HtmlForm)objControl;
if (sender is IAdminPage)
{
objForm.Controls.AddAt(0,
objForm.Page.LoadControl("AdminHeader.ascx"));
objForm.Controls.Add(objForm.Page.LoadControl("AdminFooter.ascx"));
}
else
{
objForm.Controls.AddAt(0,objForm.Page.LoadControl("Header.ascx"));
objForm.Controls.Add(objForm.Page.LoadControl("Footer.ascx"));
}
break;
}
}
}
For the code behind class for admin page:
public interface IAdminPage {
}
public class HeaderFooterModule : Page, IAdminPage{
...
}
Special note about programmatically adding control to a page
It should be noted that with this approach, you cannot write code snippets inside the ASP.NET page directly. While this might be a concern to some people, I don't find it too much of an issue because I believe most of the snippets can be replaced by controls.
Conclusion
With this approach of adding header and footer controls using a HTTP Module, we are able to achieve the following:
- Add a uniform header and footer to all pages without modifying anything in the ASP.NET page.
- Can have different sets of header and footer for different sections. All the page has to do is to implement the appropriate tag interface.
- Able to modify the module to put additional logic (such as based on the user credential...) to instantiate and insert different header/footer controls.