Last night a student of mine asked why the Button Web control in a Repeater's ItemTemplate does not raise the Repeater's ItemCommand event when clicked, but a LinkButton does. I was perplexed because from my understanding anytime a Command event is raised, a CommandEventArgs is bubbled up the control hierarchy. When the RepeaterItem class detects a bubbled CommandEventArgs, it stops the bubbling of the CommandEventArgs and starts bubbling up a RepeaterCommandEventArgs instance. Likewise, the Repeater listens for bubbled events, and in detecting a bubbled RepeaterCommandEventArgs, it raises its ItemCommand event. (This is the same sequence of steps that happens in the DataList and DataGrid.)
I asked this student to show me a code sample where this was the case at a break. He did and, sure enough, the problem existed as he described. Specifically, he had a Repeater like so:
<asp:Repeater runat=“server“>
<ItemTemplate>
<asp:Button runat=”server” CommandName=”foo” Text=”Button” />
<asp:LinkButton runat=”server” CommandName=”bar” Text=”LinkButton” />
</ItemTemplate>
</asp:Repeater>
Next, he created an event handler for the Repeater's ItemCommand event, and did a simple Response.Write() in the event handler. Finally, in the Page_Load event handler he added the code to bind the data to the Repeater on each postback. (That is, not just on the first page load...) Upon visiting the page, if the LinkButton was clicked, the ItemCommand event fired (as evidenced by the Response.Write() output, and by the debugger); clicking the Button, however, did not fire the ItemCommand event.
Clearly, this perplexed me, so I decided to spend some time researching this after class. The results I have found are very odd and run counter to my understanding of how the Repeater and ASP.NET works. I am posting this here in hopes that someone will know why this behavior exists. Let me describe my research.
I started by creating a simple ASP.NET Web page with the following HTML markup:
<asp:Repeater id="Repeater1" runat="server">
<ItemTemplate>
<asp:LinkButton ID="linkButtonTest" Runat="server" OnCommand="lCommand" CommandName="bar" Text="LinkButton"></asp:LinkButton>
<asp:Button OnCommand="bCommand" ID="buttonTest" Runat="server" CommandName="foo" Text="Button"></asp:Button>
<br />
</ItemTemplate>
</asp:Repeater>
Next I created a Page_Load event handler and BindData() method that looked like the following:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
BindData()
End Sub
Private Sub BindData()
Dim a As New ArrayList
a.Add(1) : a.Add(2) : a.Add(3)
Repeater1.DataSource = a
Repeater1.DataBind()
End Sub
Notice that the data is bound to the Repeater on each and every Page_Load. If I placed the call to BindData() in Page_Load within an If statement so that it only ran when Not Page.IsPostBack, then the problems described above did not occur.
Following these two methods, I created event handlers for the Button and LinkButton's Command event, as well as an event handler for the Repeater's ItemCommand event.
Private Sub Repeater1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles Repeater1.ItemCommand
Response.Write(String.Concat("<b>ItemCommand</b>: The CommandName = ", e.CommandName, "<br />"))
End Sub
Protected Sub bCommand(ByVal sender As Object, ByVal e As CommandEventArgs)
Response.Write("Button Command event fired!<br />")
End Sub
Protected Sub lCommand(ByVal sender As Object, ByVal e As CommandEventArgs)
Response.Write("LinkButton Command event fired!<br />")
End Sub
Again, when running this demo the LinkButton, when clicked, would display “LinkButton Command event fired” and “ItemCommand event fired.” The Button Web control, however, would only display “Button Command event fired.” It was apparent that the Button's Command event was indeed firing, but the Repeater's ItemCommand was not. In an attempt to shed more light onto this I added code in the Command event handlers to print out the control that raised the event (the Button or LinkButton), along with its ancestors in the control hierarchy. The results stupefied me. For the LinkButton, the results were as expected:
Control Ascestors: Repeater1:_ctl1:linkButtonTest (System.Web.UI.WebControls.LinkButton) --> Repeater1:_ctl1 (System.Web.UI.WebControls.RepeaterItem) --> Repeater1 (System.Web.UI.WebControls.Repeater) --> _ctl0 (System.Web.UI.HtmlControls.HtmlForm) --> (ASP.test_aspx)
As you can see, the LinkButton was first printed, it's Parent was the RepeaterItem, it's Parent was the Repeater, it's parent was the Web Form, and it's Parent was the ASP.NET Page object. The Button results, however, were a shock:
Control Ascestors: _ctl0:buttonTest (System.Web.UI.WebControls.Button) --> _ctl0 (System.Web.UI.WebControls.RepeaterItem)
The Button's Parent, as expected, was the RepeaterItem, but the RepeaterItem has no Parent! This explained why the Repeater's ItemCommand event was not firing - the RepeaterItem could not bubble up the RepeaterCommandEventArgs up to the Repeater to raise the ItemCommand event. But why isn't there a parent for the RepeaterItem? The Button's Command event fires after Page_Load, where the control hierarchy is created (via the call to BindData(), which calls the Repeater's DataBind() method). So the RepeaterItem should indeed have a parent, namely the Repeater!
I decided to create a method to print out the entire control hierarchy. I then called this method both right after the Repeater was databound and in the Button Command event handler. The results were as expected: the entire control tree was displayed, showing that the RepeaterItem did indeed have the Repeater as its parent.
Repeater1 (Parent = _ctl0)
Repeater1:_ctl0 (Parent = Repeater1)
Repeater1:_ctl0:_ctl0 (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:linkButtonTest (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:_ctl1 (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:buttonTest (Parent = Repeater1:_ctl0)
Repeater1:_ctl0:_ctl2 (Parent = Repeater1:_ctl0)
Repeater1:_ctl1 (Parent = Repeater1)
Repeater1:_ctl1:_ctl0 (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:linkButtonTest (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:_ctl1 (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:buttonTest (Parent = Repeater1:_ctl1)
Repeater1:_ctl1:_ctl2 (Parent = Repeater1:_ctl1)
Repeater1:_ctl2 (Parent = Repeater1)
Repeater1:_ctl2:_ctl0 (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:linkButtonTest (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:_ctl1 (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:buttonTest (Parent = Repeater1:_ctl2)
Repeater1:_ctl2:_ctl2 (Parent = Repeater1:_ctl2)
So each of the three RepeaterItems has a Parent in the Button Command event... but it seems like the Button does not have a RepeaterItem with a Parent. What in the world is happening here? And why does it work with the LinkButton but not the Button Web control? Why does it work if the Repeater's DataBind() method was not called on that page visit? Using Reflector, I've poured over the source code for the Repeater, Button, LinkButton, and Page class, but am at a loss. Does anyone have any ideas? Am I missing something simple?
My test case can be viewed online here (includes complete source code).
Thanks for any possible insight...
P.S.: If you can explain why this is happening, and if you're at Tech-Ed this year, I'll buy you a beer!! :-)