Walkthrough: Creating a Custom Data-Bound ASP.NET Web Control for ASP.NET 1.1
This walkthrough illustrates how to create a simple data-bound server control that exposes a bindable data source.
An ASP.NET data-bound server control provides a user interface (UI) for a data source representing a collection of records or items. The DataList and Repeater server controls are examples of data-bound server controls. For more information on data-bound controls provided with ASP.NET, see ASP.NET Data-Bound Web Server Controls Overview.
Note |
---|
The control illustrated in this walkthrough is compatible with ASP.NET version 1.1. For more information creating custom data-bound Web controls for ASP.NET 2.0, see Developing Custom Data-Bound Web Server Controls for ASP.NET 2.0 |
An ASP.NET 1.1 data-bound server control can bind to any of the following types:
IEnumerable (such as an Array, an ArrayList, or a Hashtable object)
IListSource (such as a DataSet object).
In this walkthrough, you will create a data-bound control that can bind to any object that implements the IEnumerable interface.
Tasks illustrated in this walkthrough include:
Creating the Web site to test the custom data-bound control.
Creating a data-bound control class. The class must expose a property of type IListSource or IEnumerable, enabling a page developer to specify the location of the data to be bound. It must also override the base class's DataBind and CreateChildControls methods.
Registering the control in the Web.config file.
Testing the control in an ASP.NET Web page.
Compiling the control so that you can distribute it as binary code.
Testing the compiled custom data-bound server control.
Creating a Web Site to Test the Control
You can use ASP.NET dynamic compilation to test your control in a page without compiling the control into an assembly. ASP.NET dynamically compiles code placed in the App_Code directory in the Web site root. Classes in source files in App_Code can therefore be accessed from pages without being manually compiled into assemblies.
Note |
---|
The App_Code directory is a new feature that was not available in ASP.NET 1.0 and 1.1. Using the App_Code directory for initial control testing is optional. The main steps in building a server control are the same as in earlier versions, as described in the section "Compiling the Control into an Assembly." |
To create a Web site to test custom data-bound controls
Create a Web site named ServerControlsTest.
You can create the site in IIS as a virtual directory named ServerControlsTest. For details about creating and configuring an IIS virtual directory, see How to: Create and Configure Virtual Directories in IIS.
Create an App_Code directory directly under the root directory of your Web site.
Creating the SimpleDataBoundControl Class
The next step is to create the control class.
To create the SimpleDataBoundControl class
In the App_Code folder created in the previous procedure, create a class named SimpleDataBoundControl.cs or SimpleDataBoundControl.vb, depending on what programming language you want to use.
Add the following code to your class file.
Imports System Imports System.Collections Imports System.ComponentModel Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Namespace Samples.ASPNet.VB.Controls <DefaultProperty("DataSource")> _ Public Class SimpleDataBoundControl Inherits WebControl Private _dataSource As IEnumerable Private _label As Label Private _button As Button Private _table As Table <Category("Data"), DefaultValue(""), Description("An exposed data source: A public member of type IEnumerable to bind to such as an Array, ArrayList or Hashtable.")> _ Public Overridable Property DataSource() As IEnumerable Get Return _dataSource End Get Set(ByVal value As IEnumerable) If TypeOf value Is IEnumerable OrElse value Is Nothing Then _dataSource = value Else Throw New ArgumentException() End If End Set End Property Protected Overridable Function GetDataSource() As IEnumerable If _dataSource Is Nothing Then Return Nothing End If Dim resolvedDataSource As IEnumerable resolvedDataSource = _dataSource Return resolvedDataSource End Function 'GetDataSource Protected Overridable Sub CreateMyControlHeirarchy(ByVal useViewState As Boolean) Dim resolvedDataSource As IEnumerable = Nothing If useViewState Then If Not (ViewState("RowCount") Is Nothing) Then resolvedDataSource = New Object(Fix(ViewState("RowCount"))) {} Else Throw New Exception("Unable to retrieve expected data from ViewState") End If Else resolvedDataSource = GetDataSource() End If If Not (resolvedDataSource Is Nothing) Then ' Create a label that will indicate form which source the data has been provided. Dim s As String If useViewState Then s = "Data collection retrieved from ViewState:" Else s = "Data collection retrieved from bound data source:" End If _label = New Label() Me.Controls.Add(Me._label) _label.Text = s _button = New Button() Me.Controls.Add(Me._button) _button.Text = "Test re-binding of ViewState" _table = New Table() Me.Controls.Add(Me._table) Dim dataItem As Object For Each dataItem In resolvedDataSource Dim row As New TableRow() _table.Rows.Add(row) Dim cell As New TableCell() If Not useViewState Then cell.Text = dataItem.ToString() End If row.Cells.Add(cell) Next dataItem ViewState("RowCount") = _table.Rows.Count End If End Sub 'CreateMyControlHeirarchy Protected Overrides Sub CreateChildControls() Controls.Clear() If Not (ViewState("RowCount") Is Nothing) Then Dim useViewState As Boolean = True CreateMyControlHeirarchy(useViewState) End If End Sub 'CreateChildControls Public Overrides Sub DataBind() MyBase.OnDataBinding(EventArgs.Empty) Controls.Clear() ClearChildViewState() TrackViewState() Dim useViewState As Boolean = False CreateMyControlHeirarchy(useViewState) ChildControlsCreated = True End Sub 'DataBind Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) Me._label.RenderControl(writer) Me._table.RenderControl(writer) Me._button.RenderControl(writer) End Sub 'RenderContents End Class 'SimpleDataBoundControl End Namespace
using System; using System.Collections; using System.ComponentModel; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace Samples.AspNet.CS.Controls { [ DefaultProperty("DataSource") ] public class SimpleDataBoundControl : WebControl { private IEnumerable _dataSource; private Label _label; private Button _button; private Table _table; [ Category("Data"), DefaultValue(""), Description("An exposed data source: A public member of type IEnumerable to bind to such as an Array, ArrayList or Hashtable.") ] public virtual IEnumerable DataSource { get { return _dataSource; } set { if ((value is IEnumerable) || (value == null)) { _dataSource = value; } else { throw new ArgumentException(); } } } protected virtual IEnumerable GetDataSource() { if (_dataSource == null) { return null; } IEnumerable resolvedDataSource; resolvedDataSource = _dataSource as IEnumerable; return resolvedDataSource; } protected virtual void CreateMyControlHeirarchy(bool useViewState) { IEnumerable resolvedDataSource = null; if (useViewState) { if (ViewState["RowCount"] != null) { resolvedDataSource = new object[(int)ViewState["RowCount"]]; } else { throw new Exception("Unable to retrieve expected data from ViewState"); } } else { resolvedDataSource = GetDataSource(); } if (resolvedDataSource != null) { // Create a label that will indicate form which source the data has been provided. String s; if (useViewState) { s = "Data collection retrieved from ViewState:"; } else { s = "Data collection retrieved from bound data source:"; } _label = new Label(); this.Controls.Add(this._label); _label.Text = s; _button = new Button(); this.Controls.Add(this._button); _button.Text = "Test re-binding of ViewState"; _table = new Table(); this.Controls.Add(this._table); foreach (object dataItem in resolvedDataSource) { TableRow row = new TableRow(); _table.Rows.Add(row); TableCell cell = new TableCell(); if (!useViewState) { cell.Text = dataItem.ToString(); } row.Cells.Add(cell); } ViewState["RowCount"] = _table.Rows.Count; } } protected override void CreateChildControls() { Controls.Clear(); if (ViewState["RowCount"] != null) { bool useViewState = true; CreateMyControlHeirarchy(useViewState); } } public override void DataBind() { base.OnDataBinding(EventArgs.Empty); Controls.Clear(); ClearChildViewState(); TrackViewState(); bool useViewState = false; CreateMyControlHeirarchy(useViewState); ChildControlsCreated = true; } protected override void RenderContents(HtmlTextWriter writer) { this._label.RenderControl(writer); this._table.RenderControl(writer); this._button.RenderControl(writer); } } }
Code Discussion
The SimpleDataBoundControl
class renders an HTML table based on the data collection specified in its DataSource property. ASP.NET 1.1 data-bound Web server controls must expose a bindable DataSource property that can be set to either an IEnumerable or an IListSource type.
In the example code, the set accessor for the DataSource property verifies that the value to set is either null or to an object of type IEnumerable. Therefore, in this example the page developer can bind to any IEnumerable type, such as an Array, ArrayList, or Hashtable object. The developer can initially leave the DataSource set to its default null value and set the property in code.
The metadata attributes Category, DefaultValue, and Description provide information used by design tools, the ASP.NET page parser, and the common language runtime.
The CreateMyControlHeirarchy
helper method keeps the creation of the control’s child control hierarchy on one common code path. It is called by both the overridden DataBind method and the overridden CreateChildControls method. The overridden DataBind method is required. It enumerates the object in the associated data source and creates the child controls. The overridden CreateChildControls method is required and recreates the child control hierarchy based on the data saved in view state.
The control renders by overriding the inherited RenderContents method. This enables the control to render within its own tags. The parameter passed into the RenderContents method is an object of type HtmlTextWriter, which is a utility class that has methods for rendering tags and other HTML (and HTML-variant) markup.
For more information on the required implementations of a data-bound Web server control, see Developing Custom Data-Bound Web Server Controls for ASP.NET 1.1.
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 needs 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.CS.Controls"%>
<%@ Register TagPrefix="aspSample"
Namespace="Samples.AspNet.VB.Controls"%>
Note |
---|
The @ Register directive in ASP.NET 2.0 is the same as that in ASP.NET 1.0 and ASP.NET 1.1. If you are familiar with the @ Register directive in earlier versions of ASP.NET, note that the assembly attribute that specifies the name of the control assembly is missing in the preceding @ Register directive. 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.
Note |
---|
A configuration entry for the tag prefix is new feature of ASP.NET 2.0. In ASP.NET 1.0 and 1.1 the tag prefix mapping was specified in the @ Register directive in each page that used the custom control. |
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 your Web site does not already have one, create a file named Web.config in the Web site's root folder.
If you created a new (empty) 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> element, which must be under the <pages> section under <system.web>.
<?xml version="1.0"?> <configuration> <system.web> <pages> <controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.CS.Controls"> </add> </controls> </pages> </system.web> </configuration>
<?xml version="1.0"?> <configuration> <system.web> <pages> <controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.VB.Controls"> </add> </controls> </pages> </system.web> </configuration>
The highlighted section shows a tag prefix entry, which maps the tag prefix "aspSample" to the namespace
Samples.AspNet.CS.Controls
orSamples.AspNet.VB.Controls
.
After you have specified the tag prefix mapping in the configuration file, you can use the SimpleDataBoundControl
control declaratively as <aspSample:SimpleDataBoundControl />
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 enables you to test the custom data-bound control.
To create a page that uses the custom data-bound control
Create a file named TestSimpleDataBoundControl.aspx in your Web site.
Copy the following markup into the TestSimpleDataBoundControl.aspx file and save the file.
<%@ Page Language="VB" %> <%@ Register TagPrefix="aspSample" Namespace="Samples.AspNet.VB.Controls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not IsPostBack Then Dim a As New ArrayList() a.Add("One") a.Add("Two") a.Add("Three") a.Add("Four") simpleDataBoundControl1.DataSource = a simpleDataBoundControl1.DataBind() End If End Sub 'Page_Load </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>SimpleDataBoundControl test page</title> </head> <body> <form id="form1" runat="server"> <div> <aspSample:SimpleDataBoundControl runat="server" id="simpleDataBoundControl1" BorderStyle="Solid" ></aspSample:SimpleDataBoundControl> </div> </form> </body> </html>
<%@ Page Language="C#" Trace="true" EnableViewState="true" %> <%@ Register TagPrefix="aspSample" Namespace="Samples.AspNet.CS.Controls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { ArrayList a = new ArrayList(); a.Add("One"); a.Add("Two"); a.Add("Three"); a.Add("Four"); simpleDataBoundControl1.DataSource = a; simpleDataBoundControl1.DataBind(); } } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>SimpleDataBoundControl test page</title> </head> <body> <form id="form1" runat="server"> <div> <aspSample:SimpleDataBoundControl runat="server" id="simpleDataBoundControl1" BorderStyle="Solid" ></aspSample:SimpleDataBoundControl> </div> </form> </body> </html>
Run the TestSimpleDataBoundControl.aspx page.
Make some change to the source code for the custom control. For example, write an additional string by adding this line of code at the end of the RenderContents method:
writer.Write("<br />Testing how the App_Code directory works.");
writer.Write("<br />Testing how the App_Code directory works.")
Refresh the TestSimpleDataBoundControl.aspx page in your browser.
You will see that changes in the control are reflected in the page even though you did not compile the control.
Compiling the Control into an Assembly
Although the App_Code directory enables you to test your control without compiling it, if you want to distribute your control as object code to other developers, you must compile it. In addition, a control cannot be added to the toolbox of a visual designer unless it is compiled into an assembly.
To compile the control into an assembly
Set the Windows environment PATH variable of your computer to include the path to your .NET Framework installation by following these steps:
In Windows, right-click My Computer, select Properties, click the Advanced tab, and click the Environment Variables button.
In the System variables list, double-click the Path variable.
In the Variable value text box, add a semicolon (;) to the end of the existing values in the text box, and then type the path of your .NET Framework installation. The .NET Framework is generally installed in the Windows installation directory at \Microsoft.NET\Framework\versionNumber.
Click OK to close each dialog box.
Run the following command from the App_Code directory containing the source files you created earlier in this walkthrough.
This will generate an assembly named Samples.AspNet.CS.Controls.dll or Samples.AspNet.VB.Controls.dll in the App_Code directory.
csc /t:library /out:Samples.AspNet.CS.Controls.dll /r:System.dll /r:System.Web.dll *.cs
vbc /t:library /out:Samples.AspNet.VB.Controls.dll /r:System.dll /r:System.Web.dll *.vb
The /t:library compiler option tells the compiler to create a library instead of an executable assembly. The /out option provides a name for the assembly and the /r option lists the assemblies that are linked to your assembly.
Note To keep the example self-contained, this walkthrough asks you to create an assembly with a single control. In general, the .NET Framework design guidelines recommend that you do not create assemblies that contain only a few classes. For ease of deployment, you should create as few assemblies as possible.
Using the TagPrefixAttribute to Provide a Tag Prefix/Namespace Mapping
Earlier you saw how a page developer can specify a tag prefix in the page or in the Web.config file. When you compile a control, you can optionally include the assembly-level System.Web.UI.TagPrefixAttribute attribute, which suggests a default tag prefix that a visual designer should use for your control. The TagPrefixAttribute attribute is useful because it provides a tag prefix for a visual designer to use if the designer does not find a tag prefix mapping in the Web.config file or in a @ Register directive in the page. The tag prefix is registered with the page the first time the control is double-clicked in the toolbox or dragged from the toolbox onto the page.
If you decide to use the TagPrefixAttribute attribute, you can specify it in a separate file that is compiled with your controls. By convention, the file is named AssemblyInfo.languageExtension, such as AssemblyInfo.cs or AssembyInfo.vb. The following procedure describes how to specify the TagPrefixAttribute metadata.
Note |
---|
If you do not specify the TagPrefixAttribute in the control's assembly, and the page developer does not specify the tag prefix/namespace mapping in the page or in the Web.config file, the visual designer might create a default tag prefix. For example, Visual Studio 2005 will create its own tag (such as |
To add a tag prefix mapping using the TagPrefixAttribute
Create a file named AssemblyInfo.cs or AssemblyInfo.vb in your source code directory and add the following code to the file.
using System; using System.Web.UI; [assembly: TagPrefix("Samples.AspNet.CS.Controls", "aspSample")]
Imports System Imports System.Web.UI <Assembly: TagPrefix("Samples.AspNet.VB.Controls", "aspSample")>
The tag prefix attribute creates a mapping between the namespace
Samples.AspNet.CS.Controls
orSamples.AspNet.VB.Controls
and the prefixaspSample
.Recompile all the source files using the compilation command you used earlier (with or without the embedded resource).
Using the Compiled Custom Data-Bound Server Control in an ASP.NET Page
To test the compiled version of your custom control, you must make your control's assembly available to pages in the Web site.
To make your control's assembly available to the Web site
If your site does not already have one, create a Bin directory under the root of the Web site.
Drag (move) the control assembly (Samples.AspNet.CS.Controls.dll or Samples.AspNet.VB.Controls.dll) from the App_Code directory to the Bin directory.
Move the control's source file from the App_Code directory to another directory in your Web site.
If you do not move the source files out of the App_Code directory, your control's type will exist in both the compiled assembly and in the dynamically generated assembly created by ASP.NET. This will create an ambiguous reference when loading your control, and any page in which the control is used will generate a compiler error.
The assembly that you created in this walkthrough is a private assembly because it must be placed in an ASP.NET Web site's Bin directory to enable pages in the Web site to use your control. The assembly cannot be accessed from other applications unless a copy is also installed with those applications. If you are creating controls for shared Web hosting applications, you will typically package your controls in a private assembly. However, if you create controls for use in a dedicated hosting environment or you create a suite of controls that an ISP makes available to all its customers, you might need to package your controls in a shared (strongly named) assembly that is installed in the global assembly cache. For more information, see Working with Assemblies and the Global Assembly Cache.
Next, you must modify the tag prefix mapping you created in the Web.config file to specify your control's assembly name.
To modify the tag prefix mapping in the Web.config file
Edit the Web.config file to add an assembly attribute to the an <add tagPrefix=name> element:
<controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.CS.Controls" assembly="Samples.AspNet.CS.Controls"> </add> </controls>
<controls> <add tagPrefix="aspSample" namespace="Samples.AspNet.VB.Controls" assembly="Samples.AspNet.VB.Controls"> </add> </controls>
The assembly attribute specifies the name of the assembly that the control is in. An <add tagPrefix=name> element maps a tag prefix to a namespace/assembly combination. When the assembly is dynamically generated by ASP.NET from source files in the App_Code directory, the assembly attribute is not necessary. When the assembly attribute is not used, ASP.NET loads the control's type from the assembly dynamically generated from the App_Code directory.
You can now test the control by viewing it in a page.
To view the page that uses the custom control
Display the TestSimpleDataBoundControl.aspx page in your browser by entering the following URL in the address bar:
https://localhost/ServerControlsTest/TestSimpleDataBoundControl.aspx
If you use your control in a visual designer such as Visual Studio 2005, you will be able to add your control to the toolbox, drag it from the toolbox to the design surface, and access properties and events in the property browser. In addition, in Visual Studio 2005, your control has full IntelliSense support in Source view of the page designer and in the code editor.
Next Steps
This simple custom data-bound server control illustrates the fundamental steps used to create a custom control that provides the page developer with a standard way to bind it to an external data source. From this start, you can begin to explore how Visual Studio helps you create sophisticated custom server controls. Suggestions for more exploration include:
Review an introduction to the general architecture of developing a custom data-bound server control. For details, see Developing Custom Data-Bound Web Server Controls for ASP.NET 1.1.
Investigate the existing data-bound server controls classes that you can extend and customize. For an overview of available data-bound Web server controls, see ASP.NET Data-Bound Web Server Controls Overview. For information on data-bound Web server control classes, see the following reference topics: GridView, DetailsView, FormView, Repeater, and DataList.
Learn more about rendering, defining properties, maintaining state, and implementing custom composite controls. For details, see Developing Custom ASP.NET Server Controls, Control State vs. View State Example, Server Control Custom State Management.
Learn about the metadata attributes you can apply to your custom data-bound server control and to its members in order to provide information that is used by design tools. For details, see Metadata Attributes for Custom Server Controls.
Create a control designer for adding design-time features, templates, or styles. For an example of a custom data-bound control designer, see HierarchicalDataBoundControlDesigner and Walkthrough: Creating a Basic Control Designer for a Web Server Control. For an overview of ASP.NET control designers, see ASP.NET Control Designers Overview.
Work with an example of a custom data-bound control designer. For details, see the HierarchicalDataBoundControlDesigner topic.
Explore a demonstration on defining appropriate design-time attributes to make a custom server control available in the Visual Studio Toolbox. For details, see How to: Use Custom ASP.NET Server Controls in Visual Studio.
See Also
Tasks
Walkthrough: Developing and Using a Custom Server Control
Reference
HierarchicalDataBoundControlDesigner
Concepts
ASP.NET Data-Bound Web Server Controls Overview
Metadata Attributes for Custom Server Controls
ASP.NET Control Designers Overview