
Implementing a Custom Data-Bound Control in ASP.NET 2.0
The following table summarizes the steps that are specific to implementing a custom data-bound server control in ASP.NET 2.0. After the table, you will find more detailed information about each of the implementation steps.
Step
|
Step Summary
|
|---|
Choose a data-bound control base class.
|
Extend an existing data-bound control class that already provides many of the features you need, or derive from one of the base data-bound control classes.
|
Expose data-binding properties.
|
Configure your custom control to expose data-binding properties that are in addition to the minimum required data-binding properties exposed by base data-bound control classes.
|
Initiate data retrieval.
|
Retrieve the data that the page developer assigned to your control by doing the following:
Override the PerformSelect method of the base data-bound control class.
Call the GetData method of the base data-bound control class to retrieve the control's data-source view
Call the Select method of the DataSourceView object to initiate data retrieval and specify the callback method that will receive the data.
|
Handle the retrieved data.
|
Provide the callback method that you specified as a parameter in the Select method. The callback method must have a single parameter of type IEnumerable to receive the data. If any processing of the data is required by your control, it should occur within this callback method.
|
Create the UI objects representing the data
|
Provide an override of the PerformDataBinding method. You must execute the following tasks within this method:
Call the PerformDataBinding method to allow any other code relying on this method to execute.
Enumerate through the data collection and create any child controls that will represent the data in the UI display.
|
Choose a Data-Bound Control Base Class
Control developers can extend one of the available data-bound control base classes to create a custom data-bound control. The following table provides a list of data-bound base classes that are available in ASP.NET 2.0. Review the descriptions of each class and then determine which base class best fits the requirements of your custom data-bound control. For more detailed information about each base class for data-bound controls and implementation examples, see the reference documentation for each class. For more information about data-bound controls provided by ASP.NET, see ASP.NET Data-Bound Web Server Controls Overview.
Provided Data-Binding Properties
The base data-bound control classes already provide the exposed data-binding properties needed to enable a page developer to bind a data source to a control. No additional work is needed on the part of the control author. By deriving from DataBoundControl, for example, a custom control inherits three exposed data-binding properties: DataSourceID, DataSource, and DataMember. A page developer can then specify the source of the data to which the control will bind by setting the value of either the DataSource or the DataSourceID properties.
The DataSourceID property enables the page developer to specify the ID of a control that represents the data source from which the data-bound control retrieves its data.
The DataSource property enables the page developer to bind a data-bound control directly to data-objects of these two types:
An object that implements the IEnumerable interface, such as an Array, ArrayList, or Hashtable object.
An object that implements the IListSource interface, such as a DataSet object.
Additional data-binding properties, such as the DataMember property, allow the page developer to specify the portion of the data collection to which the control should bind.
For more information about the exposed data-binding properties provided by each data-bound control class or data-bound base class, see the reference documentation for each class.
Exposing Custom Data-Binding properties
When exposing your own custom data-binding properties in a custom control or overriding existing data-binding properties, you should call the OnDataPropertyChanged method if the control has already been initialized. This forces the data-bound control to rebind the data so it can use the new data-binding property setting.
The following code example shows a property that belongs to a derived data-bound control class. The example demonstrates how a data-bound control can call the OnDataPropertyChanged method if a property that identifies a data source is changed after the data-bound control is initialized.
Inherits DataBoundControl
Public Property DataTextField() As String
Get
Dim o As Object = ViewState("DataTextField")
If o Is Nothing Then
Return String.Empty
Else
Return CStr(o)
End If
End Get
Set(ByVal value As String)
ViewState("DataTextField") = value
If (Initialized) Then
OnDataPropertyChanged()
End If
End Set
End Property
public class SimpleDataBoundColumn : DataBoundControl
{
public string DataTextField
{
get
{
object o = ViewState["DataTextField"];
return ((o == null) ? string.Empty : (string)o);
}
set
{
ViewState["DataTextField"] = value;
if (Initialized)
{
OnDataPropertyChanged();
}
}
}
Initiate Data Retrieval
Data retrieval is initiated within an override of the PerformSelect method inherited by your control's base data-bound control. Within this override you call to retrieve the data and specify a callback method that will handle the data once it is returned.
To retrieve data, complete the following tasks in the overridden PerformSelect method:
Determine if the page developer used the DataSource property or the DataSourceID property to set the data to be bound to the control. This is done by verifying the IsBoundUsingDataSourceID property. For example, a false setting for the IsBoundUsingDataSourceID property indicates that the DataSource property was used to specify the data source.
If the page developer set the DataSource property, then an extra step is required: Call the OnDataBinding method.
Call the GetData method to retrieve the DataSourceView object associated with the data-bound control.
Call the Select method of the retrieved DataSourceView to initiate data retrieval and specify the callback method that will handle the retrieved data. The Select method operates asynchronously, so other processing is allowed while the data is being retrieved.
Indicate the completion of the PerformSelect tasks by setting the RequiresDataBinding property to false and then calling the MarkAsDataBound method.
Raise the OnDataBound event.
The following code example illustrates the preceding data-retrieval tasks as completed within an override of the PerformSelect method.
Protected Overrides Sub PerformSelect()
' Call OnDataBinding here if bound to a data source using the
' DataSource property (instead of a DataSourceID), because the
' databinding statement is evaluated before the call to GetData.
If Not IsBoundUsingDataSourceID Then
OnDataBinding(EventArgs.Empty)
End If
' The GetData method retrieves the DataSourceView object from
' the IDataSource associated with the data-bound control.
GetData().Select(CreateDataSourceSelectArguments(), _
AddressOf OnDataSourceViewSelectCallback)
' The PerformDataBinding method has completed.
RequiresDataBinding = False
MarkAsDataBound()
' Raise the DataBound event.
OnDataBound(EventArgs.Empty)
End Sub
protected override void PerformSelect()
{
// Call OnDataBinding here if bound to a data source using the
// DataSource property (instead of a DataSourceID), because the
// databinding statement is evaluated before the call to GetData.
if (!IsBoundUsingDataSourceID)
{
this.OnDataBinding(EventArgs.Empty);
}
// The GetData method retrieves the DataSourceView object from
// the IDataSource associated with the data-bound control.
GetData().Select(CreateDataSourceSelectArguments(),
this.OnDataSourceViewSelectCallback);
// The PerformDataBinding method has completed.
RequiresDataBinding = false;
MarkAsDataBound();
// Raise the DataBound event.
OnDataBound(EventArgs.Empty);
}
Handle the Retrieved Data
Create your own callback method to accept the retrieved data. This is the callback method you specified when the Select method was called within the override of the PerformSelect method. The callback method is required to contain only a single parameter of the type IEnumerable. In your callback method you can do any processing of the returned data, if required by your control. As a last step, call the PerformDataBinding method.
The following code example illustrates a callback method that has a parameter of type IEnumerable and calls the PerformDataBinding method.
Private Sub OnDataSourceViewSelectCallback(ByVal retrievedData As IEnumerable)
' Call OnDataBinding only if it has not already been
' called in the PerformSelect method.
If IsBoundUsingDataSourceID Then
OnDataBinding(EventArgs.Empty)
End If
' The PerformDataBinding method binds the data in the
' retrievedData collection to elements of the data-bound control.
PerformDataBinding(retrievedData)
End Sub
private void OnDataSourceViewSelectCallback(IEnumerable retrievedData)
{
// Call OnDataBinding only if it has not already been
// called in the PerformSelect method.
if (IsBoundUsingDataSourceID)
{
OnDataBinding(EventArgs.Empty);
}
// The PerformDataBinding method binds the data in the
// retrievedData collection to elements of the data-bound control.
PerformDataBinding(retrievedData);
}
Create the UI Objects Representing the Data
Within an override of the PerformDataBinding method, you create the child controls that will represent the data. The data collection is enumerated, and the child controls are created and their properties set based on each data item. By adding the new child controls to the control's Controls collection, the child controls will be rendered for you. The control hierarchy renders during the control's inherited Render method. You might choose to override the Render method to do special rendering required by your custom control, such as including additional HTML elements or special rendering for display during design mode.
To create the UI objects representing the data, override the PerformDataBinding method and complete the following tasks:
Call the PerformDataBinding method to allow any other code relying on this method to execute.
Enumerate through the data collection and create any child controls that will represent the data in the UI display. Add each child control to the control's collection by calling the control's Add method.
Protected Overrides Sub PerformDataBinding(ByVal retrievedData As IEnumerable)
MyBase.PerformDataBinding(retrievedData)
' Verify data exists.
If Not (retrievedData Is Nothing) Then
Dim tbl As New Table()
Dim row As TableRow
Dim cell As TableCell
Dim dataStr As String = String.Empty
Dim dataItem As Object
For Each dataItem In retrievedData
' If the DataTextField was specified get the data
' from that field, otherwise get the data from the first field.
If DataTextField.Length > 0 Then
dataStr = DataBinder.GetPropertyValue(dataItem, DataTextField, Nothing)
Else
Dim props As PropertyDescriptorCollection = TypeDescriptor.GetProperties(dataItem)
If props.Count >= 1 Then
If Nothing <> props(0).GetValue(dataItem) Then
dataStr = props(0).GetValue(dataItem).ToString()
End If
End If
End If
row = New TableRow()
tbl.Rows.Add(row)
cell = New TableCell()
cell.Text = dataStr
row.Cells.Add(cell)
Next dataItem
Controls.Add(tbl)
End If
End Sub
End Class
End Namespace
protected override void PerformDataBinding(IEnumerable retrievedData)
{
base.PerformDataBinding(retrievedData);
// Verify data exists.
if (retrievedData != null)
{
Table tbl = new Table();
TableRow row;
TableCell cell;
string dataStr = String.Empty;
foreach (object dataItem in retrievedData)
{
// If the DataTextField was specified get the data
// from that field, otherwise get the data from the first field.
if (DataTextField.Length > 0)
{
dataStr = DataBinder.GetPropertyValue(dataItem,
DataTextField, null);
}
else
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(dataItem);
if (props.Count >= 1)
{
if (null != props[0].GetValue(dataItem))
{
dataStr = props[0].GetValue(dataItem).ToString();
}
}
}
row = new TableRow();
tbl.Rows.Add(row);
cell = new TableCell();
cell.Text = dataStr;
row.Cells.Add(cell);
}
this.Controls.Add(tbl);
}
}