Build Data-Bound Template Controls

Render data - without doing repetitive layout work.



TECHNOLOGIES: Custom Controls


Build Data-Bound Template Controls

Render data - without doing repetitive layout work.


By Doug Seven


Data-bound template controls can have a tremendous impact on your efficiency. They can ease the often labor-intensive rendering of data. In this article, I'm going to show you how to build your own data-bound template control - similar to a DataList control - and bind it to a DataView. This control, which I've named the ProductGrid control, is designed to control the rendering of a specific type of data. In scenarios common to large Web apps, where a common set of data exists (data such as products, insurance claims, or customers), the ProductGrid Control eliminates the need for repetitive layout work.


Data binding is the association of server controls with a data source, wherein a server control is matched to a data source and invokes a method that matches the fields present in the data source with the data-binding expressions present in the server control. For example, you can data-bind an ASP.NET Repeater control that uses a DataView as the data source with data-binding expressions in the Repeater's templates. The data-binding expressions define the data source by their relation to the Repeater's template and the field they bind to in the DataViewRow. The same data-binding capabilities are available to you in custom control development. Typically, this involves a custom template control similar to the Repeater, DataList, or DataGrid controls. With custom controls you create one or more templates and bind a data source to that control. Someone implementing your control in a Web form can define the data layout in the templates using standard data-binding expressions.


Similar to a DataList, the ProductGrid control is a custom template control. The purpose of the ProductGrid control is to provide a consistent display format for products from the Northwind table in a SQL Server database (Northwind also is available in Microsoft Access). Present in the ProductGrid control is an ItemTemplate that - when the control is bound to a DataView of products - renders each product's information. Within this control you can define a default layout for the ItemTemplate. This enables you to place the ProductGrid control on a Web form, bind it to a DataView of products, and render the default layout for the products. Additionally, you can provide a custom layout within the ItemTemplate and override the default layout. Figure 1 shows the default ProductGrid control when it is placed on a Web form and bound to an appropriate DataView.


Figure 1. The ProductGrid control renders each record in the DataView within its own cell. Using properties specific to the control, you may specify how many columns to display.


Build the ItemTemplate

The ProductGrid control is a basic template control; in other words, it is a custom control that exposes at least one property that is an ITemplate type. In this case, the ProductGrid control exposes a property named ItemTemplate whose type is a class that implements the ITemplate interface. The custom class used as the type for the ItemTemplate is called TemplatedItem. The TemplatedItem class derives from the System.Web.UI.WebControls.WebControl class and exposes a number of fields that map onto fields in the Products table of the Northwind database. The following code for the TemplatedItem class inherits from the WebControl class and exposes fields for all the product details:


Public Class TemplatedItem

    Inherits System.Web.UI.WebControls.WebControl

    Implements INamingContainer


    Public ProductName As String

    Public SupplierName As String

    Public CategoryName As String

    Public QuantityPerUnit As String

    Public UnitPrice As String

    Public UnitsInStock As String

     Public UnitsOnOrder As String

    Public ReorderLevel As String


End Class


By exposing these fields, you can use a special data-binding syntax when you place the ProductGrid control on a Web form. When you add data-binding syntax to a template in a control such as the Repeater, it should look like this:


<%# Container.DataItem("MyField") %>


You use data-binding syntax like this to say, "Bind the MyField value from the DataItem property of the data source to this control." Having created the TemplatedItem class and exposed many fields within it, your Web form will, after you have added data-binding syntax, look like this:


<%# Container.ProductName %>


This simplifies the data-binding syntax - you can access the specific properties of the template directly. This, of course, means the control is designed for a specific data type. Note that our earlier design discussion calls for a control that renders a default layout for products with the ability to use a template to override the layout, thus the nomenclature used previously.


Build the ProductGrid Control

The ProductGrid control also derives from the WebControl class, thereby providing all the basic functionality of any Web control. It also implements the INamingContainer interface, ensuring all child controls have unique IDs:


Imports System.ComponentModel

Imports System.Web.UI

Imports System.Web.UI.HtmlControls

("<{0}:ProductGrid runat=server>")> _

Public Class ProductGrid

    Inherits System.Web.UI.WebControls.WebControl

    Implements INamingContainer

    'All Code will go here

End Class


The ProductGrid control exposes four properties: DataSource, Title, ColumnCount, and ItemTemplate. These four properties combine to control the layout of the rendered HTML.


