Export (0) Print
Expand All

Creating Web Server Control Templates Programmatically

Visual Studio .NET 2003
 

Mike Pope, Nikhil Kothari
Visual Studio Team
Microsoft Corporation

December 2001

Summary: Illustrates how to create templates in code for the Repeater, DataList, and DataGrid ASP.NET server controls, showing examples in both Visual Basic .NET and Visual C# .NET. (9 printed pages)

Contents

This paper is divided into the sections that illustrate increasingly sophisticated ways to create templates. In the initial section, for example, you will learn how to create the simplest possible template in code. Subsequent sections build on this simple template to add functionality to it. By the end of the paper, you will have learned how to create a fully functional template that includes data binding.

Introduction
Basics of Creating Templates Programmatically
Adding Different Types of Templates
Adding Data Binding to Templates
Creating Templates Programmatically in the DataGrid Control
Wrapping Up

Requirements

This paper assumes that you are generally familiar with creating Web Forms pages and using ASP.NET server controls. It also assumes that you have at least experimented in the Web Forms Designer with creating templates for the Repeater or DataList ASP.NET server control. For background on templates, see Web Server Controls Templates.

Finally, the paper assumes that you know how to data bind controls in Web Forms pages. For background information on data binding, see Web Forms Data Binding in the Microsoft® Visual Studio® .NET documentation.

Introduction

The Repeater, DataList, and DataGrid ASP.NET server controls are designed to be data bound. These controls produce one item (for example, a row) for each element in the data source they are bound to. To define how the data is laid out, you use templates. A template is a container for other controls: the template itself is not an object with its own UI. Instead, to display information, you add controls to the template and then set the display properties of those controls (usually via data binding). When the control runs, it iterates through the data source and for each data element it creates an item — a merger of the controls in the template with the information in the data element.

The Repeater and DataList controls allow you to create more free-form output — for example, the items can be rows in a table, bullet list items, elements in a comma-delimited list, or whatever you like. The DataGrid control uses a predefined format that looks (not surprisingly) like a grid. Each data element produces a row in the grid. When the control is rendered, each item is output as a table row.

Defining Templates

If you are working with the Repeater, DataList, or DataGrid control in a Web Forms page, it is easy enough to create templates at design time. The DataList control supports a WYSIWYG-like editing window. For the Repeater control, you can create templates in HTML view as special elements nested inside the control. In the DataGrid control, you can use the Properties window to create a new template column.

But that supposes that you want to create the templates at design time. For various reasons, you might not know until run time what templates you need or what text or controls should be in them. In that case, you need to be able to create the templates on the fly. As long as you understand a few basics about creating classes and about Web Forms data binding, you can create templates in code. This paper shows how.

Note   You can also create templates as Web Forms user controls and bind them dynamically to controls on your page. For details, see Creating a Templated User Control in the .NET Framework SDK documentation.

Basics of Creating Templates Programmatically

To begin, this section will show you how to create the simplest possible template in code. What you want to be able to do is to run a statement such as the following:

' Visual Basic
Repeater1.ItemTemplate = New MyTemplate()

// C#
Repeater1.ItemTemplate = new MyTemplate();

You can see that this creates an instance of the MyTemplate class. Where does that template class come from? You create it.

Note   Creating templates in code is slightly different for DataGrid controls, since you are creating a column. You can find more about that at the end of this paper. Everything you read until then, however, is largely the same for DataGrid controls as it is for DataList and Repeater controls.

A Basic Template Class

A template class is relatively simple. At a minimum, it must do the following:

  • Declare that your class implements the ITemplate interface of the System.Web.UI namespace.
  • Implement the InstantiateIn method (the only member of the ITemplate interface). This method provides a way to insert an instance of text and controls into the specified container.
    Note   For background on creating classes in Microsoft Visual Basic® .NET, see Understanding Classes in the Visual Studio .NET documentation. For similar information for Microsoft Visual C#™ .NET, see class.

The point of the template, of course, is to be able to display text and controls for each item in the data source. You do this in the InstantiateIn method of your class, where you create the UI for the template.

The normal strategy for working with the InstantiateIn method is to create a new control, set the properties you want, and then add the control to the parent's Controls collection. You cannot directly add static text to the Controls collection, but you can create Literal or LiteralControl controls, set their Text properties, and then add those controls to the parent collection. The InstantiateIn method passes you a reference to the parent control (the Repeater, DataList, or DataGrid control), which makes this last step easy.

The following example shows a complete template class that displays some static text ("Item number:") and a counter. The counter is maintained as a shared or static value (depending on what language you are using) called itemcount for the class and incremented each time a new item is created.

