Monday, June 27, 2005

Event Bubbling in ASP.NET : Kaushal Patel


The DataGrid control, as you likely know, can easily be configured to add a column of buttons. By adding a ButtonColumn, whenever
one of the DataGrid's buttons in the column is clicked, the Web page posts back and the DataGrid's ItemCommand
event fires. In fact, the same behavior can be noted if you manually add a Button or LinkButton control into a DataGrid
TemplateColumn, as the ButtonColumn class simply adds a Button (or LinkButton) Web control to each row of the DataGrid.

When the Button (or LinkButton) is clicked, its Command event is raised. But how does the DataGrid know when
this event has been raised so that it can raise its ItemCommand event in response? The answer is through a
process referred to as event bubbling. Event bubbling is the process of moving an event up the control hierarchy,
from a control low in the hierarchy - such as a Button within the row of a DataGrid - and percolating it up to an ancestor
control - such as the DataGrid. Once the ancestor control has learned of the event it can respond however it sees fit; in
the DataGrid's case, the DataGrid "swallows" the Button's Command event (that is, it stops the bubbling) and
raises its own ItemCommand event in response.

In this article we'll look at how, precisely, event bubbling works in ASP.NET. Event bubbling is a technique that all
server control developers should be aware of. Additionally, it can be used as a means to pass event information from a
User Control to its parent page, as discussed in
Events from Web User Controls
(although personally I find it simpler to just use the technique of having the User Control
raise its own events through the standard event firing syntax as discussed at
An Extensive
Examination of User Controls
). Read on to learn more!

Understanding the Control Hierarchy

