Article Author: Jeff Hewitt
Introduction
When I first started working professionally, I began to see the need to create web sites that were simple to maintain and develop and therefore extremely scalable. I also wanted to be able to easily reuse my project components in later development endeavors. I discovered that the ASP.NET user control was the perfect solution.by virtue of its ability to break down large complex projects into clear, well defined smaller projects. Each piece can be developed, unit tested and integration tested while remaining essentially segregated from the rest of the page.
In the ASP.NET user control I saw the ability to break down a large complex project into clear well defined smaller projects. The different pieces could then be automatically loaded into the larger piece. Working this way, each piece could easily be developed, unit tested, and integration tested. The loading mechanism between the user controls and the main application never had to be changed once it was in place, and therefore, changes to the different pieces would essentially be segregated. Probably best of all, each piece could just as easily be embedded into a different location in the same project or into a completely new project. In addition, the uncoupled nature of user controls and the fact that they can be loaded dynamically allows for excellent content management.
However, I quickly discovered that user controls embedded into a web page could not effectively communicate with the page they were embedded in or other user controls. Like any other object, user controls are in essence autonomous. I turned to the web for answers. Surely someone had done what I was trying to do and could show me how. I found bits and pieces here and there about event bubbling and using delegates, but these seemed to be too complicated and even limited in certain situation. All in all there were no resources concise enough to solve my problem.
I began a side project and decided that I was going to make the application using nothing but ASP.NET user controls to display my content. Over the course of the project I designed a class library that can be referenced by an ASP.NET project to effectively create template pages that automatically load user controls and facilitate communication to and from those controls. It works beautifully and has been nothing like other resources suggested. On the contrary, it was surprisingly simple once I had the correct framework in place. In this article I will walk through that framework and share a basic version of the class library I developed using a simple customer management application as an example.
System Requirements
To run the code for this sample you should have
- A web server that supports ASPX pages running on Windows 200x, XP, or NT 4.0
- The .NET Framework version 1.1 or later
- Visual Studio.NET 2003 or later
- MSDE or SQL Server with the Northwind database installed
Installing and Compiling the Sample Code
The sample download for this article contains a VS.NET solution written in VB containing the following projects:
- UserControlExamples . This project is an internal website that a fictional company uses to manage their customer database. The content in this sample application is managed using the UserControlExamplesWiring class library.
- UserControlExamplesWiring .This project contains the class library described in this article.
To install the sample, you should first create an IIS virtual directory called UserControlExamples , which points to the folder UserControlExamples . The UserControlExamples.sln solution file should remain in the directory containing the folder, UserControlExamplesWiring .
You will also need to update the web.config file to match your environment for the Northwind Database connection.
Once the solution is installed, to open the solution, open the UserControlExamples.sln solution file in Visual Studio.NET.
The UserControlExamplesWiring Class Library
The UserControlExamplesWiring class library provides base classes for System.Web.UI.Page controls as well as System.Web.UI.UserControls that facilitate the following functionality in the UserControlExamples web application:
- Mapping template content areas
- Loading user controls into those content areas
- Facilitating communication to and from controls
Template Pages
Before I dive into the details of the code, I would like to give you a quick 10,000 foot overview of what a template page is and how one is used.
A template page is an ASPX page that extends the PageContentWiring class to assume functionality that can be used to define and load content areas on the page.
First content areas are defined using the base class’s addContentMapping() method. Then, during the Load event, the base class passes a unique content mapping ID to the contentDefinitionReader class and receives an array of contentDefinitions which map user controls to the defined content areas.
In the download of the source code, the contentDefinitionReader class is programmed to read content definitions from an XML file named contentMapping.xml (which can be found in the UserControlExampls root project directory). The contentDefinitionReader could be changed to read content definitions from any source you want.
The following conceptualized object model illustrates what is being described in the three paragraphs above. Refer to it as a reference on how templates work as we move through the details of the code.

Figure 1. Conceptualized template object model
Defining Content Areas
A content area can be a <DIV> , <TD> , <SPAN> or <P> which defines a region in a template page’s HTML that will have content dynamically loaded into it. Each content area must have the runat=server attribute and a unique ID attribute.
In the Customer Management System example application, main.aspx is a template page. In the image below, you can see what areas have been defined as content areas in the template.

