Understanding the naming container hierarchy of ASP.NET databound controls
Some of the most common questions about ASP.NET data-bound controls are related to the naming container hierarchy. People are using databound controls but don't understand how these are designed to construct their control hierarchy which leads to problems in scenarios when these controls should be iterated or some functional stuff should be achieved. With data-bound controls I mean Repeater, DataList, DataGrid and GridView.
Assume I have a Repeater with following structure and it's databound to a data source with three rows.
<asp:repeater ID="Repeater1" runat="server" >
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button on row" />
<asp:TextBox ID="TextBox1" runat="server" Text="Text on row" />
protected void Page_Load(object sender, EventArgs e)
ArrayList arr = new ArrayList();
Repeater1.DataSource = arr;
//We'll check later what's on Button1_Click
Essentially this builds control structure looking like this (there would be other controls like literal controls etc but I'll focus on the relevant ones)
OK, lets briefly describe what this means. Repeater has as many RepeaterItems as there were rows in the data source it was bound to. Every RepeaterItem contains the controls given in <ItemTemplate>. Basically this template was instantiated three times, once for every RepeaterItem. repeaterItems are accessible via Items collection property of the Repeater.
RepeaterItem is so called naming container, a control which implements INamingContainer interface.This means it provides unique naming scope for its child controls so that these won't have unique ids conflicting with other controls on the Page. In this case it of course means that repeated controls given in <ItemTemplate> all do have consistently unique id, accessible from control's UniqueID property. If you see how these are rendered in the previous example:
<input type="submit" name="Repeater1$ctl00$Button1" value="Button on row" id="Repeater1_ctl00_Button1" />
<input name="Repeater1$ctl00$TextBox1" type="text" value="Text on row" id="Repeater1_ctl00_TextBox1" />
<input type="submit" name="Repeater1$ctl01$Button1" value="Button on row" id="Repeater1_ctl01_Button1" />
<input name="Repeater1$ctl01$TextBox1" type="text" value="Text on row" id="Repeater1_ctl01_TextBox1" />
<input type="submit" name="Repeater1$ctl02$Button1" value="Button on row" id="Repeater1_ctl02_Button1" />
<input name="Repeater1$ctl02$TextBox1" type="text" value="Text on row" id="Repeater1_ctl02_TextBox1" />
Rendered name attributes correspond to UniqueID property and id attribute to ClientID property. What's clear also is that control's id is got by appending its id to ids of its ancestor controls (unique ID separator being $, client id the underscore _ ).
The usual scenarios this information is needed in
1. Access a specific control(s) on the databound control
If you need to access for example TextBox1 in previous example, you cannot do it by typing just TextBox1.xxx and assuming that ASP.NET would know which one of these TextBoxes you mean. As is probably clear from the picture, there are now multiple TextBoxes with ID 'TextBox1' and you need to be more accurate in telling which one you do want to access.
You can iterate through the Repeater1 and access the TextBoxes one by one.
foreach (RepeaterItem rptItem in Repeater1.Items)
TextBox textbox = (TextBox)rptItem.FindControl("TextBox1");
//Do something with the TextBox
2. Access other controls on the same item
If you have reference to a control on the item, or you are typing code to event handler raised by an control on the item, you can use that control's reference to "gather" knowledge about on which item you are. To be clear, if you handle Button's click event in Button1_Click method (wired to Button on the Repeater) you can get the parent RepeaterItem via NamingContainer property of the Button. Another important piece of knowledge is that you get reference to the Button via sender argument in Button1_Click, because sender always provides the specific object which raised the event. In code:
protected void Button1_Click(object sender, EventArgs e)
Button btn = (Button)sender;
RepeaterItem ritem = (RepeaterItem)btn.NamingContainer;
//use the RepeaterItem to locate other controls on the row
So, any control has reference to its naming container via NamingContainer property and this is very handy with databound controls. It applies equally to any of them. This technique is useful for example when you want to know the data key related to the item/row the control is on.
Note: Another way to deal with the click would've been using Command event relying to event bubbling, e.g specifying CommandName on the Button which eventually raises ItemCommand event. Point is that in ItemCommand event, the specific item is passed via command event arguments. However, Command event applies only to Buttons while this technique works for any type of control.
3. Use the information about the item type in databinding expressions
You know you are using a Repeater and it's item type is RepeaterItem. You know you get the item index via ItemIndex property so you can have this kind of databinding expression to provide running count number for items (rows)
<%#((RepeaterItem)Container).ItemIndex + 1 %>
With databinding expression you've probably seen the keyword Container multiple times. It refer's to the naming container we are currently on, and based on previous information you realize it just means it is reference to the item of the databound control which's context the databinding expression is evaluated in. You can cast the Container to the correct item type for early-bound access (in VS2005 this gives you intellisense with inline code too). In .NET 2.0 these all implement System.Web.UI.IDataItemContainer interface which gives access to certain important properties general to all item types.
Note: You've probably seen also Container being used multiple times in databinding expressions to access the DataItem property of the item in question. DataItem property is the row in data source being associated with the item type. Basically it is the data source row/item to which the item in the databound control is bound to/corresponds to. If you bind your control to a DataSet/DataTable or DataView the type of DataItem is System.Data.DataRowView. With data readers it is System.Data.Common.DbDataRecord. This is useful information even if you are using data source controls, since these provide just server control wrappers for the previously mentioned ADO.NET objects
About the databound controls
It's important to recognize that all databound controls are equal to this matter. They work in same way and have similar API. Their row/item types vary, but provide the same means for developer to utilize them. Here's list of databound controls and their corresponding row types
Repeater - System.Web.UI.WebControls.RepeaterItem
DataList - System.Web.UI.WebControls.DataListItem
DataGrid - System.Web.Ui.WebControls.DataGridItem
GridView - System.Web.UI.WebControls.GridViewRow