Creating DataBound Controls


Creating DataBound Controls

You can bind several of the standard ASP.NET controls to a data source. For example, you can bind a Repeater control to data sources, such as DataReaders , ArrayLists , and DataViews .

You too can create a control that supports binding to a data source. To create a DataBound control, you must do the following:

  • Add a DataSource property. This property typically implements the IEnumerable , ICollection , IList , or IListSource interface.

  • Add an OnDataBinding subroutine. This subroutine is invoked when the DataBind method is called.

You examine the DataSource property and OnDataBinding subroutine in the following sections.

NOTE

Classes that support the IEnumerable interface are required to have a method named GetEnumerator . The GetEnumerator method returns an enumerator that can be used to iterate through all the items in the underlying collection.


Implementing Different Data Sources

In most cases, a control's DataSource property should implement the IEnumerable interface, which is implemented by such classes as DataReader , DataView , and ArrayList . The IEnumerable interface exposes a method named GetEnumerator that enables you to walk through all the elements of a collection. If you simply want to display a list of items from a data source, you should implement this interface.

In some situations, however, you need to implement the ICollection interface, which enables you to work with the contents of a data source as a whole. For example, it includes a Count property, which enables you to get a total count of the number of items in the collection. The ICollection interface also has a CopyTo method, which copies the contents of a data source to an array.

If you're implementing a control, such as a content rotator control that enables you to randomly select an item from a data source, you need to use the ICollection interface. Otherwise, you cannot randomly select an item from the whole data source.

Standard ASP.NET controls ”such as Repeater , DataList , and DataGrid ”also support the IListSource interface. They support this interface so that they can be bound directly to a DataSet . For example, you can specify the data source for a Repeater control like this:

 
 rptRepeater.DataSource = dstDataSet rptRepeater.DataMember = "Products" 

The first statement binds the Repeater control to a DataSet . The second statement selects a particular table in the DataSet to bind to.

Implementing a Simple DataBound Control

For this example, you can start by creating a simple DataBound control that binds to an ArrayList . The control in Listing 29.7 has a DataSource property that implements the IEnumerable interface.

Listing 29.7 SimpleDataBound.vb
 Imports System Imports System.Web Imports System.Web.UI Imports System.Collections Namespace myControls Public Class SimpleDataBound Inherits Control Private _dataSource As IEnumerable Public Property DataSource As IEnumerable Get   Return _dataSource End Get Set   _dataSource = Value End Set End Property Protected Overrides Sub OnDataBinding( e As EventArgs )   Dim DataEnum As IEnumerator   Dim ltlLiteral As LiteralControl   If Not DataSource Is Nothing     Controls.Clear()     DataEnum = DataSource.GetEnumerator()     While ( DataEnum.MoveNext() )       ltlLiteral = New LiteralControl( DataEnum.Current.ToString() )       Controls.Add( ltlLiteral )       Controls.Add( New LiteralControl( "<br>" ) )     End While   End If End Sub End Class End Namespace 

The C# version of this code can be found on the CD-ROM.

The control in Listing 29.7, named SimpleDataBound , has a single public property named DataSource . This property reads and sets a private variable named _dataSource .

The control also has an OnDataBinding subroutine, which is executed whenever the control's DataBind() method is called.

Within the OnDataBinding subroutine, an enumerator named DataEnum is retrieved from the data source by calling the GetEnumerator method. The MoveNext method loops through all the values contained in the data source. Each item from the data source is transformed into a LiteralControl and added to the control's Controls collection. When the control is rendered, all the items from the data source are rendered.

The page in Listing 29.8 illustrates how you can bind a data source to this control.

Listing 29.8 DisplaySimpleDataBound.aspx
 <%@ Register TagPrefix="myControls" Namespace="myControls" Assembly="SimpleDataBound"%> <Script Runat="Server"> Sub Page_Load   Dim colArrayList As ArrayList   colArrayList = New ArrayList   colArrayList.Add( "Seattle" )   colArrayList.Add( "Paris" )   colArrayList.Add( "Banjarmasin" )   colArrayList.Add( "Tokyo" )   ctrlSimpleDataBound.DataSource = colArrayList   ctrlSimpleDataBound.DataBind() End Sub </Script> <html> <head><title>DisplaySimpleDataBound.aspx</title></head> <body> <form Runat="Server"> <myControls:SimpleDataBound   id="ctrlSimpleDataBound"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