The DataSource control is defined as a DataView. This control is dependent on a consistent format for the data, and a DataView is the only allowable DataSource. If you were to build a more generic data-bound control, you could use any type that implements IEnumerable or ICollection as a data source. The Title property is a String type and is used to render text in the first row of the rendered HTML table. The default value for this property is "Product Grid." The ColumnCount property is a 32-bit integer that defines how many columns will be rendered for the layout; the default value is 3. Figure 2 shows the code for the ItemTemplate property (other ProductGrid properties are defined similarly).


'Define the ItemTemplate property

Private _itemTemplate As ITemplate

    Description("The content to be shown in each item."), _

    PersistenceMode(PersistenceMode.InnerProperty), _

    TemplateContainer(GetType(TemplatedItem))> _

Public Overridable Property ItemTemplate() As ITemplate


      Return _itemTemplate

   End Get

   Set(ByVal Value As ITemplate)

      _itemTemplate = Value

   End Set

End Property

Figure 2. The properties are defined and default values are set. Each property has attributes applied to determine how it is listed in the Visual Studio .NET Properties window.


Override the DataBind Method

Overriding the DataBind method within the ProductGrid control allows you to control how content is rendered and how the ItemTemplate is used within the control. When the control is used, the developer will set the DataSource property to an applicable DataView and then invoke DataBind. Within the DataBind method, you may either render the default layout for the data or use the template specified by the developer. The listing for the DataBind method is available for download.


You begin the DataBind method by defining the HtmlTable, HtmlTableRow, and HtmlTableCell objects used to render the data. Once you have defined the objects and created an instance of the HtmlTable class, you may add the table instance to the ProductGrid.Controls collection. After that, any controls you add to the table.Controls collection using the table.Controls.Add method will be added to the HtmlTable instance in the ProductGrid.Controls collection, which is equivalent to this:




The next step requires you to create the title row of the ProductGrid control and add it to the table.Controls collection. The Title property is rendered in an HtmlTableCell that spans all the columns in the table by creating a ColSpan attribute with the ColumnCount property as its particular value.


Now that the incidentals are out of the way, you must loop through the data source and create the output for each record. If the developer using the control has specified a custom ItemTemplate, you need to create a new instance of the TemplatedItem class and bind that class instance to the template layout. If the developer did not specify a custom ItemTemplate, you then need to build the default layout yourself.


You begin by creating object definitions for the DataRowView, which represents the row in the DataView you are currently binding, and two integers for counting the rows and the columns.


Each time you loop you will need do a sequence of things:


  1. Create a new HTML row within which you may render the data.
  2. Create a sub loop to advance through the DataView, adding a new cell (for each record) to the row. Note that the number of cells added to each row is based on the ColumnCount property.
  3. Create a new HTML cell.
  4. Create a new instance of the TemplatedItem class and set those properties based upon the values present in DataView.
  5. Check to see if there is a defined ItemTemplate - if there is, data-bind it; if not, generate the default layout for the ItemTemplate.
  6. Add either the bound TemplatedItem or the default HTML output to the cell.
  7. Add the cell to the row. Go to Step 4 if the sub loop count is less than the ColumnCount.
  8. Add the row to the table.


The CreateItem method shown in Figure 3 is a helper method that takes a TemplatedItem instance and a DataRowView as its two input arguments and sets the properties of the TemplatedItem.