Figure 2. Content areas defined on the main.aspx page
Both the navigation and content content areas shown in Figure 2 above are table cells with runat=server and unique IDs specified in the HTML (seen below):
<TR>
<TD class="navBar" id="pnlNavigation" runat="server">
<div style="WIDTH: 136px; HEIGHT: 19px"></div>
</TD>
<TD id="pnlContent" width="100%" runat="server" vAlign="top"></TD>
</TR>
During a template page’s Init event, these areas are defined as content areas using the addContentMapping() method of the PageContentWiring base class as seen in the code below.
Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
Me.addContentMapping("navigation", Me.pnlNavigation)
Me.addContentMapping("content", Me.pnlContent)
End Sub
The addContentMapping() method takes two parameters:
- definitionKey – All content definitions returned from the ContentDefinitionReader class for this page with a matching key will load their content into this content area
- contentPanel – The content panel defined in the HTML
Each template page has a private instance of the ContentMappingManager class where the addContentMapping() method above, stores a reference to the content area passed to it during the template page’s Init event.
Loading Content Areas
When a template page is called it is passed a query string parameter named p . The value of this parameter is the template’s content wiring ID. The content wiring ID is used here to determine what content will be loaded into the content areas.
During the page’s Load event, the content wiring ID is passed to the contentDefinitionReader.getContent() function as seen below.
Public Shared Function getContent(ByVal contentWiringID As String)
As ContentDefinition()
‘===========================================================================
‘ This method reads content definitions from an XML file
‘ (contentMapping.XML).
‘ However, it can be set to read content definitions from any source.
‘===========================================================================
Dim a As New ArrayList
Dim virtualPath As String =
System.Configuration.ConfigurationSettings.AppSettings("XMLContentFile")
Dim r As New
XmlTextReader(System.Web.HttpContext.Current.Server.MapPath(virtualPath))
Dim listen As Boolean = False
Try
While r.Read
If r.Name.ToUpper.Trim = pageName.ToUpper.Trim Then
If listen Then Exit While
listen = True
End If
If listen Then
If r.Name.ToUpper.Trim = "ADD" Then a.Add(New
ContentDefinition(r.Item("contentWiringID"), r.Item("mappingKey"),
r.Item("userControl")))
End If
End While
Finally
r.Close()
End Try
Return a.ToArray(GetType(ContentDefinition))
End Function
As you can see, currently, the ContentDefinitionReader.getContent() function reads the content definitions from an xml file called contentMapping.xml . This function can, however, be changed to read definitions from any source. The following XML snippet is from the contentMapping.xml file and defines content for the home page of our Customer Management System example application.
<home>
<Add contentWiringID="mainNav" mappingKey="navigation"
userControl="mainNavigation.ascx"/>
<Add contentWiringID="mainContent" mappingKey="content"
userControl="homeContent.ascx"/>
</home>
Each <Add> node in the XML above outlines a content definition.
The contentDefinitionReader.getContent() function returns an array of content definitions each containing the three pieces of information defined in the XML <Add> node for that page:
- contentWiringID – This will be the content wiring ID of the user control being loaded by the definition. Because this ID is used in sending and receiving messages, this ID must be unique to other controls being loaded on the page (including the page’s content wiring ID)
- mappingKey – The user control defined by this definition will be loaded into the content area with the matching key
- userControl – The file name of the user control ASCX file that this definition defines

Figure 3. ContentDefinition class diagram
The user controls defined by these content definitions are loaded one by one in the order they are returned. So, if you are loading two different navigation controls, as in the customer editor page of the Customer Management System example application, the user controls will be stacked one on the other in the order they are returned in.
Also, before the first user control is loaded into a content area, the content area is cleared of all controls.
Communication Between Controls
Messages are used to tell a control that an event has occurred and/or pass parameters between controls. Messages can be sent from any project that references the UserControlExamplesWiring library.via the MessageSwitch.sendMessage() method, but only registered controls that inherit the UserControlContentWiring class or the PageContentWiring class can receive messages.
How Controls are Registered to Receive Messages
During a controls load event, a page or a user control that inherits from its respective content wiring base class, registers itself with the messageSwitch class’ messageRegistry object. This way, a reference to a control along with a corresponding content wiring ID can be found when sending messages using the MessageSwitch class. The code below is called from the Load event handler for the PageContentWiring class to register it with the MessageSwitch object’s message registry.
messageSwitch.messageRegistry.add(Me._contentWiringID, Me)
This way, each control currently loaded (pages and user controls) can be located when sending messages. Also, messages without loaded recipients can be dealt with as I’ll discuss later.
Sending Messages
Messages are encapsulated in the ContentMessage class which manages the following information:
- forWho – The content wiring ID of the control the message is intended for
- content – The string content of the message
- args – An array of objects that accompany the content as arguments