In Listing 29.8, an ArrayList is bound to the custom control in the Page_Load subroutine. This ArrayList contains a list of cities. When the DataBind method is called, the OnDataBinding method of the SimpleDataBound control is executed, and the items from the ArrayList are added to the Controls collection of the custom control.

Using DataBound Controls and Templates

Typically, when you create DataBound controls, you use the control with a template. By implementing a template, you can format each of the data source items displayed by the control.

The control in Listing 29.9 contains a template named ItemTemplate .

Listing 29.9 DataBoundTemplate.vb
 Imports System Imports System.Web Imports System.Web.UI Imports System.Collections Namespace myControls <ParseChildren(true)> Public Class DataBoundTemplate Inherits Control   Private _dataSource As IEnumerable   Private _itemTemplate As ITemplate   Public Property DataSource As IEnumerable     Get       Return _dataSource     End Get     Set       _dataSource = Value     End Set   End Property   Protected Overrides Sub OnDataBinding( e As EventArgs )     Dim objDataEnum As IEnumerator     Dim objItem As DataBoundTemplateItem     If Not DataSource Is Nothing       Controls.Clear()       objDataEnum = DataSource.GetEnumerator()       While ( objDataEnum.MoveNext() )         objItem = New DataBoundTemplateItem( objDataEnum.Current )         ItemTemplate.InstantiateIn( objItem )         Controls.Add( objItem )       End While     End If   End Sub   <TemplateContainer(GetType(DataBoundTemplateItem))> _   Public Property ItemTemplate As ITemplate     Get       Return _itemTemplate     End Get     Set       _itemTemplate = value     End Set   End Property End Class Public Class DataBoundTemplateItem Inherits Control Implements INamingContainer   Private _DataItem As Object   Public Sub New( DataItem As Object )     MyBase.New()     _dataItem = DataItem   End Sub   Public ReadOnly Property DataItem As Object     Get       Return _dataItem     End Get   End Property End Class End Namespace 

The C# version of this code can be found on the CD-ROM.

In Listing 29.9, a control named DataBoundTemplate is declared. This control has a single template named ItemTemplate .

When you call the DataBind method on the DataBoundTemplate control, its OnDataBinding subroutine executes. This subroutine iterates through the items in the data source. For each item, a new control named DataBoundTemplateItem is created to represent the item.

Each element of the ItemTemplate is instantiated in the DataBoundTemplateItem control with the InstantiateIn method. The DataBoundTemplateItem control is then added to the Controls collection of the DataBoundTemplate control.

The ASP.NET page in Listing 29.10 demonstrates how you can bind the DataBoundTemplate control to a database table named Authors.

Listing 29.10 DisplayDataBoundTemplate.aspx
 <%@ Register TagPrefix="myControls" Namespace="myControls" Assembly="DataBoundTemplate"%> <%@ Import Namespace="System.Data.SqlClient" %> <Script Runat="Server"> Sub Page_Load   Dim conPubs As SqlConnection   Dim cmdSelect As SqlCommand   conPubs = New SqlConnection( "Server=LocalHost;UID=sa;PWD=secret;Database=Pubs" )   cmdSelect = New SqlCommand( "Select * From Authors", conPubs )   conPubs.Open()   ctrlDataBoundTemplate.DataSource = cmdSelect.ExecuteReader()   ctrlDataBoundTemplate.DataBind()   conPubs.Close() End Sub </Script> <html> <head><title>DisplayDataBoundTemplate.aspx</title></head> <body> <form Runat="Server"> <myControls:DataBoundTemplate   ID="ctrlDataBoundTemplate"   Runat="Server">   <ItemTemplate>     <li> <i> <%# Container.DataItem( "au_lname" ) %> </i>   </ItemTemplate> </myControls:DataBoundTemplate> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 29.10, a SqlDataReader is created to represent all the records from the Authors table. This SqlDataReader is bound to the DataBoundTemplate control.

The DataBoundTemplate control is declared with a template named ItemTemplate . This template formats each item in italics.

Using DataBound Controls and State

When you bind a data source to the standard ASP.NET controls, you typically need to bind the data source only when the page is first loaded. If the page is posted back to the server, the control maintains its state.

You can maintain the state of a control by taking advantage of a control's view state. Items added to view state are automatically preserved between postbacks in a hidden __VIEWSTATE form field. The control in Listing 29.11 demonstrates how you can store the items from a data source in a control's view state.

