MasterPages are arguably the most useful new feature in ASP.NET 2.0. They solve one of the biggest issues with 1.1—providing a clean way of implementing a common look and feel across an application. In fact, they are so slick they end up encouraging some bad practices. Namely using the site’s MasterPage as the site’s base page class. Now, say this three times: master pages are not meant for logic but rather for look & feel.
Now, this is not to say using some sort of base page class is a bad idea. In fact, it is a very good idea. But a base page class still means a class that inherits from System.Web.UI.Page. The challenge becomes that now, with MasterPages and all, one ends up with a bit of a quandary: at the end of the day, this logic needs to get expressed in web controls. And the MasterPage, not the base page class, is directly aware of those controls. But the base page class is not, at least not unless one wants to start a messy game of calling Master.FindControl(). I argue, however, that the base page class need not be directly aware of these controls, but merely certain functions of these controls. A sort of contract, if you will. .NET conveniently has programmatic contracts, they are just known as Interfaces. And they are good.
Have I lost you yet? Ok, lets just dive into scenarios & code samples, eh?
The Scenario
Your project requires a common set of buttons be displayed on all pages. These buttons are to be Add, Edit, Delete and Exit. Now, some pages will not allow the use of one or all of these buttons. And the specifics of what happens when these buttons are clicked needs to be handled by each concrete page.
The Strategy
What we are going to do here is:
- Create an interface, ICommandStrip, which encapsulates the necessary functionality. Namely events for Adding, Deleting, Editing and Exiting. As well as a set of Boolean values for enabling or disabling commands.
- Create another interface, ICommandStripContainer, for the containing control to implement. Its sole purpose in life is to pass a reference to the concrete ICommandStrip
- A User Control which implements ICommandStrip
- An abstract base MasterPage class which implements ICommandStripContainer and implements some basic plumbing.
- A concrete MasterPage class which implements the abstract class in #4.
- A base Page class which provides a reference to our ICommandStripContainer and handles some basic plumbing for said container.
- An actual page that communicates with the command strip to prove it all works swimmingly.
So, first, ICommandStrip:
/// <summary>
/// Command strip interface contract.
/// </summary>
public interface ICommandStrip
{
/// <summary>
/// Fired when the Add command is enacted.
/// </summary>
event EventHandler AddClicked;
/// <summary>
/// Fired when the Edit command is enacted.
/// </summary>
event EventHandler EditClicked;
/// <summary>
/// Fired when the Delete command is enacted.
/// </summary>
event EventHandler DeleteClicked;
/// <summary>
/// Fired when the Exit command is enacted.
/// </summary>
event EventHandler ExitClicked;
/// <summary>
/// Is the Add commmand enabled?
/// </summary>
bool IsAddable { get; set; }
/// <summary>
/// Is the Delete command enabled?
/// </summary>
bool IsDeletable { get; set; }
/// <summary>
/// Is the Edit command enabled?
/// </summary>
bool IsEditable { get; set; }
/// <summary>
/// Is the Exit command enabled?
/// </summary>
bool IsExitable { get; set;}
}
Now ICommandStripContainer:
/// <summary>
/// Container interface for our Command Strip
/// </summary>
public interface ICommandStripContainer
{
/// <summary>
/// Gets a reference to the CommandStrip
/// </summary>
ICommandStrip CommandStrip { get;}
}
With me so far? Now we are starting to get to the good part. Lets see the user control:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="ButtonCommandStrip.ascx.cs" Inherits="ButtonCommandStrip" %>
<div style="background-color: #e2e2e2; padding:5px; text-align: right;">
<asp:Button runat="Server" ID="AddButton" OnClick="AddButtonClicked" Text="Add" />
<asp:Button runat="Server" ID="EditButton" OnClick="EditButtonClicked" Text="Edit" />
<asp:Button runat="Server" ID="DeleteButton" OnClick="DeleteButtonClicked" Text="Delete" />
<asp:Button runat="Server" ID="ExitButton" OnClick="ExitButtonClicked" Text="Exit" />
</div>
And Codebehind:
public partial class ButtonCommandStrip : System.Web.UI.UserControl, ICommandStrip
{
public ButtonCommandStrip()
{
PreRender += new EventHandler(bindButtonAbilities);
}
private void bindButtonAbilities(object sender, EventArgs e)
{
AddButton.Enabled = IsAddable;
EditButton.Enabled = IsEditable;
DeleteButton.Enabled = IsDeletable;
ExitButton.Enabled = IsExitable;
}
#region ICommandStrip Members
private event EventHandler addClicked;
public event EventHandler AddClicked
{
add { addClicked += value; }
remove { addClicked -= value; }
}
private event EventHandler editClicked;
public event EventHandler EditClicked
{
add { editClicked += value; }
remove { editClicked -= value; }
}
private event EventHandler deleteClicked;
public event EventHandler DeleteClicked
{
add { deleteClicked += value; }
remove { deleteClicked -= value; }
}
private event EventHandler exitClicked;
public event EventHandler ExitClicked
{
add { exitClicked += value; }
remove { deleteClicked -= value; }
}
public bool IsAddable
{
get
{
if (ViewState["IsAddable"]==null)
{
return true;
}
return (bool)ViewState["IsAddable"];
}
set
{
ViewState["IsAddable"] = value;
}
}
public bool IsDeletable
{
get
{
if (ViewState["IsDeletable"] == null)
{
return true;
}
return (bool)ViewState["IsDeletable"];
}
set
{
ViewState["IsDeletable"] = value;
}
}
public bool IsEditable
{
get
{
if (ViewState["IsEditable"] == null)
{
return true;
}
return (bool)ViewState["IsEditable"];
}
set
{
ViewState["IsEditable"] = value;
}
}
public bool IsExitable
{
get
{
if (ViewState["IsExitable"] == null)
{
return true;
}
return (bool)ViewState["IsExitable"];
}
set
{
ViewState["IsExitable"] = value;
}
}
#endregion
protected void AddButtonClicked(object sender, EventArgs e)
{
if (addClicked != null)
{
addClicked(this, e);
}
}
protected void EditButtonClicked(object sender, EventArgs e)
{
if (editClicked != null)
{
editClicked(this, e);
}
}
protected void DeleteButtonClicked(object sender, EventArgs e)
{
if (deleteClicked != null)
{
deleteClicked(this, e);
}
}
protected void ExitButtonClicked(object sender, EventArgs e)
{
if (exitClicked != null)
{
exitClicked(this, e);
}
}
}
Nothing too fancy there. It just implements ICommandStrip, as well as some plumbing to enable or disable buttons as appropriate.
Now back to App_Code for the master page base class:
public abstract class SiteMasterPageBase : MasterPage, ICommandStripContainer
{
/// <summary>
/// Internal mapping property to get a reference to the concrete ICommandStrip control
/// </summary>
protected abstract ICommandStrip CommandStripControl { get; }
/// <summary>
/// Returns a reference to the CommandStripControl for use by the SitePage.
/// </summary>
public ICommandStrip CommandStrip
{
get { return CommandStripControl; }
}
}
The only voodoo going on here is that it is an abstract class. Why, you ask? Pretty simple really—you can have multiple master pages for a site. And if you maintain the same back-end interfaces, you can switch between them on the fly. Or at least in the PreInit event. But then the trick becomes wiring the master pages up to the controls they must manipulate; hence the abstract CommandStripControl property. Its purpose is to act as a bridge between the concrete MasterPage class and the normal interface.
Just to prove it is easy, lets have a looksee at our site’s master page:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inherits="Site" %>
<%@ Register Src="ButtonCommandStrip.ascx" TagName="ButtonCommandStrip" TagPrefix="SpUc" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>SpBlog Test Page</title>
</head>
<body>
<form id="form1" runat="server">
<h1>Master Page Command Strip Demo</h1>
<SpUc:ButtonCommandStrip id="CommandStripCtl" runat="server" />
<asp:contentplaceholder id="MainContentPlaceHolder" runat="server" />
</form>
</body>
</html>
And the codebehind, which does nothing save tell the MasterPage how to find the ICommandStrip:
public partial class Site : SiteMasterPageBase
{
protected override ICommandStrip CommandStripControl
{
get { return this.CommandStripCtl; }
}
}
Working hand-in-hand (or Interface-to-Interface, lol) with this SiteMasterPageBase class is the SitePageBase class:
public class SitePageBase : Page
{
public SitePageBase()
{
//Wire up the command strip.
//Note that Init is the earliest we can hook up the events.
//They are not avaliable before the MasterPage has loaded its controls.
Init+=new EventHandler(BindCommandStrip);
}
/// <summary>
/// Gets a reference to the Master CommandStripContainer
/// </summary>
protected ICommandStripContainer CommandStripContainer
{
get
{
return (ICommandStripContainer)Master;
}
}
#region commandstrip utility
/// <summary>
/// Binds the command strip events
/// </summary>
/// <param name="sender">sender</param>
/// <param name="e">empty event args</param>
private void BindCommandStrip(object sender, EventArgs e)
{
if (CommandStripContainer != null)
{
CommandStripContainer.CommandStrip.AddClicked += new EventHandler(AddClicked);
CommandStripContainer.CommandStrip.DeleteClicked += new EventHandler(DeleteClicked);
CommandStripContainer.CommandStrip.EditClicked += new EventHandler(EditClicked);
CommandStripContainer.CommandStrip.ExitClicked += new EventHandler(ExitClicked);
}
}
#region virtual event handler stubs
protected virtual void ExitClicked(object sender, EventArgs e)
{}
protected virtual void EditClicked(object sender, EventArgs e)
{}
protected virtual void DeleteClicked(object sender, EventArgs e)
{}
protected virtual void AddClicked(object sender, EventArgs e)
{ }
#endregion
#endregion
}
Nothing too fancy here either. Some code to wire up the events, and some virtual stub methods for easy overriding later. Do note the CommandStripContainer method—it is the bridge back to the MasterPage’s ICommandStrip control.
Now for the good part—a simple example of a concrete implementation. First the ASPX file:
<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="CommandStripPage.aspx.cs" Inherits="CommandStripPage" Title="Untitled Page" %>
<asp:Content ID="MainContentPlaceHolder" ContentPlaceHolderID="MainContentPlaceHolder" Runat="Server">
<p>Last Command: <asp:Label runat="Server" ID="WhatWasClicked" Text="Nothing Yet" ForeColor="red" Font-Bold="true" /></p>
<asp:CheckBox ID="AddableCheckbox" Text="Addable" runat="Server" Checked="true" /><br />
<asp:CheckBox ID="EditableCheckbox" Text="Editable" runat="Server" Checked="true" /><br />
<asp:CheckBox ID="DeletableCheckbox" Text="Deletable" runat="Server" Checked="true" /><br />
<asp:CheckBox ID="ExitableCheckbox" Text="Exitable" runat="Server" Checked="true" /><br />
<asp:Button runat="Server" ID="ApplyChangesButton" Text="Apply Ability Changes" OnClick="ApplyChanges" />
</asp:Content>
And now the C# bits:
public partial class CommandStripPage : SitePageBase
{
protected override void EditClicked(object sender, EventArgs e)
{
WhatWasClicked.Text = "EDIT was clicked!";
}
protected override void DeleteClicked(object sender, EventArgs e)
{
WhatWasClicked.Text = "DELETE was clicked!";
}
protected override void AddClicked(object sender, EventArgs e)
{
WhatWasClicked.Text = "ADD was clicked!";
}
protected override void ExitClicked(object sender, EventArgs e)
{
WhatWasClicked.Text = "EXIT was clicked!";
}
protected void ApplyChanges(object sender, EventArgs e)
{
CommandStripContainer.CommandStrip.IsAddable = AddableCheckbox.Checked;
CommandStripContainer.CommandStrip.IsDeletable = DeletableCheckbox.Checked;
CommandStripContainer.CommandStrip.IsEditable = EditableCheckbox.Checked;
CommandStripContainer.CommandStrip.IsExitable = ExitableCheckbox.Checked;
}
}
As you can see, there is nothing particularly fancy here either. Just handling some events like a normal ASP.NET page. But the trick is that all those events get wired up by the SiteMasterPageBase & SitePageBase without this page caring a bit. And when you need to swap out control structures, you will only need to swap them out in one place because your pages are only sharing defined interfaces rather than classes.
Enjoy, happy coding and don’t forget to kick it if you like it.
[A few editorial notes & shout outz: the inspiration for this demo came out of this thread on SitePoint’s ASP.NET forums. And I did borrow most of the interface from Pufa’s post. His enum is a bit more elegant than my bools, but I got lazy.]