' Visual Basic
Public Class MyTemplate
   Implements ITemplate
   Shared itemcount As Integer = 0

   Sub InstantiateIn(ByVal container As Control) _
         Implements ITemplate.InstantiateIn
      Dim lc As New Literal()
      lc.Text = "Item number: " & itemcount.ToString & "<BR>"
      itemcount += 1
      container.Controls.Add(lc)
   End Sub
End Class

// C#
private class MyTemplate : ITemplate
{
   static int itemcount;
   public void InstantiateIn(System.Web.UI.Control container)
   {
      Literal lc = new Literal();
      lc.Text = "Item number: " + itemcount.ToString() + "<BR>";
      itemcount += 1;
      container.Controls.Add(lc);
   }
}

Testing the Template Class

To see how this sample template class works, test it using a Repeater control. Start by creating a new Web Forms page and doing the following:

  1. Add a Repeater control.
  2. Add a data connection and data adapter. For this test, it does not matter what data you are using.
  3. Create a dataset whose schema matches the SQL query in the data adapter you just created. The dataset will hold the data you will display in your control.
    Tip   If you are using Visual Studio, drag a data adapter onto the page and use the wizard to create the connection. Then use the Generate Dataset command from the Data menu to create a typed dataset.
  4. Set the Repeater control's DataSource property to the dataset and its DataMember property to the name of a table in the dataset.

