8.5 Validation and Data Binding
Essential ASP.NET with Examples in Visual Basic .NET
Authors: Onion F.
Published year: 2003
Pages: 75/94
Buy this book on amazon.com >>

8.5 Validation and Data Binding

Depending on the features of your custom controls, it may make sense to add support for either validation or data binding. This section describes how to build controls that support these two capabilities.

8.5.1 Supporting Validation

You can add validation support for custom controls that collect information from the user . For a control to support validation, it must be annotated with the ValidationProperty attribute, indicating which of its public properties should be tested with the validation algorithm, and when rendered in HTML, the value attribute of the rendered HTML must equate to the value that is to be validated (for client-side validation). Listing 8-36 shows a sample control to which validation controls could be applied, and Listing 8-37 shows a sample client page applying a validation control to our CustomTextBox control.

Listing 8-36 A Control That Supports Validation
<ValidationProperty("Text")> _
Public Class CustomTextBox
  Inherits Control

  Public Sub New()
    ViewState("Text") = ""
  End Sub

  Public Property Text As String
    Get
      Return CType(ViewState("Text"), String)
    End Get
    Set (value As String)
      ViewState("Text") = value
    End Set
  End Property

  Protected Overrides Sub Render(output As HtmlTextWriter)
    output.AddAttribute(HtmlTextWriterAttribute.Name, _
                        UniqueID)
    output.AddAttribute(HtmlTextWriterAttribute.Value, _
                        Text)
    output.RenderBeginTag(HtmlTextWriterTag.Input)
    output.RenderEndTag()
  End Sub
End Class
Listing 8-37 Client to Custom Control Supporting Validation
<%@ Page Language='VB' %>
<%@ Register TagPrefix='eadn'
    Namespace='EssentialAspDotNet.CustomControls'
    Assembly='Validation' %>
<html>
<body>
<form runat=server>
  <eadn:ValidatableControl id='ctb' runat='server' />
  <asp:RequiredFieldValidator runat='server'
       ControlToValidate='ctb'>*
  </asp:RequiredFieldValidator>

  <input type='submit' value='submit' />
</form>
</body>
</html>

8.5.2 Data-Bound Controls

Many controls in the WebControls hierarchy support the concept of data binding. You can add data binding support to your custom controls as well by exposing a DataSource property. When your control renders itself, it pulls data from the data source and uses it as part of its rendering.

To begin with, it is important to understand how clients expect data-bound controls to work. Typically, a client prepares a connection to a data source in the Load event of a Page class and connects that data source to the DataSource property that all data-bound controls provide. The data source could be a simple collection, such as an ArrayList ; a forward-only data reader, such as an IDataReader ; or a fully cached data source, such as a DataSet , DataView , or DataTable . As an author of data-bound controls, you need to accommodate all these possibilities. Because all the data-bound controls in the base class hierarchy retain their state across post-backs, clients expect any data-bound control you write to do the same, so that a control need be populated only the first time a page is accessed. Once the client has attached a data source to the control, she may need to set the DataTextField and DataValueField properties of the control to indicate which field from a tabular data source should be used. Note that if your control can render tabular data (rows with multiple columns ), there is no need to support these properties. Finally, once the data source, DataTextField , and DataValueField properties have been correctly populated, the client calls DataBind() , at which point your control's OnDataBinding method is called. Your control should iterate over the attached data source, saving the data locally within itself so that when your Render() method is called, you have the data to display. This interaction between a client page and a data-bound control is shown in Figure 8-5.

Figure 8-5. Interaction between a Page and a Data-Bound Control

graphics/08fig05.gif

8.5.3 Implementing a Data-Bound Control

The first step in creating a data-bound control is to define a DataSource property. This involves adding a public property called DataSource to your class of type object . Next you need to override the virtual function OnDataBinding , inherited from the Control base class. Your implementation of OnDataBinding should iterate across the data source that was assigned through your control's property, saving the data to a local data structure for future rendering. Note that it is not safe to assume that the data source will be around during your Render method, so you must take this step to ensure that you save the data that your control needs to be able to render itself during the OnDataBinding method call.

If your control expects only a list of items, not a tabular set of data, to be bound to it, you should expose another property, called DataTextField . This field will contain the index into a rowset that the client expects you to dereference when you perform the data binding. If you have the capability of associating values with the data items, you can also expose a property called DataValueField (most of the data-bound list controls in the base class hierarchy expose both of these fields). You should also be as accommodating as possible in what your control supports for data sources. All the data binding controls in the base class hierarchy support binding to any class that supports the IEnumerable or IList interface, plus they support binding directly to DataSets or DataTables by locating the default DataTable within a DataSet and the default DataView within a DataTable .

