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:
- The Repeater's base's Controls collection has its Clear() method called, cleaning out any controls in the Repeater's hierarchy.
- The child view state is cleared out.
- The CreateControlHierarchy() method is called, passing in True (indicating that the data should be bound from the DataSource).
- 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.