All ASP.NET pages are represented in code as a hierarchy of controls. While ASP.NET page developers are accustomed to working
in terms of the Visual Studio .NET designer and HTML and Web control syntax, this markup is automatically converted into a
programmatically-created control hierarchy when an ASP.NET page is first visited (or first visited after a change to its
.aspx page's content). To better understand this process, let me refer to a previous article of mine,
Understanding ASP.NET View State:

All ASP.NET server controls can have a parent control, along with a variable number of child controls. The
System.Web.UI.Page class is derived from the base control class (System.Web.UI.Control), and
therefore also can have a set of child controls. The top-level controls declared in an ASP.NET Web page's HTML portion are
the direct children of the autogenerated Page class. Web controls can also be nested inside one another. For
example, most ASP.NET Web pages contain a single server-side Web Form, with multiple Web controls inside the Web Form. The
Web Form is an HTML control (System.Web.UI.HtmlControls.HtmlForm). Those Web controls inside the Web Form are
children of the Web Form.

Since server controls can have children, and each of their children may have children, and so on, a control and its
descendents form a tree of controls. This tree of controls is called the control hierarchy. The root of the control
hierarchy for an ASP.NET Web page is the Page-derived class that is autogenerated by the ASP.NET engine.

Whew! Those last few paragraphs may have been a bit confusing, as this is not the easiest subject to discuss or digest. To
clear out any potential confusion, let's look at a quick example. Imagine you have an ASP.NET Web page with the following HTML

<h1>Welcome to my Homepage!</h1>
<form runat="server">
What is your name?
<asp:TextBox runat="server" ID="txtName"></asp:TextBox>
<br />What is your gender?
<asp:DropDownList runat="server" ID="ddlGender">
<asp:ListItem Select="True" Value="M">Male</asp:ListItem>
<asp:ListItem Value="F">Female</asp:ListItem>
<asp:ListItem Value="U">Undecided</asp:ListItem>
<br />
<asp:Button runat="server" Text="Submit!"></asp:Button>

When this page is first visited, a class will be autogenerated that contains code to programmatically build up the control
hierarchy. The control hierarchy for this example [is shown below:]

This concept of a control hierarchy works just the same with a DataGrid. Specifically, a DataGrid contains a child control
for each of its rows. Each of these row controls contains a child for each of its columns. And each column control may
contain further controls. For ButtonColumns, the column control would contain a single child control - either a Button or
LinkButton control. For a TemplateColumn, the column control would contain its own hierarchy of children that composed the
HTML markup and Web controls specified in the template.

For example, imagine we had a DataGrid with two columns, a ButtonColumn and a BoundColumn. When binding the DataGrid to
a DataSource with three records, the DataGrid's control hierarchy might look like the following:

Bubbling Basics

When one of the DataGrid's Buttons are clicked, the Button's Command event is fired. When this happens we
also want to have the DataGrid's ItemCommand event fire, but as the above figure illustrates, there is a bit
of distance between the Button instance that was clicked and the DataGrid control. Event bubbling is employed to percolate
the event from the Button control up to the DataGrid, where the ItemCommand event can be raised in response.

Event bubbling works using two protected methods of the Control class:

  • RaiseBubbleEvent(source, eventArgs) - when called, a specified event's information is
    bubbled up to control's parent. The source parameter is typically a reference to the object doing the bubbling (the
    actual Button control in the DataGrid example), whereas the eventArgs contains information about the event
    being bubbled (in the DataGrid example, the Button's CommandEventArgs information is percolated up, which includes
    the Button's CommandName and CommandArgument parameters).

  • OnBubbleEvent(source, eventArgs) - when an event is bubbled up to a control, the control's
    OnBubbleEvent() method fires. A control in the hierarchy may override this method to do some custom
    processing on the bubbled event. In the DataGrid's case, the OnBubbleEvent() method checks to see if
    a CommandEventArgs instance is being bubbled up. If so, it raises its own ItemCommand event.
    (This is a slight simplification of how things really work with the DataGrid.)

    The OnBubbleEvent() method returns a Boolean value that indicates if bubbling of the event should continue.
    If OnBubbleEvent() returns False, the event continues to bubble up the hierarchy; if it returns True, the
    bubbling of that event ends. By default, a control's OnBubbleEvent() returns False, thereby allowing event
    bubbling to progress unimpeded.

To demonstrate event bubbling, image that the Button in the third
DataGrid row is clicked. Clicking the Button causes a postback and raises the appropriate Button control's Click
event handler. The Button (and LinkButton) Web control's are coded such that when their Command event is fired,
the event details are bubbled up the hierarchy. Specifically, the OnCommand() method in the Button and LinkButton
classes looks as follows (comments added by yours truly):

Protected Overridable Sub OnCommand(ByVal e As CommandEventArgs)
'OnCommand is passed in the CommandEventArgs for the event...
Dim handler1 As CommandEventHandler = _
CType(MyBase.Events.Item(Button.EventCommand), CommandEventHandler)

If (Not handler1 Is Nothing) Then
'This raises the event...
handler1.Invoke(Me, e)
End If

'The call to RaiseBubbleEvent starts the percolation of the
'event up the control hieracrhy.
MyBase.RaiseBubbleEvent(Me, e)
End Sub

This event is then bubbled up to the ButtonColumn, calling its OnBubbleEvent() method, which, by default, does
nothing, thereby letting the bubbling continue. Following that, the DataGridItem's OnBubbleEvent() method is
called. The DataGridItem's OnBubbleEvent() checks to see if a CommandEventArgs object is being bubbled
up and, if so, it crafts an instance of the DataGridCommandEventArgs object based on the passed-in
CommandEventArgs object. This new type is then bubbled up to its parent (the DataGrid) and event bubbling is
ceased. Here's the code from the DataGridItem's OnBubbleEvent() method:

Protected Overrides Function OnBubbleEvent(ByVal source As Object, _
ByVal e As EventArgs) As Boolean
If TypeOf e Is CommandEventArgs Then
'Craft a DataGridCommandEventArgs object from the CommandEventArgs...
Dim args1 As New DataGridCommandEventArgs(Me, _
source, CType(e, CommandEventArgs))

'Bubble the new DataGridCommandEventArgs
MyBase.RaiseBubbleEvent(Me, args1)

'Stop the bubbling of the CommandEventArgs
Return True
End If

'If we are dealing with something other than a CommandEventArgs,
'let the bubbling proceed unmolested...
Return False
End Function

Finally, in the DataGrid's OnBubbleEvent() method a check is made to see if a DataGridCommandEventArgs
object has been bubbled up. If so, it halts the bubbling and raises its ItemCommand event. (Note that the
DataGrid's OnBubbleEvent() method is a bit more complex than the DataGridItem's because the DataGrid
might raise a number of events based on the CommandName value in the DataGridCommandEventArgs object.
(For example, a CommandName of Edit raises the EditCommand event, a CommandName of Update
raises the UpdateCommand event, and so on. Of course, regardless of the CommandName the ItemCommand
always fires.)

The following figure depicts the chain of events just described:


In this article we saw how event bubbling is used to percolate events up the control hierarchy. This technique is an
invaluable one for control developers creating composite controls whose contents cannot be determined until runtime. The most
common Web control that fits into this class is the DataGrid, although both the DataList and Repeater use event bubbling in
a similar manner. For more information on creating these types of controls, along with a further discussion on event bubbling,
be sure to read
DataBound Templated Custom ASP.NET Server Controls