Private Sub CreateItem( _

  ByRef itemTemplate As TemplatedItem, _

  ByVal view As DataRowView)

    itemTemplate.ProductName = _


    itemTemplate.SupplierName = _


    itemTemplate.CategoryName = _


    itemTemplate.QuantityPerUnit = _


    itemTemplate.UnitPrice = _

      Convert.ToDecimal( _

          view.Item("UnitPrice") _


    itemTemplate.UnitsInStock = _

      Convert.ToInt32( _

        view.Item("UnitsInStock") _


    itemTemplate.UnitsOnOrder = _

      Convert.ToInt32( _

        view.Item("UnitsOnOrder") _


    itemTemplate.ReorderLevel = _

      Convert.ToInt32( _

        view.Item("ReorderLevel") _


End Sub

Figure 3. The CreateItem method sets the properties of the referenced TemplatedItem instance.


Once you have created and compiled the ProductGrid control, you can distribute the assembly and other developers can begin using it. Because the ProductGrid control has a default layout defined in the DataBind method, a developer can use the control by simply placing it in a Web form and binding it to an applicable DataView. Figure 4 shows the Web form code for using the ProductGrid control.


<%@ Page Language="vb" AutoEventWireup="false"



<%@ Register TagPrefix="cc1"


    Assembly="DataBoundControls" %>




          content="Microsoft Visual Studio.NET 7.0">


          content="Visual Basic 7.0">













Figure 4. The ProductGrid control's namespace is registered using the @ Register directive, and the control is placed using standard server control syntax.


Figure 5 shows the Page_Load event handler from the code-behind class for the Web form shown in Figure 4. (Note that this Web form renders the page shown in Figure 1.)


    Private Sub Page_Load(ByVal sender As System.Object, _

      ByVal e As System.EventArgs) Handles MyBase.Load

        Dim conString As String = _


        Dim sql As New System.Text.StringBuilder()

        sql.Append("SELECT TOP 10 Products.ProductID, ")

        sql.Append("Products.ProductName, ")

        sql.Append("Products.SupplierID, ")

        sql.Append("Suppliers.CompanyName, ")

        sql.Append("Products.CategoryID, ")

        sql.Append("Categories.CategoryName, ")

        sql.Append("Products.QuantityPerUnit, ")

        sql.Append("Products.UnitPrice, ")

        sql.Append("Products.UnitsInStock, ")

        sql.Append("Products.UnitsOnOrder, ")

        sql.Append("Products.ReorderLevel, ")

        sql.Append("Products.Discontinued, ")

        sql.Append("Categories.CategoryID, ")

        sql.Append("Suppliers.SupplierID ")

         sql.Append("FROM Products ")

        sql.Append("INNER JOIN Categories ON ")

        sql.Append("Products.CategoryID = ")

        sql.Append("Categories.CategoryID ")

        sql.Append("INNER JOIN ")

        sql.Append("Suppliers ON Products.SupplierID = ")

        sql.Append("Suppliers.SupplierID ")

        sql.Append("ORDER BY Products.UnitsInStock ")

        sql.Append("DESC, Products.ProductName")


        Dim sda As New _

          SqlClient.SqlDataAdapter(sql.ToString(), _


         Dim ds As New DataSet()


            sda.Fill(ds, "Products")

            ProductGrid1.DataSource = _




            'TODO: Add exception handling

        End Try

    End Sub

Figure 5. The product grid is bound to a DataView that contains the product data.


Adding an ItemTemplate layout to the ProductGrid, as shown in Figure 6, allows the data to be rendered by creating the ItemTemplate and binding it to the data source item.


  runat="server" ColumnCount="5">



          <%# Container.ProductName %>




        <%# Container.CategoryName %>



        <%# Container.SupplierName %>


        Unit Price:

        <%# Container.UnitPrice %>


        Units In Stock:

        <%# Container.UnitsInStock %>


        Units on Order:

        <%# Container.UnitsOnOrder %>


        Reorder Level:

        <%# Container.ReorderLevel %>


        Qty / Units:

        <%# Container.QuantityPerUnit %>



Figure 6. By defining an ItemTemplate, a developer can control how the data in the data source is bound to the control and rendered.


Note that in Figure 6, the ColumnCount property is changed from the default value. The page that results is shown in Figure 7.


Figure 7. This Web page contains the data rendered based on the layout specified in the ItemTemplate.


You can use the techniques you learned building the ProductGrid in other applications as well. For example, you can use them in building the Web application a company makes available to customers, an intranet application for sales people, an administration application, and so forth. You'll also be able to use the concepts in this article to create other generic controls, which do not depend on a consistent data source structure.


The code referenced in this article is available for download. It includes a Visual Studio .NET Solution, which requires a virtual directory named ControlTowerWS200301.


Doug Seven is a co-founder of the .NET training company DotNetJunkies ( and is a Microsoft ASP.NET MVP. He has held technical roles at Nordstrom, Microsoft, and, and he has experience as a training specialist at Seattle Coffee Company. Doug co-authored Programming Data-Driven Web Applications with ASP.NET and ASP.NET: Tips, Tutorials & Code (Sams), and Professional ADO.NET and Professional ASP.NET Security (Wrox). E-mail Doug at mailto:[email protected].


Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.




Hide comments


  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.