Listing 29.11 DataBoundState.vb
 Imports System Imports System.Web Imports System.Web.UI Imports System.Collections Namespace myControls <ParseChildren(true)> _ Public Class DataBoundState Inherits Control   Private _dataSource As IEnumerable   Private _itemTemplate As ITemplate   Public Property DataSource As IEnumerable     Get       Return _dataSource     End Get     Set       _dataSource = Value     End Set   End Property   Protected Overrides Sub OnDataBinding( e As EventArgs )     Dim objDataEnum As IEnumerator     Dim objItem As DataBoundStateItem     Dim intCounter As Integer     If Not DataSource Is Nothing       Controls.Clear()       ClearChildViewState()       objDataEnum = DataSource.GetEnumerator()       While ( objDataEnum.MoveNext() )         objItem = New DataBoundStateItem( objDataEnum.Current )         ItemTemplate.InstantiateIn( objItem )         Controls.Add( objItem )         intCounter += 1       End While       ViewState( "NumItems" ) = intCounter       ChildControlsCreated = True     End If   End Sub   Protected Overrides Sub CreateChildControls()     Dim objNumItems As Object     Dim intItemCount As Integer     Dim intCounter As Integer     Dim objItem As DataBoundStateItem     objNumItems = ViewState( "NumItems" )     If Not objNumItems = Nothing Then       Controls.Clear()       intItemCount = CInt( objNumItems)       For intCounter = 0 To intItemCount - 1         objItem  = New DataBoundStateItem( Nothing )         ItemTemplate.InstantiateIn( objItem )         Controls.Add( objItem )       Next     End If   End Sub   <TemplateContainer(GetType(DataBoundStateItem))> _   Public Property ItemTemplate As ITemplate     Get       Return _itemTemplate     End Get     Set       _itemTemplate = value     End Set   End Property End Class Public Class DataBoundStateItem Inherits Control Implements INamingContainer   Private _DataItem As Object   Public Sub New( DataItem As Object )     MyBase.New()     _DataItem = DataItem   End Sub   Public ReadOnly Property DataItem As Object     Get       Return _DataItem     End Get   End Property End Class End Namespace 

The C# version of this code can be found on the CD-ROM.

In the OnDataBinding subroutine in Listing 29.11, a count of the number of items from the data source is added to the control's view state. A new item, named NumItems , is assigned to the ViewState property. If NumItems can be retrieved from the control's ViewState property, the value of NumItems is used to determine the number of child controls to add to the Controls collection.

When each DataBoundStateItem control is added to the Controls collection, notice that each DataBoundStateItem is initialized with the value Nothing . The ASP.NET framework automatically retrieves the previous value of each control. You need only to instantiate each control.

Here's one final thing you should notice about Listing 29.11. At the end of the OnDataBinding subroutine, the ChildControlsCreated property is set to the value True . You set this value to prevent the CreateChildControls subroutine from executing and overwriting the controls added by the OnDataBinding subroutine.

The page in Listing 29.12 illustrates how you can use the DataBoundState custom control.

Listing 29.12 DisplayDataBoundState.aspx
 <%@ Register TagPrefix="myControls" Namespace="myControls" Assembly="DataBoundState"%> <%@ Import Namespace="System.Data.SqlClient" %> <Script Runat="Server"> Sub Page_Load   If Not IsPostBack Then     Dim conPubs As SqlConnection     Dim cmdSelect As SqlCommand     conPubs = New SqlConnection( "Server=LocalHost;UID=sa;PWD=secret;Database=Pubs" )     cmdSelect = New SqlCommand( "Select * From Authors", conPubs )     conPubs.Open()     ctrlDataBoundState.DataSource = cmdSelect.ExecuteReader()     ctrlDataBoundState.DataBind()     conPubs.Close()   End If End Sub </Script> <html> <head><title>DisplayDataBoundState.aspx</title></head> <body> <form Runat="Server"> <myControls:DataBoundState   ID="ctrlDataBoundState"   Runat="Server">   <ItemTemplate>     <li> <i> <%# Container.DataItem( "au_lname" ) %>   </ItemTemplate> </myControls:DataBoundState> <p> <asp:Button   Text="Reload Page!"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The IsPostBack property in the Page_Load subroutine in Listing 29.12 detects whether this is the first time the page has been loaded. The data source is bound to the control only when IsPostBack has the value False .

If you click the Reload Page! button, the page is reloaded. However, DataBoundState continues to display all the data from the data source. The values of all the items are preserved in view state.

Binding a Custom Control to a DataSet