Clients of data-bound controls expect the controls to retain their state across post-backs. This lets page authors populate a control once if IsPostBack is false , and avoid additional round-trips to the database if the page is posted to again. As the author of a data bound control, you are responsible for making sure that your control's state is retained across a post-back. Typically, you do this by using the ViewState mechanism described earlier in this chapter. Because data-bound controls usually need to persist collections of data into view state, it is typically most efficient to override the LoadViewState and SaveViewState methods in your control to explicitly populate and retrieve collections of data from view state. Keep in mind that clients can always disable view state on your control, so even if you suspect it will be inefficient for clients to rely on state retention for your control, you should leave that decision to the client and support it nonetheless for consistency.

Listing 8-38 shows a sample control that supports data binding to all the different data source types with state retention. It renders itself as an item list and caches the contents of the data source in an ArrayList . This control defines two helper functions that should be useful for any implementer of data-bound controls. The first is GetResolvedDataSource , which takes the data source as an object and returns a reference to an IEnumerable interface. This function accounts for the fact that the data source may be a collection class, an IDataReader , a DataView , a DataTable , or a DataSet , and returns the enumeration interface on the correct element of the data source. The second helper function is GetDataItem , which takes an item pointed to by an enumerator and indexes it with the m_DataTextField value if is a rowset, or simply returns the object as a string if not. This is necessary to accommodate items stored in simple collections and items stored in tabular data sets.

Listing 8-38 A Data-Bound Control
Public Class DataBoundControl
  Inherits Control

  Private _cachedData As ArrayList = new ArrayList()

  Private _dataSource As Object
  Private _dataTextField As String
  Private _dataValueField As String

  Public Property DataTextField As String
    Get
      Return _dataTextField
    End Get
    Set (value As String)
      _dataTextField = value
    End Set
  End Property

  Public Property DataValueField As String
    Get
      Return _dataValueField
    End Get
    Set (value As String)
      _dataValueField = value
    End Set
  End Property

  Public Overrides Property DataSource As Object
    Get
      Return _dataSource
    End Get
    Set (value As Object)
      _dataSource = value
    End Set
  End Property

  Public Function GetResolvedDataSource(ds As Object) _
                  As IEnumerable

    Dim dv As DataView
    If ds Is IEnumerable Then
      Return CType(ds, IEnumerable)
    ElseIf ds is DataTable Then
      dv = CType(ds, DataTable).DefaultView
      Return CType(dv, IEnumerable)
    ElseIf ds is DataSet Then
      dv = CType(ds, DataSet).Tables(0).DefaultView
      Return CType(dv, IEnumerable)
    ElseIf ds is IList Then
      Return CType(CType(ds, IList), IEnumerable)
    Else
      return Nothing
    End If
  End Function

  Protected Function GetDataItem(item As Object) As String
    Dim ret As String
    If item Is DataRowView Then
      DataRowView drv = CType(item, DataRowView)
      ret = drv(_dataValueField).ToString();
    ElseIf item Is DbDataRecord Then
      Dim ddr As DbDataRecord = CType(item, DbDataRecord)
      ret = ddr(_dataValueField).ToString()
    Else
      ret = item.ToString()
    End If
    Return ret
  End Function

  Protected Overrides Sub OnDataBinding(e As EventArgs)

    MyBase.OnDataBinding(e)

    If Not DataSource Is Nothing Then
      Dim ds As IEnumerable
      ds = GetResolvedDataSource(_dataSource)
      Dim dataEnum As IEnumerator
      dataEnum = ds.GetEnumerator()
      Do While dataEnum.MoveNext()
        _cachedData.Add(GetDataItem(dataEnum.Current))
      Loop
    End If
  End Sub

  Protected Overrides Sub Render(htw As HtmlTextWriter)
    htw.RenderBeginTag(HtmlTextWriterTag.Ul) '<ul>
    Dim s As String
    For Each s in _cachedData
      htw.RenderBeginTag(HtmlTextWriterTag.Li) '<li>
      htw.Write(s)
      htw.RenderEndTag() '</li>
    Next s
    htw.RenderEndTag() '</ul>
  End Sub

  Protected Overrides Sub LoadViewState( _
                                  savedState As Object)

    If Not savedState Is Nothing

      ' Load State from the array of objects that
      ' was saved in SaveViewState
      Dim vState As Object() = CType(savedState, Object())
      If Not vState(0) Is Nothing Then
        MyBase.LoadViewState(vState(0))
      End If
      If Not vState(1) Is Nothing Then
        _cachedData = CType(vState(1), ArrayList)
      End If
    End If
  End Sub

  Protected Overrides Function SaveViewState() As Object

    Dim vState(2) As Object

    vState(0) = MyBase.SaveViewState()
    vState(1) = _cachedData

    Return vState
  End Function
End Class
Essential ASP.NET with Examples in Visual Basic .NET
Authors: Onion F.
Published year: 2003
Pages: 75/94
Buy this book on amazon.com >>