Now you are ready to create your template class. Do the following:

  1. Copy the last sample above and paste it into the code for your page.

    When you have pasted in the class code, the page might look like the following (with the code you have pasted in highlighted here):

    ' Visual Basic
    Public Class WebForm1
       Inherits System.Web.UI.Page
       ' Other Inherits statements here
    
       Private Class MyTemplate
          Implements ITemplate
          Dim itemcount As Integer = 0
          Sub InstantiateIn(ByVal container As Control) _
                Implements ITemplate.InstantiateIn
             Dim lc As New Literal()
             lc.Text = "Item number: " & itemcount.ToString & "<BR>"
             itemcount += 1
             container.Controls.Add(lc)
          End Sub
       End Class
    
    ' etc.
    
    // C#
    using System;
    using System.Collections;
    // other namespaces here
    
    namespace myproject
    {
       public class WebForm1 : System.Web.UI.Page
       {
       protected System.Data.SqlClient.SqlDataAdapter sqlDataAdapter1;
       // other declarations here
    
       public class MyTemplate : ITemplate
       {
          int itemcount;
          public void InstantiateIn(System.Web.UI.Control container)
          {
          Literal lc = new Literal();
          lc.Text = "Item number: " + itemcount.ToString() + "<BR>";
          itemcount += 1;
          container.Controls.Add(lc);
          }
       }
    
    // etc.
    
  2. Add code in the Page_Load event handler to create an instance of your template class and assign it to the Repeater control's ItemTemplate property.
  3. Finally, in the Page_Load event handler, fill the dataset and then call the Repeater control's DataBind method to set all your efforts in motion.

    After adding the code, the Page_Load handler will look like the following. (The code you have added in Steps 2 and 3 is highlighted here.)

    ' Visual Basic
    Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
       Repeater1.ItemTemplate = New MyTemplate()
       SqlDataAdapter1.Fill(DataSet11)
       Repeater1.DataBind()
    End Sub
    
    // C#
    private void Page_Load(object sender, System.EventArgs e)
    {
       Repeater1.ItemTemplate = new MyTemplate();
       sqlDataAdapter1.Fill(dataSet11);
       Repeater1.DataBind();
    }
    
  4. Run the page.

    You will see a list marching down the page, one row per data-source element.

Adding Different Types of Templates

The examples above illustrate only how to work with a single template, namely the item template. In this section, you will learn how to create a more sophisticated version of your template class, one that allows you to define a header template, footer template, an alternating item template (for the Repeater and DataList controls), and so on.

One possibility is to create a separate template class for each type of template, or a base template class and derived classes for different types of templates. This might be practical if each template is very complex or if each template is quite different.

An alternative approach is to use a single template class for all of them and specify what type of template you are creating. You can pass into your class a value that the class can use to determine what type of template to create. In fact, you can use an enumeration (ListItemType) already defined for template types in Repeater, DataList, and DataGrid controls.

The example below illustrates a more elaborate version of the examples above. The class code includes an explicit constructor this time, which accepts a single string value indicating the template type. In the InstantiateIn method, you use a Select Case or switch statement (depending on your language) to determine what type of template is being created. To show the advantages of creating different types of templates, the example creates a table with a different background color for the alternating item template.

' Visual Basic
Private Class MyTemplate
   Implements ITemplate
   Shared itemcount As Integer = 0
    Dim TemplateType As ListItemType
    Sub New(ByVal type As ListItemType)
        TemplateType = type
    End Sub

   Sub InstantiateIn(ByVal container As Control) _
         Implements ITemplate.InstantiateIn
      Dim lc As New Literal()
      Select Case TemplateType
         Case ListItemType.Header
                lc.Text = "<TABLE border=1><TR><TH>Items</TH></TR>"
         Case ListItemType.Item
            lc.Text = "<TR><TD>Item number: " & itemcount.ToString _
               & "</TD></TR>"
         Case ListItemType.AlternatingItem
            lc.Text = "<TR><TD bgcolor=lightblue>Item number: " _
               & itemcount.ToString & "</TD></TR>"
         Case ListItemType.Footer
            lc.Text = "</TABLE>"
      End Select
      container.Controls.Add(lc)
      itemcount += 1
   End Sub
End Class

// C#
public class MyTemplate : ITemplate
{
   static int itemcount = 0;
   ListItemType templateType;
   public MyTemplate(ListItemType type)
   {
      templateType = type;
   }

   public void InstantiateIn(System.Web.UI.Control container)
   {
      Literal lc = new Literal();
      switch( templateType )
      {
         case ListItemType.Header:
            lc.Text = "<TABLE border=1><TR><TH>Items</TH></TR>";
            break;
         case ListItemType.Item:
            lc.Text = "<TR><TD>Item number: " + itemcount.ToString() +
               "</TD></TR>";
            break;
         case ListItemType.AlternatingItem:
            lc.Text = "<TR><TD bgcolor=lightblue>Item number: " + 
               itemcount.ToString() + "</TD></TR>";
            break;
         case ListItemType.Footer:
            lc.Text = "</TABLE>";
            break;
      }
      container.Controls.Add(lc);
      itemcount += 1;
   }
}

To create the different templates, you use code such as the following:

' Visual Basic
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
   Repeater1.HeaderTemplate = New MyTemplate(ListItemType.Header)
   Repeater1.ItemTemplate = New MyTemplate(ListItemType.Item)
   Repeater1.AlternatingItemTemplate = _
      New MyTemplate(ListItemType.AlternatingItem)
   Repeater1.FooterTemplate = New MyTemplate(ListItemType.Footer)
   SqlDataAdapter1.Fill(DataSet11)
   Repeater1.DataBind()
End Sub

// C#
private void Page_Load(object sender, System.EventArgs e)
{
   Repeater1.HeaderTemplate = new MyTemplate(ListItemType.Header);
   Repeater1.ItemTemplate = new MyTemplate(ListItemType.Item);
   Repeater1.AlternatingItemTemplate = 
      new MyTemplate(ListItemType.AlternatingItem);
   Repeater1.FooterTemplate = new MyTemplate(ListItemType.Footer);
   sqlDataAdapter1.Fill(dataSet11);
   Repeater1.DataBind();
}

Adding Data Binding to Templates

The template class you have defined so far displays only static data. In this section you will therefore complete the template class you have been defining by adding to it the ability to incorporate data from the control's data source.

There are various ways to get access to data from within a template class, depending on how you have created the class. A good way is the one in which the page architecture itself implements data binding. When you add controls to the template, you also add a handler for their DataBinding event. This event will be raised after the template item has been created with all its controls.

Note   You might think that you could simply embed a data-binding expression as a string when creating controls in the template, as you do when defining templates at design time. However, that does not work — it turns out that data-binding expressions are turned into code at a stage of page processing that occurs before your template is being created.

In the handler for the DataBinding event, you have an opportunity to manipulate the contents of the control. Typically (but not necessarily), you fetch data from somewhere and assign it to the control's Text property.

Note   For background on data binding in Web Forms pages, see Web Forms Data Binding in the Visual Studio .NET documentation.

Binding an Event Handler to the Controls

Start by binding an event handler to the controls you create in the template. Using a snippet out of the example above, the code to bind a control in a template to a handler called TemplateControl_DataBinding might look like the following. (You will add the code for the handler later.)

' Visual Basic
      Case ListItemType.Item
         lc.Text = "<TR><TD>"
         AddHandler lc.DataBinding, AddressOf TemplateControl_DataBinding

// C#
      case ListItemType.Item:
         lc.Text = "<TR><TD>";
         lc.DataBinding += new EventHandler(TemplateControl_DataBinding);
         break;
Note   For details on how to add event handlers dynamically, see AddHandler and RemoveHandler (for Visual Basic) and Events Tutorial (for Visual C#) in the Visual Studio .NET documentation.

Note that in this example the text you add to the literal control's Text property is different than in the previous example. It contains only the beginning of the table row and cell for the item template. You will complete the cell and row in the data binding event handler.

Creating the Event Handler

Now you need a handler that will be called when the control is data bound. The handler is part of your template class; you create it as a peer of the class's other methods (such as InstantiateIn).

The signature of the handler is shown in the following example. The name of this handler must match the name you used when binding the event earlier.

' Visual Basic
Private Sub TemplateControl_DataBinding(ByVal sender As Object, _
ByVal e As System.EventArgs)

// C#
private void TemplateControl_DataBinding(object sender, 
System.EventArgs e)

When your method is called, you are passed a reference to the control in the sender argument, so you can set its properties. Because the sender argument is passed as an object, you need to cast it to the right type of control — in this example, a Literal.

How do you get to data? For each element in a data source, the Repeater, DataList, or DataGrid control produces a single item (whose contents are based on the template). Each item has a property called DataItem that provides access to the data for that item — in effect, the corresponding data row.

When the data-binding event handler is called, however, you are working with a specific control, not with the template item. You need to climb up the control hierarchy from your control to the template item. The best way to do this is via your control's NamingContainer property, which returns a reference to the template item in which you created the control. (In some cases, you could also use your control's Parent property to get a reference to the container. However, if your control is nested inside another control — for instance, if your control is inside a Panel control or a Table control — then the Parent property does not return a reference to the template item.)

To bind a control in a template to specific data, therefore, you do the following:

  • Get a reference to the template item. You create a variable to hold the reference and then assign it the value you get from your control's NamingContainer property.
  • Use that reference to get the naming container's (the template item's) DataItem property.
  • Extract the individual data element (data column, for example) from the DataItem object and use it to set a property of the control you are binding.

The following code illustrates one way to do this, based on the examples you have been working with. It is a complete data-binding event handler for the Literal controls being created for your templates.

The code creates a variable called container of type RepeaterItem (since you have been working with the Repeater control) and then sets this variable to the NamingContainer property of the control being data bound. You can then use the reference to get to the DataItem of the container, which is an object. An easy way to do so is to call the Eval method of the DataBinder object (the same method often used in data-binding expressions), passing it a reference to the container's DataItem object and the name of the field to get.

If you have multiple types of controls in your templates, you would need to create a different data-binding event handler for each of the control types.

' Visual Basic
Private Sub TemplateControl_DataBinding(ByVal sender As Object, _
ByVal e As System.EventArgs)
   Dim lc As Literal
   lc = CType(sender, Literal)
   Dim container As RepeaterItem
   container = CType(lc.NamingContainer, RepeaterItem)
   lc.Text &= DataBinder.Eval(container.DataItem, "CategoryName")
   lc.Text &= "</TD></TR>"
End Sub

// C#
private void TemplateControl_DataBinding(object sender, 
System.EventArgs e)
{
   Literal lc;
   lc = (Literal) sender;
   RepeaterItem container = (RepeaterItem) lc.NamingContainer;
   lc.Text += DataBinder.Eval(container.DataItem, "CategoryName");
   lc.Text += "</TD></TR>";
}

Creating Templates Programmatically in the DataGrid Control

The procedures described above work for both the Repeater and the DataList controls. You can also create templates programmatically for the DataGrid control. The process for creating the template itself is the same. But there are a few differences in how you use the templates in a DataGrid control:

  • You do not create item templates for the grid itself; instead, you create them for a column in the grid.
  • There are slightly different templates for a DataGrid column than for a Repeater or DataList control. A DataGrid column does not include an alternating item or separator template. However, like the DataList control, it does include an EditItem template.

A quick review: in a DataGrid control, you can have these types of columns:

  • Bound columns — columns generated from specific elements in your data source.
  • Button columns — columns with predefined buttons (Edit, Update, Cancel, Delete, or Select).
  • Hyperlink columns — columns in which each item is a hyperlink to a URL you specify at design time.
  • Template columns — columns that are based on a template you specify.

As you might guess, the last type of column is the one you use programmatically created templates for. To put it another way: if you want to change the controls of a DataGrid column at run time, you should use a template column and create its template on the fly.

The following example shows how to create two template columns for a DataGrid control and then assign these templates to the columns. The first part is the class to create the templates. It is almost identical to the earlier example, except for two small changes. One is that the template class constructor accepts two parameters; the second parameter allows you to pass in the name of the column you are creating. The second change is that this example includes code to create an EditItem template, which creates a Textbox control.

' Visual Basic
Private Class DataGridTemplate
   Implements ITemplate
   Dim templateType As ListItemType
   Dim columnName As String
   Sub New(ByVal type As ListItemType, ByVal ColName As String)
      templateType = type
      columnName = ColName
   End Sub
   Sub InstantiateIn(ByVal container As Control) _
        Implements ITemplate.InstantiateIn
      Dim lc As New Literal()
      Select Case templateType
         Case ListItemType.Header
            lc.Text = "<B>" & columnName & "</B>"
            container.Controls.Add(lc)
         Case ListItemType.Item
            lc.Text = "Item " & columnName
            container.Controls.Add(lc)
         Case ListItemType.EditItem
            Dim tb As New TextBox()
            tb.Text = ""
            container.Controls.Add(tb)
         Case ListItemType.Footer
            lc.Text = "<I>Footer</I>"
            container.Controls.Add(lc)
      End Select
   End Sub
End Class

// C#
public class DataGridTemplate : ITemplate
   {
      ListItemType templateType;
      string columnName;
      public DataGridTemplate(ListItemType type, string colname)
      {
         templateType = type;
         columnName = colname;
      }

      public void InstantiateIn(System.Web.UI.Control container)
      {
         Literal lc = new Literal();
         switch(templateType)
         {
            case ListItemType.Header:
               lc.Text = "<B>" + columnName + "</B>";
               container.Controls.Add(lc);
               break;
            case ListItemType.Item:
               lc.Text = "Item " + columnName;
               container.Controls.Add(lc);
               break;
            case ListItemType.EditItem:
               TextBox tb = new TextBox();
               tb.Text = "";
               container.Controls.Add(tb);
               break;
            case ListItemType.Footer:
               lc.Text = "<I>" + columnName + "</I>";
               container.Controls.Add(lc);
               break;
         }
      }
   }

The next example shows how to create the template columns and assign the templates to them.

' Visual Basic
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load

   Dim tc1 As New TemplateColumn()
   tc1.HeaderTemplate = New _
      DataGridTemplate(ListItemType.Header, "Column1")
   tc1.ItemTemplate = New DataGridTemplate(ListItemType.Item, "Column1")
   tc1.EditItemTemplate = New _
      DataGridTemplate(ListItemType.EditItem, "Column1")
   tc1.FooterTemplate = New _
      DataGridTemplate(ListItemType.Footer, "Column1")
   DataGrid1.Columns.Add(tc1)
   
   Dim tc2 As New TemplateColumn()
   tc2.HeaderTemplate = New _
      DataGridTemplate(ListItemType.Header, "Column2")
   tc2.ItemTemplate = New DataGridTemplate(ListItemType.Item, "Column2")
   tc2.EditItemTemplate = New _
      DataGridTemplate(ListItemType.EditItem, "Column2")
   tc2.FooterTemplate = New _
      DataGridTemplate(ListItemType.Footer, "Column2")
   DataGrid1.Columns.Add(tc2)
   SqlDataAdapter1.Fill(DsCategories1)
   DataGrid1.DataBind()
End Sub

// C#
private void Page_Load(object sender, System.EventArgs e)
{
   TemplateColumn tc1 = new TemplateColumn();
   tc1.HeaderTemplate = new 
      DataGridTemplate(ListItemType.Header, "Column1");
   tc1.ItemTemplate = new DataGridTemplate(ListItemType.Item, "Column1");
   tc1.EditItemTemplate = new 
      DataGridTemplate(ListItemType.EditItem, "Column1");
   tc1.FooterTemplate = new 
      DataGridTemplate(ListItemType.Footer, "Column1");
   DataGrid1.Columns.Add(tc1);

   TemplateColumn tc2 = new TemplateColumn();
   tc2.ItemTemplate = new DataGridTemplate(ListItemType.Item, "Column2");
   tc2.HeaderTemplate = new 
      DataGridTemplate(ListItemType.Header, "Column2");
   tc2.EditItemTemplate = new 
      DataGridTemplate(ListItemType.EditItem, "Column2");
   tc2.FooterTemplate = new 
      DataGridTemplate(ListItemType.Footer, "Column2");
   DataGrid1.Columns.Add(tc2);

   sqlDataAdapter1.Fill(dataSet11);
   DataGrid1.DataBind();
}

Wrapping Up

The examples in this paper, although simple, show you all the basics you need for creating templates in code. Of course, the templates you create in your own applications are likely to be more complex — everything you have seen in this paper could easily be done at design time. But for now, you have all the tools you need to create templates on the fly.

Show:
© 2014 Microsoft