You can bind all the standard ASP.NET controls to a number of different types of data sources. For example, you can bind a Repeater control to an ArrayList like this:

 
 colArrayList = New ArrayList colArrayList.Add( "Hello World!" ) rptRepeater.DataSource = colArrayList rptRepeater.DataBind() 

You also can bind a Repeater to a DataReader like this:

 
 rptRepeater.DataSource = dtrDataReader rptRepeater.DataBind() 

You can bind a Repeater to a DataView like this:

 
 rptRepeater.DataSource = dstDataSet.Tables( "Products" ).DefaultView() rptRepeater.DataBind() 

You can bind a Repeater directly to a DataSet like this:

 
 rptRepeater.DataSource = dstDataSet rptRepeater.DataBind() 

If a DataSet contains multiple tables, you can select a particular table to bind to like this:

 
 rptRepeater.DataSource = dstDataSet rptRepeater.DataMember = "titles" rptRepeater.DataBind() 

To create a custom control that works like the standard ASP.NET controls, you need to support all these very different data-binding options. In this section, you learn how to add a function to a custom control that enables it to bind to any standard data source.

The custom control, named DataBoundDataSet , is contained in Listing 29.13.

Listing 29.13 DataBoundDataSet.aspx
 Imports System Imports System.Web Imports System.Web.UI Imports System.Collections Imports System.Data Namespace myControls <ParseChildren(true)> _ Public Class DataBoundDataSet Inherits Control   Private _dataSource As Object   Private _dataMember As String = String.Empty   Private _itemTemplate As ITemplate   Public Property DataSource As Object     Get       Return _dataSource     End Get     Set       _dataSource = Value     End Set   End Property   Public Property DataMember As String     Get       Return _dataMember     End Get     Set       _dataMember = Value     End Set   End Property   Private Function GetDataSource( DataSource, DataMember ) As IEnumerable     If TypeOf DataSource Is IEnumerable Then       Return DataSource     Else If TypeOf DataSource Is DataSet Then       If DataMember <> String.Empty Then         Return DataSource.Tables( DataMember ).DefaultView       Else         Return DataSource.Tables( 0 ).DefaultView       End If     Else       Throw New ArgumentException( "Invalid data source!" )     End If   End Function   Protected Overrides Sub OnDataBinding( e As EventArgs )     Dim objDataEnum As IEnumerator     Dim objItem As DataBoundDataSetItem     Dim intCounter As Integer     If Not DataSource Is Nothing       Controls.Clear()       ClearChildViewState()       objDataEnum = GetDataSource( _dataSource, _dataMember ).GetEnumerator()       While ( objDataEnum.MoveNext() )         objItem = New DataBoundDataSetItem( objDataEnum.Current )         ItemTemplate.InstantiateIn( objItem )         Controls.Add( objItem )         intCounter += 1       End While       ViewState( "NumItems" ) = intCounter       ChildControlsCreated = True     End If   End Sub   Protected Overrides Sub CreateChildControls()     Dim objNumItems As Object     Dim intItemCount As Integer     Dim intCounter As Integer     Dim objItem As DataBoundDataSetItem     objNumItems = ViewState( "NumItems" )     If Not objNumItems = Nothing Then       Controls.Clear()       intItemCount = CInt( objNumItems)       For intCounter = 0 To intItemCount - 1         objItem  = New DataBoundDataSetItem( Nothing )         ItemTemplate.InstantiateIn( objItem )         Controls.Add( objItem )       Next     End If   End Sub   <TemplateContainer(GetType(DataBoundDataSetItem))> _   Public Property ItemTemplate As ITemplate     Get       Return _itemTemplate     End Get     Set       _itemTemplate = value     End Set   End Property End Class Public Class DataBoundDataSetItem Inherits Control Implements INamingContainer   Private _DataItem As Object   Public Sub New( DataItem As Object )     MyBase.New()     _DataItem = DataItem   End Sub   Public ReadOnly Property DataItem As Object   Get     Return _DataItem   End Get   End Property End Class End Namespace 

The C# version of this code can be found on the CD-ROM.

Listing 29.13 contains a private function named GetDataSource that has two parameters: the data source and a data member. The function does the best it can to return an instance of a data source that supports the IEnumerable interface based on these parameters.

If you pass an ArrayList or DataView to the function, the function simply returns the data source cast as IEnumerable . If you pass a DataSet to the function, the function does its best to return a DataTable from the DataSet . For example, if you pass a DataSet without providing a data member parameter, the function returns the first DataTable in the DataSet .