Figure 4. ContentMessage class diagram
Messages can be sent by any control that has access to the MessageSwitch.sendMessage() method. The code below is an example which sends a message to userControlNumber1 telling it to UPDATE_CONTENT .
Dim message As ContentMessage = New ContentMessage( "userControlNumber1", "UPDATE_CONTENT")
messageSwitch.sendMessage(message)
The message is sent to the messageSwitch.sendMessage() method seen below.
Public Shared Sub sendMessage(ByVal message As ContentMessage)
Dim contentObject As IContentWiring =
_registry.contentObject(message.forWho)
If contentObject Is Nothing Then
PendingContentMessageCollection.add(message)
Else
contentObject.acceptUserControlCommunication(message.content,
message.args)
End If
End Sub
Once a message has been sent to the MessageSwitch class, there are two scenarios for how the message will be handled.
First, the MessageSwitch class checks its registry for the receiving content wiring ID. If it finds the receiving control it sends the message right away by loading the control into the IContentWiring interface. The interface exposes one method, acceptUserControlCommunication() . This method takes two arguments, the message contents and the arguments if any.
In the second scenario, the MessageSwitch class checks its registry and doesn’t find the receiving content wiring ID. So, it saves the message to the PendingContentMessageCollection object. This second scenario may seem odd at first glance. You may think that if the control hasn’t registered, the method could assume that the control is not loaded and that the message might just be discarded. However, later I’ll describe how both of these scenarios are necessary for the Customer Management System example application.
Receiving Messages
For each of the two scenarios in the sending messages section above, there is a scenario for reading messages.
Scenario One: Sending or Discarding
In scenario one above, the control receives the message in the base class through its acceptUserControlCommunication() method seen below.
Public Sub acceptUserControlCommunication(ByVal messageContent As String, ByVal args() As Object) Implements IContentWiring.acceptUserControlCommunication Me.handleContentCommunication(messageContent, args)
End Sub
As you can see, this method simply forwards the message on to the handleContentCommunication() method.
Overridable Sub handleContentCommunication(ByVal messageContent As String, ByVal args() As Object)
End Sub
This method does nothing here and is meant to be overridden in the extending class. The following code is the overridden handleContentCommunication() from the editCustomer.ascx user control. It handles messages for manipulating the customer record being edited.
Overrides Sub handleContentCommunication(ByVal messageContent As String,
ByVal args() As Object)
Me.lblMessage.Visible = False
Select Case messageContent
Case "LOAD_RECORD"
Me.loadRecord(args(0))
Case "UPDATE_CURRENT"
Me.updateCurrentRecord()
Case "DELETE_CURRENT"
Me.deleteCurrentRecord()
End Select
End Sub
Scenario Two: Storing the Message
In scenario two, the MessageSwitch object could not find the receiving content wiring ID in its registry so it stores the message in the PendingContentMessageCollection object. During the PreRender event, each control that inherits from the PageContentWiring class or the UserControlContentWiring class will query the PendingContentMessageCollection object for any messages that were sent to it before it registered with the MessageSwitch class’s registry. The following code is from the UserControlContentWiring class and shows how the PendingContentMessageCollection is queried for any pending messages. The code in the PageContentWiring class is similar, but also contains some code for analyzing content areas.
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.PreRender
If PendingContentMessageCollection.hasMessages(Me.ID) Then
For Each message As ContentMessage In
PendingContentMessageCollection.myMessages(Me.ID)
Me.handleContentCommunication(message.content, message.args)
Next
End If
End Sub
As you can see, the method first checks to see if any messages exist for the control. If they do, it queries the PendingContentMessageCollection object using its content wiring ID (In the case of a user control, this is its ID) in return for an array of contentMessage objects. Finally, the messages are sent to the controls handleContentCommunication() method one by one in the order they were sent.
Messages in the Customer Management System Example Application
In the previous section I explained the two different scenarios for sending and receiving messages between controls. In this section, I’ll walk through the scenarios in detail as they pertain to the example application. This section doesn’t contain any new information, but is intended to help clear up any uncertainty by showing the UserControlExamplesWiring class implemented into a working application.
Sending Messages in the Example Application
From the Customer Management System home page, select Customer Database from the main navigation bar on the left hand side to load the customer database page. When you select Edit for one of the customers in the data grid, the following event in the CustomerDB.ascx user control catches the event.
Private Sub dgCommand_handler(ByVal sender As Object, ByVal e As
DataGridCommandEventArgs) Handles DataGrid2.ItemCommand
Select Case e.CommandName.Trim.ToUpper
Case "EDIT"
Dim args(1) As String
args(0) = e.Item.Cells(1).Text
messageSwitch.sendMessage(New ContentMessage("editorContent",
"LOAD_RECORD", args))
contentLoader.loadContent("editor")
End Select
End Sub
First, the event handler makes sure that the event was raised by an edit command. Then, it creates an array of strings with a length of one. It sets the first slot in the array to the primary key of the record to be edited and sends it to the editorContent control along with the message LOAD_RECORD . Finally the event handler loads the editor template by calling the ContentLoader.loadContent() method which simply loads main.aspx passing it a value for p as seen below.
Public Shared Sub loadContent(ByVal mappingKey As String)
System.Web.HttpContext.Current.Server.Transfer("main.aspx?p=" &
mappingKey)
End Sub
When the message switch receives the message, it checks its registry and finds that there is not a control registered with a content wiring ID of editorContent . So, it saves the message to the PendingContentMessageCollection object. Why couldn’t it find the editorContent control? Because the editor content control is loaded when the template is passed editor which happens one line after the message is sent.
When the template is loaded, it loads the content dictated by the editor key passed in the query string. Into the content content area, the page loads the editCustomer.ascx user control and gives it the content wiring ID editorContent as specified in the content definition. During the editCustomer user control’s PreRender event, the control retrieves the LOAD_RECORD message sent on the previous page and sends it to the handleContentCommunication() method to which loads the record for editing as seen below.
Overrides Sub handleContentCommunication(ByVal messageContent As String,
ByVal args() As Object)
Me.lblMessage.Visible = False
Select
Case messageContent
Case "LOAD_RECORD"
Me.loadRecord(args(0))
Case "UPDATE_CURRENT"
Me.updateCurrentRecord()
Case "DELETE_CURRENT"
Me.deleteCurrentRecord()
End Select
End Sub
Notice that the editor key has three user controls associated with it in the contentMapping.xml file. Two of these controls are to be loaded into the navigation content area so that this page has the main navigation and an edit menu. The edit menu has one button for updating the record and another for deleting the record. The code behind the two buttons is as follows.
Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnDelete.Click
messageSwitch.sendMessage(New ContentMessage("editorContent",
"DELETE_CURRENT"))
End Sub
Private Sub btnUpdate_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnUpdate.Click
messageSwitch.sendMessage(New ContentMessage("editorContent",
"UPDATE_CURRENT"))
End Sub
Each button sends a message to the editorContent control telling it to either delete or update its current record. Because the editorContent control is loaded and has registered with the messageSwitch class’s messageRegistry object, the message is sent immediately to the control via the handleContentCommunication() method. The editCustomer.ascx user control also processes those messages in the handleContentCommunication() code listing above.
Possible Improvements to the Code
Although the UserControlExamplesWiring class library comes downloaded with quite a bit of functionality, it could be modified to fit a broader range of scenarios and applications. In particular logic could be added to make the messaging system more robust. For example, there could be logic for expiring messages if they are never collected or even for storing messages so they are ready to be delivered on certain application events. Or, it may be necessary to read messages from the PendingContentMessagesCollection in an event earlier than PreRender .
Because pages can be templated and the loading of user controls can be parameterized, the logic may be a spring board for a content management application where users can manage content through a web UI.
In the end, if you like the UserControlExamplesWiring class library enough to use it, your bound to find areas that could be improved to better server your needs and account for many more scenarios than those presented in the UserControlExamples web application provided.
Conclusion
ASP.NET user controls have the potential to break up complex problems into smaller more manageable pieces. Their uncoupled nature allows them to remain virtually segregated from the rest of the page they are embedded in. However, this autonomous nature is also the reason why the potential of the user control has been severely under appreciated. In this article, I have shown how a web applications content can be templetised using user controls and how those controls can communicate with one another and the pages they are embedded into.

