Repeater Weirdness...

Rate It (1)

Last post 10-10-2008 4:42 AM by gsan420. 7 replies.

Sort Posts:

  • Repeater Weirdness...

    05-20-2004, 2:06 PM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs
    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("ItemCommand: 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...

    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: Repeater Weirdness...

    05-20-2004, 3:03 PM
    • All-Star
      22,585 point All-Star
    • Bug_Bugger
    • Member since 01-15-2004, 8:24 AM
    • London
    • Posts 4,505
    I personally never came accross this kinda problem before but have an opinion about. I cannot test that here but almost sure that the same story would happen if you used DataList or DataGrid instead of Repeater.

    I think that this kind of a difference between the Button and LinkButton is determined by the way the page is posted in response to user action. LinkButton posts values in the hidden fields whereas Button does not. This still does not explain to problem though.

    I think that on DataBinding, existing RepeaterItems somehow get detached from the Repeater. Interesting test would be to to compare the Parent property of the Button in the Button event handler with the corresponding Item of the Repeater accessed through the repeater. I bet those will refer to different objects

    As is said, i am surprised myself by such a behaviour and this post is just to add an opinion. I personally would consider Button's behaviour to be the more appropriate one in this situation.
    Bug [MCSD]
  • Re: Repeater Weirdness...

    05-20-2004, 3:42 PM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs
    Interesting... from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbtskrespondingtobuttoneventsindatalistitems.asp

    "If you call the DataBind method of the parent control (the DataList, Repeater, or DataGrid control), the ItemCommand event is not raised, because the contents of the parent control are reset. Therefore, you generally do not want to call the DataBind method on each round trip (that is, in the page initialization without checking for a post back)."

    I agree that calling DataBind() on the Repeater "resets" it's Controls collection, but then it builds it back up in the CreateControlHierarchy() method (which is called from the Repeater's DataBind() method). Since it builds the hierarchy back up in the Page_Load, and since the Button's Command event doesn't fire until later in the life-cycle, it still perplexes me as to why the Button doesn't raise the Repeater's ItemCommand event.

    Even if we assume that for some magical reason the Button wouldn't raise this event, why does the LinkButton raise the ItemCommand event?
    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: Repeater Weirdness...

    05-20-2004, 3:48 PM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs
    I think that this kind of a difference between the Button and LinkButton is determined by the way the page is posted in response to user action. LinkButton posts values in the hidden fields whereas Button does not. This still does not explain to problem though.

    That was my assumption, too. But looking at the ProcessRequestMain() method of the Page class, it doesn't matter if a submit button causes the postback, or if a LinkButton calls the __doPostBack. In either case, the Page class invokes the RaisePostBackEvent() method of the control that caused the postback. (This, of course, happens after the Page_Load event, so it happens after the Repeater's DataBind() method has been called...)

    I think that on DataBinding, existing RepeaterItems somehow get detached from the Repeater

    This wouldn't make sense, since when DataBind() is called, the Repeater's entire control hierarchy is cleared out then rebuilt. In the Repeater's CreateItem() method, the newly created RepeaterItem is added to the Repeater's Controls collection, which has the side effect of setting the RepeaterItem's Parent property to the Repeater itself.

    Interesting test would be to to compare the Parent property of the Button in the Button event handler with the corresponding Item of the Repeater accessed through the repeater. I bet those will refer to different objects

    My demo illustrates this, see http://scottonwriting.net/demos/RepeaterWeirdness.aspx. Click a LinkButton and you'll see:

    Examining Repeater's children:
    # Repeater1:_ctl0 (System.Web.UI.WebControls.RepeaterItem), Parent = Repeater1 - this is the LinkButton's parent!
    # Repeater1:_ctl1 (System.Web.UI.WebControls.RepeaterItem), Parent = Repeater1
    # Repeater1:_ctl2 (System.Web.UI.WebControls.RepeaterItem), Parent = Repeater1

    Note the "this is the LinkButton's parent". This is displayed if the RepeaterItem equals the RepeaterItem that's the LinkButton's parent. Doing this same test with Button, though, you see:

    Examining Repeater's children:
    # Repeater1:_ctl0 (System.Web.UI.WebControls.RepeaterItem), Parent = Repeater1
    # Repeater1:_ctl1 (System.Web.UI.WebControls.RepeaterItem), Parent = Repeater1
    # Repeater1:_ctl2 (System.Web.UI.WebControls.RepeaterItem), Parent = Repeater1

    Thereby implying that none of the RepeaterItems are the same object as the Parent of the Button.

    Why in the world is this? How can there be two different Buttons with two different RepeaterItems? Oy.
    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: Repeater Weirdness...

    05-20-2004, 4:56 PM
    • All-Star
      22,585 point All-Star
    • Bug_Bugger
    • Member since 01-15-2004, 8:24 AM
    • London
    • Posts 4,505
    From your code, test: c.Equals(b.Parent) reveals that parent of the button who's event handler is invoked is not a control from the Controls collection of the repeater which implies that there are 2 buttons (and two RepeaterItems) at the time when the bCommand event handler is run and event is raised for a button that does no longer belong to the Repeater. Therefore for some reason event gets attached to a control that actually raised it in case of a Button but in case of a LinkButton raising event, it is attached to a newly created LinkButton.

    As was mentioned i might be wrong but I still believe that has something to do with the way the postback happens. Something should be handled differently in case controls gets posted itself, i mean Button will have his own entry in the list of posted variables. I don't remember how <input type="image" behaves on postback but would be interesting to extend the test and add ImageButton to the Repeater's item (although i am not sure if ImageButton raises ItemCommand at all)

    I would be very interested in seeing the explanation of this mistery.
    Bug [MCSD]
  • Re: Repeater Weirdness...

    05-20-2004, 5:51 PM
    • Contributor
      4,100 point Contributor
    • Scott Mitchell
    • Member since 06-15-2002, 8:41 PM
    • San Diego, CA
    • Posts 707
    • ASPInsiders
      TrustedFriends-MVPs
    Well, I discovered the solution with some help from my student, and, as you stated, it had to do with the Button postback and there being a superfluous set of RepeaterItems. The following is quoted from this blog entry of mine...

    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

    In my last blog entry I posed an ASP.NET / Repeater question that had me and a student of mine utterly stumped. I have been communicating with this student today, bouncing ideas back and forth, and he noted that if the Repeater's EnableViewState property was set to False, then the Button column raised the ItemCommand event, as expected. Now, all that the Repeater stores in its ViewState StateBag is the number of items in the Repeater (stored in a view state variable named _!ItemCount).

    Basically, when the Repeater's DataBind() method is called, the following steps happen:

    1. The Repeater's base's Controls collection has its Clear() method called, cleaning out any controls in the Repeater's hierarchy.
    2. The child view state is cleared out.
    3. The CreateControlHierarchy() method is called, passing in True (indicating that the data should be bound from the DataSource).
    4. The ChildControlsCreated property is set to True. This property is a flag to indicate that the Repeater's control hierarchy has been built. Whenever the Repeater's Controls property is accessed, it first checks to make sure that the control hierarchy has been built by calling EnsureChildControls(). EnsureChildControls() checks to see if ChildControlsCreated is false; if it is, it calls CreateChildControls().

    When a Button submits a Form, the Button's name and value attributes are sent along in the POST data to the Web server. After the LoadViewState stage in the page's life-cycle (which comes before the Load stage), the ProcessPostData stage transpires. This stage loads post data back into the appropriate Web controls, such as reloading the text from a textbox back into the Text property of a TextBox Web control. Specifically, the Page class's ProcessPostData() method enumerates all the POST names passed in and searches the control hierarchy for these controls to determine if they participate in this stage of the life-cycle. In accessing the control via FindControl(), the EnsureChildControls() method is called and the Repeater's CreateChildControls() method is invoked.

    Now, what does the Repeater's CreateChildControls() method do? Well, it checks to see if the view state variable _!ItemCount is null or not. If it is not null, then it calls CreateControlHierarchy(), passing in False. This builds up the control hierarchy, creating the Button. This created Button is then the Button the Page class's ProcessPostData() method marks to have its Command event raised during the RaisePostBackEvent stage later on.

    Hopefully the problem is becoming clearer now. After this ProcessPostData stage, the Load stage transpires, and the Repeater's DataBind() method is called. This clears out the control hierarchy and rebuilds it. Hence, the reference to the Button we had earlier has had it's RepeaterItem “detached” from the Repeater, replaced by a new RepeaterItem isntance from the call to DataBind(). Since it's this orphaned Button's Command event that is raised, the event cannot percolate up to the Repeater, and hence the Repeater's ItemCommand event does not fire.

    The LinkButton works because clicking a LinkButton does not send the LinkButton's ID through the POST data. Rather, it passes this information along in the __EVENTARG hidden form field. This information, then, is not queried by the Page class until the RaisePostBackEvents stage, which happens after the Load event, after the Repeater's control hierarchy has been constructed.

    So, one workaround is as follows: don't call a data Web control's DataBind() method each and every page load if you are using view state. If you are not using view state, then you'll need to call DataBind() on each and every page load.

    My student, Matt, wrote in to share the following workaround as well (I've not tested it; might not work in all browsers):

    *********************************

    My colleague Borys has proposed a fix. I don't think I can explain it
    very well, but we think it has to do with a conflict between post data
    and viewstate. I'll let the code speak for itself.

    Private Sub DataList1_ItemDataBound(ByVal sender As Object, ByVal e As
    System.Web.UI.WebControls.DataListItemEventArgs) Handles
    DataList1.ItemDataBound

    Dim b As Button = DirectCast(e.Item.Controls(1), Button)

    Dim evTarget As String = b.UniqueID.Replace(":", "$")

    Dim script As String = "__doPostBack('" + evTarget +
    "','');return(false)"

    b.Attributes.Add("onclick", script)

    End Sub

    *********************************

    Hope this helps someone, it was rather fun to examine and unearth the solution.
    Happy Programming!


    -- Scott Mitchell
    -- mitchell@4guysfromrolla.com
    -- http://scottonwriting.net/sowblog/
    -- http://www.4GuysFromRolla.com/ScottMitchell.shtml
  • Re: Repeater Weirdness...

    09-14-2007, 10:25 AM
    • Member
      193 point Member
    • frist44
    • Member since 03-01-2007, 1:38 PM
    • Posts 447

    I think i just fell on to the same thing with an image button.  weird...but great post!

  • Re: Repeater Weirdness...

    10-10-2008, 4:42 AM
    • Member
      2 point Member
    • gsan420
    • Member since 10-10-2008, 4:36 AM
    • Posts 1

     Hi there

     

    Try instead of this 

     

    <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>
     
     
    try this
     
    <asp:Repeater id="Repeater1" OnItemDataBound="Repeater1_ItemDataBound" runat="server">


    <ItemTemplate>


    <asp:LinkButton ID="linkButtonTest" Runat="server" CommandName="bar" Text="LinkButton"></asp:LinkButton>


    <asp:Button OnCommand="bCommand" ID="buttonTest" Runat="server" Text="Button"></asp:Button>


    <br />


    </ItemTemplate>


    </asp:Repeater>
     
    And catch the command name in the event handler for the repeaters item data bound.
     
     

     

Page 1 of 1 (8 items)