NOTE

The standard Microsoft data binding controls, such as the Repeater and DataGrid controls, use a private, undocumented function named GetResolvedDataSource . This function is a member of the private DataSourceHelper class.

Microsoft's GetResolvedDataSource function is slightly more sophisticated than the GetDataSource function. It uses methods of the IListSource interface to retrieve an enumerator from a DataSet .


Since the GetDataSource function explicitly refers to the DataSet class, you need to compile the control with the following statement:

 
 vbc /t:library /r:System.dll,System.Web.dll,System.Data.dll,System.Xml.dll DataBoundDataSet.vb 

You need to add a reference to both the System.Data.dll and System.Xml.dll assemblies when working with DataSets in a control.

The page in Listing 29.14 illustrates how you can bind a number of different types of data sources to a custom control that uses the GetDataSource function (see Figure 29.2).

Listing 29.14 DisplayDataBoundDataSet.aspx
 <%@ Register TagPrefix="myControls" Namespace="myControls" Assembly="DataBoundDataSet"%> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <Script Runat="Server"> Sub Page_Load   If Not IsPostBack Then     Dim conPubs As SqlConnection     Dim dadAdapter As SqlDataAdapter     Dim dstDataSet As DataSet     Dim cmdSelect As SqlCommand     Dim colArrayList As ArrayList     conPubs = New SqlConnection( "Server=LocalHost;UID=sa;PWD=secret;Database=Pubs" )     conPubs.Open()     ' Create a dataset with 2 DataTables     dadAdapter = New SqlDataAdapter( "Select top 3 * From Titles", conPubs )     dstDataSet = New DataSet()     dadAdapter.Fill( dstDataSet, "Titles" )     dadAdapter.SelectCommand = New SqlCommand( "Select top 3 * From Authors", conPubs )     dadAdapter.Fill( dstDataSet, "Authors" )     ' Bind with DataSet and no DataMember     ctrlDataBound1.DataSource = dstDataSet     ctrlDataBound1.DataBind()     ' Bind with DataSet and explicit DataMember     ctrlDataBound2.DataSource = dstDataSet     ctrlDataBound2.DataMember = "Authors"     ctrlDataBound2.DataBind()     ' Bind to DataReader     cmdSelect = New SqlCommand( "Select top 3 * From Titles", conPubs )     ctrlDataBound3.DataSource = cmdSelect.ExecuteReader()     ctrlDataBound3.DataBind()     ' Bind to ArrayList     colArrayList = New ArrayList     colArrayList.Add( "Milk" )     colArrayList.Add( "Toast" )     ctrlDataBound4.DataSource = colArrayList     ctrlDataBound4.DataBind()     conPubs.Close   End If End Sub </Script> <html> <head><title>DisplayDataBoundDataSet.aspx</title></head> <body> <form Runat="Server"> <h3>DataSet and no DataMember</h2> <myControls:DataBoundDataSet   ID="ctrlDataBound1"   Runat="Server">   <ItemTemplate>     <li> <i><%# Container.DataItem( "Title" ) %></i>   </ItemTemplate> </myControls:DataBoundDataSet> <h3>DataSet with DataMember</h2> <myControls:DataBoundDataSet   ID="ctrlDataBound2"   Runat="Server">   <ItemTemplate>     <li> <i><%# Container.DataItem( "au_lname" ) %></i>   </ItemTemplate> </myControls:DataBoundDataSet> <h3>DataReader</h2> <myControls:DataBoundDataSet   ID="ctrlDataBound3"   Runat="Server">   <ItemTemplate>     <li> <i><%# Container.DataItem( "Title" ) %></i>   </ItemTemplate> </myControls:DataBoundDataSet> <h3>ArrayList</h2> <myControls:DataBoundDataSet   ID="ctrlDataBound4"   Runat="Server">   <ItemTemplate>     <li> <i><%# Container.DataItem %></i>   </ItemTemplate> </myControls:DataBoundDataSet> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

Figure 29.2. Binding to multiple data sources.

graphics/29fig02.jpg

In the Page_Load subroutine in Listing 29.14, the DataBoundDataSet control is bound to four different types of data sources. The control is bound to a DataSet , a DataSet and DataMember , a DataReader , and an ArrayList .



ASP.NET Unleashed
ASP.NET 4 Unleashed
ISBN: 0672331128
EAN: 2147483647
Year: 2003
Pages: 263

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net