It is easy to bind a property of a control to a single data item (or expression) using ASP.NET data-binding syntax. This section addresses the more complex scenario of developing a control that has templated properties bound to a data source that is a collection type (System.Collections.ICollection or System.Collections.IEnumerable). Templates enable a page developer to customize the presentation of data that is bound to the control. The Repeater and DataList controls are examples of templated data-bound controls.
For an overview of data binding in ASP.NET pages, see the ASP.NET QuickStart —> ASP.NET Web Forms —> Databinding Server Controls. For background on authoring a templated control, see Developing a Templated Control.
A templated data-bound control has a data source property of type ICollection or IEnumerable and one or more properties of type ITemplate. The container for one of the template properties defines a property (generally named DataItem) to bind data to. The control implements its data-binding logic in the Databind method that it inherits from Control. It overrides the CreateChildControls method to recreate the hierarchy of child controls upon postback. These steps are explained in greater detail in the following discussion.
To develop a templated data-bound control
- Define a control that implements the System.Web.UI.INamingContainer interface.
public class TemplatedList : WebControl, INamingContainer {...}
[Visual Basic]
Public Class TemplatedList
Inherits WebControl
Implements INamingContainer
...
End Class
- Define a property of type System.Web.UI.ITemplate.
[TemplateContainer(typeof(TemplatedListItem))]
public virtual ITemplate ItemTemplate {
get {
return itemTemplate;
}
set {
itemTemplate = value;
}
}
[Visual Basic]
<TemplateContainer(GetType(TemplatedListItem))> _
Public Overridable Property ItemTemplate() As ITemplate
Get
Return _itemTemplate
End Get
Set
_itemTemplate = value
End Set
End Property
The logical container for the template (specified in the TemplateContainerAttribute attribute) must have a property to bind data to. By convention, this property is named DataItem. For details about logical containers for template properties, see Developing a Templated Control. The following example defines a container for the template property.
public class TemplatedListItem : TableRow, INamingContainer {
private object dataItem;
public virtual object DataItem {
get {
return dataItem;
}
set {
dataItem = value;
}
}
[Visual Basic]
Public Class TemplatedListItem
Inherits TableRow
Implements INamingContainer
Private _dataItem As Object
Public Overridable Property DataItem() As Object
Get
Return _dataItem
End Get
Set
_dataItem = value
End Set
End Property
End Class
- Override the DataBind method (inherited from Control) to provide data-binding logic. This must have the following steps:
- Invoke the OnDataBinding method of the base class to invoke the handlers (attached by the page) that evaluate data-binding expressions on your control.
- Clear the Controls collection.
- Clear the ViewState of the child controls.
- Create the child controls using the data source.
- Signal to the ASP.NET page framework to track the ViewState for your control.
The following code performs these steps. CreateChildControlsHierarchy is a helper method to perform the actual work of creating the child controls. See step 5 for details.
public override void DataBind() {
// Controls with a data-source property perform their
// custom data binding by overriding DataBind to
// evaluate any data-binding expressions on the control
// itself.
base.OnDataBinding(EventArgs.Empty);
// Reset the control's state.
Controls.Clear();
ClearChildViewState();
// Create the control hierarchy using the data source.
CreateControlHierarchy(true);
ChildControlsCreated = true;
TrackViewState();
}
[Visual Basic]
Public Overrides Sub DataBind()
' Controls with a data-source property perform their custom data
' binding by overriding DataBind.
' Evaluate any data-binding expressions on the control itself.
MyBase.OnDataBinding(EventArgs.Empty)
' Reset the control state.
Controls.Clear()
ClearChildViewState()
' Create the control hierarchy using the data source.
CreateControlHierarchy(True)
ChildControlsCreated = True
TrackViewState()
End Sub
- Override CreateChildControls to recreate the child controls in a postback scenario. This involves clearing the Controls collection and creating the control hierarchy using the view state instead of the data source. The actual work of creating the child controls is hidden in the
CreateControlHierarchy method described in step 5.
protected override void CreateChildControls() {
Controls.Clear();
if (ViewState["ItemCount"] != null) {
// Create the control hierarchy using the view state,
// not the data source.
CreateControlHierarchy(false);
}
}
[Visual Basic]
Protected Overrides Sub CreateChildControls()
Controls.Clear()
If Not (ViewState("ItemCount") Is Nothing) Then
' Create the control hierarchy using the view state,
' not the data source.
CreateControlHierarchy(False)
End If
End Sub
- Define a data source that has null elements and use this data source instead of the real data source when creating the control hierarchy on postback. Steps 3 and 4 create the controls hierarchy using the data source and the saved view state, respectively. A dummy data source enables a control to implement a single code path for the common elements of these two steps.
Note This step (step 5) describes implementation details used by the data-bound ASP.NET controls in the .NET Framework. The DummyDataSource class and the CreateControlHierarchy method shown in the following fragment are not in the .NET Framework but have to be defined by a control developer. You are not required to implement these elements; however, it is recommended that you use this or a similar technique to provide a common code path for creating the control hierarchy.
The following code fragment defines a dummy data source.
internal sealed class DummyDataSource : ICollection {
private int dataItemCount;
public DummyDataSource(int dataItemCount) {
this.dataItemCount = dataItemCount;
}
// Implement other methods of the ICollection interface.
...
public IEnumerator GetEnumerator() {
return new DummyDataSourceEnumerator(dataItemCount);
}
private class DummyDataSourceEnumerator : IEnumerator {
private int count;
private int index;
public DummyDataSourceEnumerator(int count) {
this.count = count;
this.index = -1;
}
public object Current {
get {
return null;
}
}
// Define other methods of the IEnumerator interface.
}
}
[Visual Basic]
NotInheritable Friend Class DummyDataSource
Implements ICollection
Private dataItemCount As Integer
Public Sub New(dataItemCount As Integer)
Me.dataItemCount = dataItemCount
End Sub
' Implement other methods of the ICollection interface.
...
Public Function GetEnumerator() As IEnumerator Implements ICollection.GetEnumerator
Return New DummyDataSourceEnumerator(dataItemCount)
End Function
Private Class DummyDataSourceEnumerator
Implements IEnumerator
Private count As Integer
Private index As Integer
Public Sub New(count As Integer)
Me.count = count
Me.index = - 1
End Sub
Public ReadOnly Property Current() As Object Implements IEnumerator.Current
Get
Return Nothing
End Get
End Property
' Define other methods of the IEnumerator interface.
...
End Class
End Class
DummyDataSource can be used to define the CreateControlHierarchy method, as follows.
private void CreateControlHierarchy(bool useDataSource) {
IEnumerable dataSource = null;
int count = -1;
if (useDataSource == false) {
// ViewState must have a non-null value for ItemCount because this is checked
// by CreateChildControls.
count = (int)ViewState["ItemCount"];
if (count != -1) {
dataSource = new DummyDataSource(count);
}
}
else {
dataSource = this.dataSource;
}
if (dataSource != null) {
int index = 0;
count = 0;
foreach (object dataItem in dataSource) {
...
// Invoke a private helper method to create each item.
CreateItem(...);
count++;
index++;
}
}
if (useDataSource) {
// Save the number of items contained for use in round trips.
ViewState["ItemCount"] = ((dataSource != null) ? count : -1);
}
}
[Visual Basic]
Private Sub CreateControlHierarchy(useDataSource As Boolean)
Dim dataSource As IEnumerable = Nothing
Dim count As Integer = - 1
If useDataSource = False Then
' ViewState must have a non-null value for ItemCount because this is checked
' by CreateChildControls.
count = CInt(ViewState("ItemCount"))
If count <> - 1 Then
dataSource = New DummyDataSource(count)
End If
Else
dataSource = Me._dataSource
End If
If Not (dataSource Is Nothing) Then
Dim table As New Table()
Controls.Add(table)
Dim selectedItemIndex As Integer = SelectedIndex
Dim index As Integer = 0
count = 0
Dim dataItem As Object
For Each dataItem In dataSource
Dim itemType As ListItemType = ListItemType.Item
If index = selectedItemIndex Then
itemType = ListItemType.SelectedItem
Else
If index Mod 2 <> 0 Then
itemType = ListItemType.AlternatingItem
End If
End If
CreateItem(table, index, itemType, useDataSource, dataItem)
count += 1
index += 1
Next dataItem
End If
If useDataSource Then
' Save the number of items contained for use in round trips.
If Not (dataSource Is Nothing) Then
ViewState("ItemCount") = count
Else
ViewState("ItemCount") = -1
End If
End If
End Sub
The CreateItem method does the actual work of creating the template and binding the DataItem property to the data source. The following code fragment shows how the CreateItem method is implemented in the Templated Data-Bound Control Sample. Note that the CreateItem method is an implementation detail and is not defined in the .NET Framework.
private TemplatedListItem CreateItem(Table table, int itemIndex, ListItemType itemType, bool dataBind, object dataItem) {
TemplatedListItem item = new TemplatedListItem(itemIndex, itemType);
TemplatedListItemEventArgs e = new TemplatedListItemEventArgs(item);
if (itemTemplate != null) {
itemTemplate.InstantiateIn(item.Cells[0]);
}
if (dataBind) {
item.DataItem = dataItem;
}
OnItemCreated(e);
table.Rows.Add(item);
if (dataBind) {
item.DataBind();
OnItemDataBound(e);
item.DataItem = null;
}
return item;
}
[Visual Basic]
Private Function CreateItem(table As Table, itemIndex As Integer, itemType As ListItemType, dataBind As Boolean, dataItem As Object) As TemplatedListItem
Dim item As New TemplatedListItem(itemIndex, itemType)
Dim e As New TemplatedListItemEventArgs(item)
If Not (_itemTemplate Is Nothing) Then
_itemTemplate.InstantiateIn(item.Cells(0))
End If
If dataBind Then
item.DataItem = dataItem
End If
OnItemCreated(e)
table.Rows.Add(item)
If dataBind Then
item.DataBind()
OnItemDataBound(e)
item.DataItem = Nothing
End If
Return item
End Function
For a sample of a data-bound control that implements the steps discussed in this topic, see the Templated Data-Bound Control Sample.
See Also
Templated Data-Bound Control Sample