Business Problem
There is a listing of clubs, and the user base wants to submit requests for a new club to be added to the list. In the first view, clubs are listed in a grid view control. The user clicks an add club link to submit a club using the form view control. The view changes to allow a user submission, and the list of clubs is hidden from view. The form view control is contained in a generic web user control. When the user selects Submit or Cancel, a message must be sent to the page containing the control. The information contained in the message is used to display a message indicating a successful or cancelled submission. When the user has completed or cancelled the submission, the view changes back to the listing of the clubs. The appropriate message is displayed, and the submission form is hidden from view.
Illustrating the Use Case
This is how the application starts with the grid view populated. The user clicks the Add New Club link to submit a new club.
The user enters the club information and chooses to submit or cancel the entry. The page, which is the container of the web user control, receives a message indicating the user's choice.
This demonstrates that the user chose to submit the club and the form view user control sent a message that bubbled up the the containing page. The page determines the submission was a success and displays an acknowledgement.
When the user chooses to cancel the submission, the web user control that contains the form view sends a message to the page containing the control. Here, the user chose to cancel the club submission.
Background
The challenge and solution: To implement this business process, the developer must leverage event bubbling. Using event bubbling, a message is sent from the web user control back to the club listing page. The information in the message is used to change the state of the page and list an acknowledgement of the submission. Unfortunately, the documentation on the web describing how to implement this process is scarce and sketchy at best. This article illustrates to the developer the details of how to implement this process.
Event bubbling in a nutshell: An event bubble simply contains a message that is sent from one object to another. The message is contained in a class that inherits from EventArgs
. EventArgs
is a system base class that contains event data. This, of course, is a simplification of the concept.
To illustrate, the page class contains both the grid view and the custom web user control. The web user control is a container for the form view control. The form view control contains the Submit and Cancel links.
What we want is to send a message from the Submit or Cancel links up through the control hierarchy to the Club List page container. The message is said to bubble up to the page container. There, an event handler interprets the message, and displays the success or cancellation message to the user.
That is about as deep as this article is going to get into the event bubbling theory. On to composing the code.
Key Elements
The UserControlEventArgs
class has two parts. The class declaration and the declaration of the event delegate:
UserControlEventArgs Class |
Instance Variable(s) |
InsertComplete | This flag indicates a successful insertion of a new record or a cancellation of the submission. |
The event delegate:
Event Delegate |
ClubInsertedEventHandler | Accepts two arguments. The first is the sender of the type object. The second is the UserControlEventArgs used to pass the message in the bubble. |
The WebUserControlAddUser
class requires an instance variable for the event handler.
WebUserControlAddUser |
Instance Variable |
public event ClubInsertedEventHandler ClubInsertComplete; | Declare this variable to be an event of type ClubInserted EventHandler . This is used to raise the event that will bubble up to the ClubList page.
|
Firing the Event |
UserControlEventArgs args = new UserControlEventArgs(true); | Declare the args variable that will be passed into the event bubble. |
ClubInsertComplete(this, args); | Raise the event. |
Finally, the ClubList page consumes the event.
Club List Page |
Click Event (ASPX) |
OnClubInsertComplete="ClubInsertComplete" | The web user control WebUserControlAddUser invokes the method ClubInsertComplete .
|
Handling the Web User Control Event |
ClubInsertComplete(object sender, UserControlEventArgs e) | "e " exposes the InsertComplete variable that is passed as an argument into the event handler. |
Composing the Code
Key Concepts
The UserControlEventArgs
class is used to pass the message from the web user control to the default web page that contains the user control. A delegate called the ClubInsertedEventHandler
is used to declare the “args
” delegate type for the event that is raised in the user control. The event is called “ClubInsertComplete
”.
The delegate event declaration resides in the UserControlEventArgs
class. Note that the declaration is external to the class. It is a mistake to include the delegate within the body of the UserControlEventArgs
class, and will cause the event to be hidden from the default page which is the container for the web user control.
When a record has been inserted into the data table, the args
value is instantiated and passed into the “ClubInsertComplete
” event as an argument. Note that the delegate for the event follows the Microsoft pattern for declaring events.
When the “ClubInsertComplete
” event is raised, the event bubbles up to the default page that is a container for the “WebUserControlAddUser
”, and there it is handled. VS2008 intellisense does not show the event in the customary manner. However, the user can go to the GUI portion of the default page where the user control has been added. There, VS2008 intellisense will list the “OnClubInsertComplete
” event that is set to the “ClubInsertComplete
” method in the default code-behind.
Steps to follow:
Step 1. The ClubList
class is a helper that simply creates a datatable and populates it with a list of clubs. They will be displayed in the grid view control on the default page.
Static class ClubList
:
Methods | Description |
BuildClubList() | A data table is populated with a default club list. The club list is later bound to the GridView control. |
AddClub() | Add a club to the data table. |
ClearTable() | Specific to the VS2008 debugger. It is necessary to clear out the table when using a static class because the object has not been disposed by the debugger when it is closed between debugging runs. |
public static class ClubList
{
private static DataTable _dt = new DataTable("ClubList");
static public DataTable dt
{
get { return _dt; }
set { _dt = value; }
}
static public void ClearTable()
{
_dt.Columns.Clear();
_dt.Rows.Clear();
}
static public DataTable BuildClubList()
{
DataColumn col;
DataColumn col2;
DataColumn col3;
DataRow row;
col = new DataColumn();
col.DataType = Type.GetType("System.Int32");
col.ColumnName = "ClubID";
col.ReadOnly = true;
col.Unique = false;
_dt.Columns.Add(col);
col2 = new DataColumn();
col2.DataType = Type.GetType("System.String");
col2.ColumnName = "ClubName";
col2.AutoIncrement = false;
col2.Caption = "Club Name";
col2.ReadOnly = false;
col2.Unique = false;
_dt.Columns.Add(col2);
col3 = new DataColumn();
col3.DataType = Type.GetType("System.String");
col3.ColumnName = "URL";
col3.AutoIncrement = false;
col3.Caption = "URL";
col3.ReadOnly = false;
col3.Unique = false;
_dt.Columns.Add(col3);
row = _dt.NewRow();
row["ClubID"] = 1000;
row["ClubName"] = "Joe's Rocketry";
row["URL"] = "www.joes.com";
_dt.Rows.Add(row);
row = _dt.NewRow();
row["ClubID"] = 1001;
row["ClubName"] = "American Rocketry";
row["URL"] = "www.ar.com";
_dt.Rows.Add(row);
return _dt;
}
public static DataTable AddClub(int ClubID, string ClubName, string url)
{
DataRow row;
row = _dt.NewRow();
row["ClubID"] = ClubID;
row["ClubName"] = ClubName;
row["URL"] = url;
_dt.Rows.Add(row);
row = _dt.NewRow();
return _dt;
}
}
Step 2. Create a class that inherits from EventArgs
. This class also has an event delegate declared outside of the body of the class.
The class UserControlEventArgs
contains a single property InsertComplete
that encapsulates the message being sent from the web user control to the club list page. The EventArgs
inheritance is a base class that resides in the system.
public class UserControlEventArgs : EventArgs
{
public UserControlEventArgs(bool InsertComplete)
{
if (InsertComplete)
{
this.InsertComplete = true;
}
else
{
this.InsertComplete = false;
}
}
private bool _InsertComplete;
public bool InsertComplete{
get { return _InsertComplete ; }
set { _InsertComplete = value; }
}
public delegate void ClubInsertedEventHandler(object sender, UserControlEventArgs e);
Step 3. Add a web user control to your project. I use the form view control with the insert item template to gather data from the user. Observe how the ItemCommand
event is used to manage the state of the form view control. I do that because of peculiarities present in the control.
The key here is the ClubInsertedEventHandler
, which is an event delegate that declares the variable ClubInsertComplete
. When the form view ItemCommand
event fires, the switch
structure fall through to CommitInsert
or CancelInsert
. There, the args
variable is instantiated using the constructor. The value of true or false flags whether the user chose to submit a club or cancel the submission. ClubInsertComplete
initiates the event bubbling process. A message is sent back to the Club List page where it is handled.
public partial class WebUserControlAddUser : System.Web.UI.UserControl
{
public event ClubInsertedEventHandler ClubInsertComplete;
protected void FormView1_Load(object sender, EventArgs e)
{
FormView1.ChangeMode(FormViewMode.Insert);
}
protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)
{
UserControlEventArgs args;
switch (e.CommandName)
{
case "StartInsert":
break;
case "CommitInsert":
InsertNewClub();
FormView1.ChangeMode(FormViewMode.ReadOnly);
args = new UserControlEventArgs(true);
ClubInsertComplete(this, args);
break;
case "CancelInsert":
FormView1.ChangeMode(FormViewMode.ReadOnly);
FormView1.DataBind();
args = new UserControlEventArgs(false);
ClubInsertComplete(this, args);
break;
default:
break;
}
}
protected void InsertNewClub()
{
TextBox tb;
tb = (TextBox)FormView1.FindControl("ClubID");
int ClubID = Convert.ToInt32(tb.Text);
tb = (TextBox)FormView1.FindControl("ClubName");
string ClubName = tb.Text;
tb = (TextBox)FormView1.FindControl("URL");
string url = tb.Text;
ClubList.AddClub(ClubID, ClubName, url);
}
}
Step 4. Create the user control and use the form view control to collect the data. Note that only the insert item template is used in this example.
<%@ Control Language="C#" AutoEventWireup="true"
CodeFile="WebUserControlAddUser.ascx.cs"
Inherits="WebUserControlAddUser" %>
<asp:FormView ID="FormView1" runat="server" Width="300px"
EmptyDataText="Empty rocketry data"
OnItemCommand="FormView1_ItemCommand" OnLoad="FormView1_Load">
<InsertItemTemplate>
<table cellpadding="2" cellspacing="0"
style="vertical-align: top; border-color: Black;border-style: outset;
border-width: 3px; width: 400px">
<tr>
<td style="text-align: left; vertical-align: top; width: 33%">
Club ID (xxx - int):
</td>
<td style="text-align: left; vertical-align: top">
<asp:TextBox ID="ClubID" runat="server" Columns="30"
Style="text-align: left" Text='<%# Bind("ClubID") %>'
TabIndex="10"> </asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator2"
runat="server" Text="*" ErrorMessage="Enter an integer"
ControlToValidate="ClubID"></asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td style="text-align: left; vertical-align: top">
Club Name:
</td>
<td style="text-align: left; vertical-align: top">
<asp:TextBox ID="ClubName" runat="server" Columns="30"
Style="text-align: left" Text='<%# Bind("ClubName") %>'
TabIndex="10"> </asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
runat="server" ControlToValidate="ClubName"
Text="*"
ErrorMessage="The club name is required.">
</asp:RequiredFieldValidator>
<br />
</td>
</tr>
<tr>
<td style="text-align: left; vertical-align: top">
URL:
</td>
<td style="text-align: left; vertical-align: top">
<asp:TextBox ID="url" runat="server"
Style="text-align: left" Text='<%# Bind("url") %>'
TabIndex="20"></asp:TextBox>
<br />
<br />
</td>
</tr>
<tr>
<td colspan="2">
<asp:LinkButton ID="LinkButton4" runat="server"
CommandName="CommitInsert" CausesValidation="true"
TabIndex="100">Submit</asp:LinkButton>
<asp:LinkButton ID="LinkButton3" runat="server"
CommandName="CancelInsert" CausesValidation="false"
TabIndex="200">Cancel</asp:LinkButton>
</td>
</tr>
<tr>
<td colspan="2">
<asp:ValidationSummary ID="ValidationSummary1" runat="server" />
</td>
</tr>
</table>
</InsertItemTemplate>
<EditRowStyle />
</asp:FormView>
Step 5. Finally, create a default page with a multiview control and drop the user control into part one of the view. Put a data grid in the second view.
ClubInsertComplete
intercepts the message that bubbled up from the web user control. The message is passed to ClubInsertComplete
and is exposed by the value e
, which is of type UserControlEventArgs
. It is the UserControlEventArgs
object that encapsulates the message that was sent from the web user control back to the Club List page. It is here that the submit successful or cancellation label is set to be made visible. Also, the view is set as necessary.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
MultiView1.SetActiveView(view2);
ClubList.ClearTable();
gvClubList.DataSource = ClubList.BuildClubList();
gvClubList.DataBind();
SubmissionSuccessful.Visible = false;
}
}
protected void LinkButton1_Click(object sender, EventArgs e)
{
MultiView1.SetActiveView(view1);
}
protected void ClubInsertComplete(object sender, UserControlEventArgs e)
{
if (e.InsertComplete)
{
SubmissionSuccessful.Visible = true;
}
MultiView1.SetActiveView(view2);
gvClubList.DataSource = ClubList.dt;
gvClubList.DataBind();
}
}
Of special note is that VS2008 intellisense does not expose the events for the web user control in the customary manner. However, in the ASPX page, the OnClubInsertComplete
event appears in intellisense. Set that to ClubInsertComplete
to handle the event that is bubbled up to the page from the web user control.
<pre>
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Src="WebUserControlAddUser.ascx"
TagName="WebUserControlAddUser" TagPrefix="uc1" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:MultiView ID="MultiView1" runat="server">
<asp:View ID="view1" runat="server">
<uc1:WebUserControlAddUser ID="AddClubUser" runat="server"
OnClubInsertComplete="ClubInsertComplete" />
</asp:View>
<asp:View ID="view2" runat="server">
<asp:Label ID="SubmissionSuccessful" runat="server"
Text="Your Club Has Been Submitted."></asp:Label><br />
<asp:LinkButton ID="LinkButton1" runat="server"
Text="Add New Club" OnClick="LinkButton1_Click">
Add New Club</asp:LinkButton><br />
<asp:GridView ID="gvClubList" runat="server">
</asp:GridView>
</asp:View>
</asp:MultiView>
</div>
</form>
</body>
</html>
Conclusion
It was challenging to find all of the materials necessary for developing the actual solution that has been implemented on a live production site. The development process involved putting together diverse concepts, and there was a clear lack of references on the web. At best, the materials that I found were dated. It is my hope that developers will find the example useful in understanding how event bubbling, as it relates to web user controls, can be utilized on production web sites.