
Creating the SimpleDataBoundColumn Class
In this section, you will create the data-bound control class that extends the DataBoundControl class. The new control displays the bound data in a one-column table.
To create the SimpleDataBoundColumn class
In the App_Code folder, create a class named SimpleDataBoundColumn.cs or SimpleDataBoundColumn.vb.
Add the following code to your class file:
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Namespace Samples.AspNet.Controls.VB
<AspNetHostingPermission(SecurityAction.Demand, _
Level:=AspNetHostingPermissionLevel.Minimal), _
AspNetHostingPermission(SecurityAction.InheritanceDemand, _
Level:=AspNetHostingPermissionLevel.Minimal)> _
Public Class SimpleDataBoundColumn
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
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
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
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
using System;
using System.Collections;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Samples.AspNet.Controls.CS
{
[AspNetHostingPermission(SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level = AspNetHostingPermissionLevel.Minimal)]
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();
}
}
}
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);
}
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);
}
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);
}
}
}
}
Code Discussion
The SimpleDataBoundColumn class derives from the base DataBoundControl data-binding class. Deriving from this base class provides the DataSourceID, DataSource, and DataMember data-binding properties. These properties enable a page developer to specify the data source and a specific data member to bind to this custom control.
To illustrate how to add custom data-binding properties, a DataTextField property has been added to the SimpleDataBoundColumn class. When the page developer sets the DataTextField property, the new value is stored in view state. The code in the property definition also calls the OnDataPropertyChanged method if the control has already been initialized. This forces the data-bound control to re-bind the data so that the control can use the new data-binding property setting.
The overridden DataBind method is required, and it includes logic to enumerate the object in the associated data source and create the child controls. The following tasks must be performed in ASP.NET data-bound controls in the overridden DataBind method, as shown in the SimpleDataBoundColumn class:
The IsBoundUsingDataSourceID property is checked for a value of false to determine whether the data source is specified in the DataSource property.
If the data to be bound is specified in the DataSource property, the OnDataBinding method is called to bind the data member that is specified in the DataSource property.
The GetData method is called to retrieve the DataSourceView object that is associated with the data-bound control.
The Select method of the retrieved DataSourceView object is called to initiate data retrieval and specify the OnDataSourceViewSelectCallback callback method that will handle the retrieved data.
To indicate that the data-retrieval tasks of the PerformSelect method are complete, the RequiresDataBinding property is set to false and then the MarkAsDataBound method is called.
The OnDataBound event is raised.
The OnDataSourceViewSelectCallback method receives the retrieved data. This callback method must accept a single parameter of type IEnumerable. Any data processing should occur here if the custom control requires it. In this walkthrough, the custom control uses the data as it is. Therefore, no additional data processing occurs in this example. As a last step, the PerformDataBinding method is called to start the data-binding process.
In the override of the PerformDataBinding method, all child controls are created that will represent the data. In this example, the data collection is enumerated and a new TableCell object is created for each data item. If the DataTextField property is set, the property value is used to determine which data field is bound to the cell's Text property. Otherwise, the first field of the data item is used.
The parent Table control is added to the custom SimpleDataBoundColumn control's Controls collection. Any control that is added to the control's Controls collection is automatically rendered when the inherited Render method executes.
For more information about the required implementations of a data-bound Web server control, see Developing Custom Data-Bound Web Server Controls for ASP.NET 2.0.
Creating a Tag Prefix
A tag prefix is the prefix, such as "asp" in <asp:Table />, that appears before a control's type name when the control is created declaratively in a page. To enable your control to be used declaratively in a page, ASP.NET requires a tag prefix that is mapped to your control's namespace. A page developer can provide a tag prefix/namespace mapping by adding an @ Register directive on each page that uses the custom control, as in the following example:
<%@ Register TagPrefix="aspSample"
Namespace="Samples.AspNet.Controls.CS"%>
<%@ Register TagPrefix="aspSample"
Namespace="Samples.AspNet.Controls.VB"%>
Note: |
|---|
In this example, the @
Register directive does not contain an assembly attribute that specifies the name of the control assembly. When the assembly attribute is missing, ASP.NET infers that the assembly is dynamically compiled from source files in the App_Code directory.
|
As an alternative to using the @ Register directive in each .aspx page, the page developer can specify the tag prefix and namespace mapping in the Web.config file. This is useful if the custom control will be used in multiple pages in a Web application. The following procedure describes how to specify the tag prefix mapping in the Web.config file.
To add a tag prefix mapping in the Web.config file
If the site does not already contain a Web.config file, create one in the Web site's root folder.
If you created a new Web.config file, copy the following XML markup to the file and save the file. If your site already had a Web.config file, add the following highlighted element to it.
Note: |
|---|
The tag prefix entry must be a child of the
controls section, which must be under the pages section, which in turn must be a child of system.web.
|
<?xml version="1.0"?>
<configuration>
<system.web>
<pages>
<controls>
<add tagPrefix="aspSample"
namespace="Samples.AspNet.Controls.cs" />
</controls>
</pages>
</system.web>
</configuration>
<?xml version="1.0"?>
<configuration>
<system.web>
<pages>
<controls>
<add tagPrefix="aspSample"
namespace="Samples.AspNet.Controls.vb" />
</controls>
</pages>
</system.web>
</configuration>
The highlighted section shows a tag prefix entry that maps the tag prefix "aspSample" to the namespace Samples.AspNet.Controls.CS or Samples.AspNet.Controls.VB.
After you have specified the tag prefix mapping in the configuration file, you can use the SimpleDataBoundColumn control declaratively (as <aspSample:SimpleDataBoundColumn />) in any page in the Web site.
Creating a Page to Use the Custom Data-Bound Control
In this section of the walkthrough you will create page markup that lets you test the custom data-bound control.
To create a page that uses the custom data-bound control
In the Web site, create a file named TestSimpleDataBoundColumn.aspx.
Copy the following markup into the TestSimpleDataBoundColumn.aspx file and save the file.
<%@ Page Language="VB" Trace="true"%>
<%@ Register TagPrefix="aspSample" Namespace="Samples.AspNet.Controls.VB" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<script runat="server">
Function CreateDataSource() As ICollection
' Create sample data for the DataList control.
Dim dt As DataTable = New DataTable()
dim dr As DataRow
' Define the columns of the table.
dt.Columns.Add(New DataColumn("IntegerValue", GetType(Int32)))
dt.Columns.Add(New DataColumn("StringValue", GetType(String)))
dt.Columns.Add(New DataColumn("CurrencyValue", GetType(Double)))
dt.Columns.Add(New DataColumn("ImageValue", GetType(String)))
' Populate the table with sample values.
Dim i As Integer
For i = 0 To 8
dr = dt.NewRow()
dr(0) = i
dr(1) = "Description for item " & i.ToString()
dr(2) = 1.23 * (i + 1)
dr(3) = "Image" & i.ToString() & ".jpg"
dt.Rows.Add(dr)
Next i
Dim dv As DataView = New DataView(dt)
Return dv
End Function
Sub Page_Load(sender As Object, e As EventArgs)
' Load sample data only once, when the page is first loaded.
If Not IsPostBack Then
simpleDataBoundColumn1.DataSource = CreateDataSource()
simpleDataBoundColumn1.DataBind()
End If
End Sub
</script>
<head runat="server">
<title>SimpleDataBoundColumn test page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<aspSample:SimpleDataBoundColumn runat="server" id="simpleDataBoundColumn1" DataTextField="CurrencyValue" BorderStyle="Solid"></aspSample:SimpleDataBoundColumn>
</div>
</form>
</body>
</html>
<%@ Page Language="C#" Trace="true"%>
<%@ Register TagPrefix="aspSample" Namespace="Samples.AspNet.Controls.CS" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<script runat="server">
ICollection CreateDataSource()
{
DataTable dt = new DataTable();
DataRow dr;
dt.Columns.Add(new DataColumn("IntegerValue", typeof(Int32)));
dt.Columns.Add(new DataColumn("StringValue", typeof(string)));
dt.Columns.Add(new DataColumn("CurrencyValue", typeof(double)));
for (int i = 0; i < 9; i++)
{
dr = dt.NewRow();
dr[0] = i;
dr[1] = "Item " + i.ToString();
dr[2] = 1.23 * (i + 1);
dt.Rows.Add(dr);
}
DataView dv = new DataView(dt);
return dv;
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
simpleDataBoundColumn1.DataSource = CreateDataSource();
simpleDataBoundColumn1.DataBind();
}
}
</script>
<head runat="server">
<title>SimpleDataBoundColumn test page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<aspSample:SimpleDataBoundColumn runat="server" id="simpleDataBoundColumn1" DataTextField="CurrencyValue" BorderStyle="Solid"></aspSample:SimpleDataBoundColumn>
</div>
</form>
</body>
</html>
Run the SimpleDataBoundColumnTest.aspx page.
The data is displayed by the custom control.