This is an excellent post. You broke it down very well. Thanks.
August 31st, 2006 at 4:44 pm
Good article .
You have been kicked
September 5th, 2006 at 8:57 pm
Excellent! Great explanation and the code works perfectly.
October 1st, 2006 at 9:20 am
I was trying to solve the problem of having a page that “implements” a master page and, in the other hand, inherits from a custom base page class. The dificulty was that we can’t access properties in the master page through the custom base page class. Well, your article really opened may mind. Using an interface to perform this access to the master page works fine.
To do this i create a master base page that implements ITeste named BaseMaster (in VB) and i made the property implemented from the interface MustOverride:
Public MustInherit Class BaseMaster
Inherits MasterPage
Implements ITeste
#Region “ITeste Implementation”
MustOverride Property MenuHiddenz() As Boolean Implements ITeste.MenuHiddenz
#End Region
Public Interface ITeste
Property MenuHiddenz() As Boolean
End Interface
Then in the page that “implements” BaseMaster:
Public Overrides Property MenuHiddenz() As Boolean
Get
Return Not Me.MainMenu.Visible
End Get
Set(ByVal value As Boolean)
Me.MainMenu.Visible = Not value
End Set
End Property
Finally, in the custom base page class i can access the master page:
DirectCast(Me.Master, ITeste).MenuHiddenz = True
or in C#
(ITeste)this.Master.MenuHiddenz = true;
Thank’s a lot Wyatt
October 3rd, 2006 at 9:14 pm
Glad you found it useful Rui. I actually came up with this pattern while trying to solve a similar problem. Now, there is probably an official name, but official design patterns never were my thing.
October 3rd, 2006 at 11:29 pm
When I right click my web project I don’t have the option to add an App_code folder. Its missing and I can’t add it :-(
All I can see is
App_globalresources
App_localresources
App_data
App_browsers
Theme
Argh, I think your code sample above will work for me, but I’m stuck on step 1.
October 23rd, 2006 at 9:45 pm
I should add that I migrated this from a vs 2003 web app. I suppose that has something to do with the missing option for app_code
October 23rd, 2006 at 9:49 pm
Sounds like it converted into a web application project. Which is no matter; just put the stuff from app_code in a separate class library and reference it.
Note the library will need to reference System.Web.
October 23rd, 2006 at 10:00 pm
Thanks for the post. I was struggling with how to reconcile master pages with a custom page base… this was the answer I was looking for!
December 8th, 2006 at 4:39 am
(sorry the code in my first post wasn’t formatted properly)
I agree that interfaces are good, but that solution seems overly complicated to me.
The base page does not need to be involved at all and the button.click event handlers should be in the code behind for the master page.
If I were going to use interfaces to solve the example here’s what I would do:
Interface Definition
Public Interface IAddable
Sub Add()
End Interface
Public Interface IEditable
Sub Edit()
End Interface
Public Interface IDeletable
Sub Delete()
End Interface
Public Interface IExitable
Sub [Exit]()
End Interface
Public Interface ICommandButtonContainer
ReadOnly Property AddButton() As Button
ReadOnly Property EditButton() As Button
ReadOnly Property DeleteButton() As Button
ReadOnly Property ExitButton() As Button
End Interface
Code Behind for Master Page
Partial Class MasterPage
Inherits System.Web.UI.MasterPage
Implements ICommandButtonContainer
Public ReadOnly Property AddButton() As System.Web.UI.WebControls.Button Implements ICommandButtonContainer.AddButton
Get
‘return a reference to the button
Return btnAdd
End Get
End Property
Public ReadOnly Property DeleteButton() As System.Web.UI.WebControls.Button Implements ICommandButtonContainer.DeleteButton
Get
Return btnDelete
End Get
End Property
Public ReadOnly Property EditButton() As System.Web.UI.WebControls.Button Implements ICommandButtonContainer.EditButton
Get
Return btnEdit
End Get
End Property
Public ReadOnly Property ExitButton() As System.Web.UI.WebControls.Button Implements ICommandButtonContainer.ExitButton
Get
Return btnExit
End Get
End Property
Protected Sub btnAdd_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAdd.Click
‘if the child page implements IAddable then call it’s Add() method
If TypeOf Page Is IAddable Then
CType(Page, IAddable).Add()
End If
End Sub
Protected Sub btnEdit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnEdit.Click
If TypeOf Page Is IEditable Then
CType(Page, IEditable).Edit()
End If
End Sub
Protected Sub btnDelete_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnDelete.Click
If TypeOf Page Is IDeletable Then
CType(Page, IDeletable).Delete()
End If
End Sub
Protected Sub btnExit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnExit.Click
If TypeOf Page Is IExitable Then
CType(Page, IExitable).Exit()
End If
End Sub
End Class
Code behind for Page
Partial Class _Default
Inherits Web.UI.Page
Implements IAddable, IDeletable, IEditable, IExitable
Public Sub Add() Implements IAddable.Add
WhatWasClicked.Text = “Add”
End Sub
Public Sub Delete() Implements IDeletable.Delete
WhatWasClicked.Text = “Delete”
End Sub
Public Sub Edit() Implements IEditable.Edit
WhatWasClicked.Text = “Edit”
End Sub
Public Sub [Exit]() Implements IExitable.Exit
WhatWasClicked.Text = “Exit”
End Sub
Protected Sub btnApplyChanges_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnApplyChanges.Click
If TypeOf Master Is ICommandButtonContainer Then
Dim container As ICommandButtonContainer = Master
container.AddButton.Enabled = AddableCheckbox.Checked
container.DeleteButton.Enabled = DeletableCheckbox.Checked
container.EditButton.Enabled = EditableCheckbox.Checked
container.ExitButton.Enabled = ExitableCheckbox.Checked
End If
End Sub
End Class
March 29th, 2008 at 4:44 am