Developing Custom Data-Bound Web Server Controls

ASP.NET data-bound Web server controls provide a user interface (UI) for a data source representing a collection of records or items. The GridView Web Server Control Overview, DataList Web Server Control Overview, and Repeater Web Server Control Overview server controls are examples of data-bound Web server controls. For more information about data-bound controls included with ASP.NET, see Data-Bound Web Server Controls.

In ASP.NET version 2.0, a new data source model enables you to bind data-bound controls to source controls, permitting common data operations — such as paging, sorting, and deleting — to be moved out of the data-bound control itself. This model yields a more flexible data-bound control for page developers and increases the level of reusability.

This topic introduces the basic steps that are required to implement a custom ASP.NET 2.0 data-bound server control. For more information about the general architecture and implementations of custom controls, see Developing Custom ASP.NET Server Controls and Walkthrough: Developing and Using a Custom Web Server Control. For more information about developing ASP.NET 1.1 compatible data-bound controls, see Developing Custom Data-Bound Web Server Controls for ASP.NET 1.1 and Walkthrough: Creating a Custom Data-Bound ASP.NET Web Control for ASP.NET 1.1. For more information about data source controls, see Data Source Controls Overview.

When to Create a Custom Data-Bound Control

Before creating your own custom data-bound control, review the capabilities of the data-bound controls already provided with ASP.NET. Existing controls might meet your needs, or you might decide to create a custom control that extends an existing control which already provides many of the features you need. For more information about data-bound controls provided with ASP.NET see, Data-Bound Web Server Controls.

Here are some reasons you might decide to create a custom data-bound control:

  • Your particular needs require custom UI, custom data-sorting features, or custom data-editing features that are not available in existing data-bound controls.

  • You want to create a custom data-bound control that that is pre-compiled and redistributable.

  • You want to extend the features of a data-bound control that is already provided with ASP.NET.

  • You want to create a data-bound control with a custom designer that fits your specific needs.

Basic Functionality of a Custom Data-Bound Control

By deriving from one of the data-bound control base classes, such as the DataBoundControl class or the CompositeDataBoundControl class, your custom data-bound control automatically inherits many built-in features, including the following:

  • Data source support for the IDataSource interface and its related DataSourceView class, which provides named views of data, the ability to query the types of operations supported by the data store, and on-demand data loading functionality.

  • Data source support for the generic IEnumerable or IListSource interfaces.

  • Support for data-binding expressions, which enables page developers to create bindings between an exposed, specially marked property of your control and a data source. For more information about data-binding expressions, see Data-Binding Expressions Overview.

  • Support for automatic data binding that invokes data-binding logic as late as possible during the page life cycle and only when needed, rather than on each postback. After a page performs the first data-binding request, subsequent requests attempt to retrieve the data from view state. This enhances performance by avoiding the need to reconnect to the data source on every request.

Utilizing Available Design Time Features

There are design-time features available to all Web server controls that you might want to consider for your custom data-bound control. You can create a designer class and control templates for your custom control. These features are invoked when your control is used at design time on a visual design surface, such as Design view in Visual Studio.

Creating a control designer can greatly increase your custom control’s usability at design time by providing a design-time interface that enables page developers to customize control properties. For an overview of ASP.NET control designers, see ASP.NET Control Designers Overview. For examples, see HierarchicalDataBoundControlDesigner and Walkthrough: Creating a Basic Control Designer for a Web Server Control.

By creating a templated control, you provide page developers the flexibility to specify the controls and markup that define control's user interface. For an example of a custom templated control, see Templated Server Control Example.

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 Data-Bound Web Server Controls.

Class

Description

BaseDataBoundControl

  • This is the base class for all data-bound controls, which performs the data binding and validation of any bound data.

DataBoundControl

This is the base data-bound class for building standard data-bound controls. DataBoundControl does the following:

  • Provides basic implementation shared by all data-bound controls.

  • Contains the logic to communicate with data source controls and data containers.

  • Serves as the basis for data-bound controls, such as TreeView and ListControl.

ListControl

  • This is the base class for list controls and extends the DataBoundControl class. It provides an Items collection and advanced layout rendering capabilities.

CompositeDataBoundControl

  • This is the base class that extends the DataBoundControl class, providing the following functionality:

  • Implements the typical code required by composite controls, including the code that restores the control's child control hierarchy from view state after a postback is made.

  • Binds to an IEnumerable data source and enumerates the data to build a control tree.

  • Serves as the basis for data-bound controls, such as GridView and DetailsView.

HierarchicalDataBoundControl

  • This is the base class for controls that display their data in hierarchical form. It serves as the basis for data-bound tree-based controls, such as TreeView and Menu.

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:

  1. 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.

  2. If the page developer set the DataSource property, then an extra step is required: Call the OnDataBinding method.

  3. Call the GetData method to retrieve the DataSourceView object associated with the data-bound control.

  4. 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.

  5. Indicate the completion of the PerformSelect tasks by setting the RequiresDataBinding property to false and then calling the MarkAsDataBound method.

  6. 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:

  1. Call the PerformDataBinding method to allow any other code relying on this method to execute.

  2. 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.

  • The following code example illustrates overriding the PerformDataBinding method. The PerformDataBinding method is called to allow any other code relying on this method to execute. The data collection is enumerated and child controls are created to represent the data in the UI display.
        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); 
    }
}

Building Your Custom Server Control

For information about building your custom data-bound Web server control and using it in a Web page, see Building the Custom Server Control Examples.

See Also

Tasks

Walkthrough: Creating a Custom Data-Bound ASP.NET Web Control for ASP.NET 2.0

Walkthrough: Developing and Using a Custom Web Server Control

Reference

HierarchicalDataBoundControlDesigner

Concepts

Data-Bound Web Server Controls

Metadata Attributes for Custom Server Controls

ASP.NET Control Designers Overview

Other Resources

Developing Custom ASP.NET Server Controls