Saturday, January 27, 2007

Event Bubbling in ASP.NET 2.0 and templates

I have been working recently with Page.LoadTemplate and ITemplate.InstantiateIn to add content from user controls to pages or other controls. This thing solves the problem of having to declare abstract classes in App_Code, but poses another problem linked to the lack of object references to the user controls you instantiate. What that means, among other things, is that you lose the ability to use events in your code.

But there is a way to bubble events to the upper controls and catch them. The first thing you should look at is the OnBubbleEvent protected method of the Control object. It catches all the events raised by its controls. Override it and you can catch events and treat them. Now, this method returns a bool value. It tells the RaiseBubbleEvent method (I'll talk about it shortly) if the event was handled or not. If it was, then the event doesn't rise above that control.

Now, about the RaiseBubbleEvent method, also a protected method of the Control object. What it does is go up the object hierarchy and execute on each object the OnBubbleEvent. It also stops if the method returns true. It is so simple and perfect that it is not overridden in any of the ASP.NET 2.0 WebControls.

But there is a catch. Some objects, like DataLists, DataGrids, Gridviews, Repeaters, etc, choose to handle the events in their items no matter what. That's why if you click a button inside a DataList, the event won't bubble to the OnBubbleEvent method from the UserControl or Page the DataList is in. Instead, the event reaches the OnItemCommand event.

So, if you want to use this general bubbling method with WebControls that override OnBubbleEvent, you have two options.
One is use inherited objects that remove this issue altogether (like inheriting from a DataList, overriding OnBubbleEvent, executing base.OnBubbleEvent, then always return false), which might make things cumbersome since you would have to define a control library.
The second option is to always handle the OnItemCommand and other similar events with something like RaiseBubbleEvent(sender, args); . This very line will re bubble the event upwards.

Benefits: the benefits of using this general event bubbling technique is that you don't have to always specifically write code in the OnItemCommand or having to handle OnClick events and so on and so on. Just catch all events, check if their arguments are CommandEventArgs, then handle them depending on source, command name and command argument.

5 comments:

Anonymous said...

It seems whether you use a RaiseBubbleEvent or not for a button event inside a usercontrol (the usercontrol is inside of a datalist), the OnItemCommand event for the datalist gets fired anyway when clicking on the button. The RaiseBubbleEvent doesn't seem to accomplish anything.

Siderite said...

RaiseBubbleEvent raises an event, even a custom one or a non existant one. What I said about DataLists is that you catch the OnItemCommand event and bubble it further.
So you do have to catch the OnItemCommand event, because an event won't bubble upwards from it, but the only code you need to put in is RaiseBubbleEvent(this,e);
Considering the DataList is itself in a UserControl or loaded through templates or some other mechanism, the command will bubble and you will be able to catch it in a general OnCommand event in the page or some high level UserControl.

Anonymous said...

I am not sure if I still follow. Here's a code snippet of what I have -

In test.aspx - a datalist control with an objectdatasource. Each object is passed to the usercontrol via a property with declarative data binding.

protected void DataList1_ItemCommand(object source, DataListCommandEventArgs e)
{

}


In Usercontrol.ascx - several labels and textboxes populated with the object data passed in and a couple of buttons to aprrove or disapprove.

protected void btnApprove_Click(object sender, EventArgs e)
{
RaiseBubbleEvent(sender, e);
}

protected void btnDisapprove_Click(object sender, EventArgs e)
{
RaiseBubbleEvent(sender, e);
}"

Regardless of whether the RaiseBubbleEvent is commented out or not, the button event gets fired as well as the datalist itemcommand event.

Is the RaiseBubbleEvent a means of transfering sender and event information to the "bubble (parent)" event as this information would not be accessible if the RaiseBubbleEvent was not used.

I am trying to wrap my arms around the purpose of this as I obviuosly never had the need to use it. I guess I need to see a good example of when or where it is beneficial to use it.

Siderite said...

The point of what I wrote in the article was NOT to use the OnClick events or even declarative OnCommand events and catch everything using OnBubbleEvent somewhere high up.

In your example, the OnClick event is fired (of course, since you declared it for each button), then the OnItemCommand event for the DataList in which your user controls are. But there it stops. You can't catch the event in the page OnBubbleEvent, since it stops at the OnItemCommand.

So you do something like this:
protected void DataList1_ItemCommand(object source, DataListCommandEventArgs e)
{
RaiseBubbleEvent(source,e);
}

The OnItemCommand event will say it handled the event, so it won't bubble up, but with that piece of code you programatically bubble it up, thus you can catch it in the page OnBubbleCommand like this:

protected override bool OnBubbleEvent(object source, EventArgs args)
{
if (args is DataListCommandEventArgs)
{
DataListCommandEventArgs dlcea = (DataListCommandEventArgs) args;
switch (dlcea.CommandName)
{
case "SomeCommandName":
DoSomethingWith(source,dlcea.Item, dlcea.CommandArgument);
return true;
}
}
if (args is CommandEventArgs)
{
CommandEventArgs cea = (CommandEventArgs) args;
switch(cea.CommandName)
{
case "SomeCommandName":
DoSomethingWithElse(source, cea.CommandArgument);
return true;
}
}
return base.OnBubbleEvent(source, args);
}

Thus you can move all logic of command handling in the page code, without adding a zillion events for each datalist, usercontrol, etc.

Anonymous said...

Thanks siderite! It is now very clear where you would want to use the RaiseBubbleEvent.

In my scenario, it is not necessary to use RaiseBubbleEvent.

Like you said earlier, if I wanted to do some event operations higher up from OnItemCommand event is where I would use RaiseBubbleEvent.

Again, thanks for providing an example from a different angle.