Introduction
Data bound controls are most popular amongst developers because of their verticality. Controls such as GridView and DataList are popular not just because they provide rich features out of the box but because they allow great deal of customization. To that end templates go a long way in providing a customized look and feel. The concept of templates can be extended to custom controls also. With templates in place you can customize the way your data is presented to the user. This lesson is going to illustrate how this can be done.
Data Binding and Templates
Strictly speaking a templated control need not be always data bound. However, in most of the real world cases data drives your presentation layer. Hence it is worth to learn how to develop a templated control that is data bound. This means the control will allow you to use the familiar Eval() and Bind() data binding expressions.
In order to develop a data bound templated control you need to inherit your custom control class from CompositeDataBoundControl base class. The CompositeDataBoundControl class represents the base class for tabular data bound controls (GridView for example). When you inherit your custom control class from the CompositeDataBoundControl base class you must override the CreateChildControls(IEnumerable, Boolean) method to create the control hierarchy. Individual child controls can then be accessed using the Controls collection.
If you wish to use Eval() or Bind() data binding expressions in your custom control then you also need to create a class that implements the IDataItemContainer interface. This interface enables data bound controls to identify a data item object for data binding operations.
Example
In order to demonstrate what you just learnt let's develop a simple templated control. Begin by creating a new ASP.NET web site adding a new class in its App_Code folder. The class declaration is shown below:
namespace BinaryIntellect
{
public class MyTemplatedControl:CompositeDataBoundControl
{
...
Here we created MyTemplatedControl class within BinaryIntellect namespace. The MyTemplatedControl class inherits from CompositeDataBoundControl base class. The MyTemplatedControl is our main custom control class.
Now add another class named TemplateItem as shown below:
public class TemplateItem:Control,IDataItemContainer
{
...
The TemplateItem class inherits from Control base class and more importantly it implements IDataItemContainer interface. The TemplateItem class represents an individual data item within a template. The IDataItemContainer interface forces the class to implement three properties viz. DataItem, DataItemIndex and DisplayIndex.
Declare three variables inside the TemplateItem class as shown below:
private object _DataItem;
private int _DataItemIndex;
private int _DisplayIndex;
The _DataItem variable is of type object and represents individual data item (object from a collection, DataRow etc.) that is bound with the template control. The _DataItemIndex variable indicates the index of the data item in the data source and the _DisplayIndex indicates position of the item as displayed in the templated control. Remember that an item can be at 100th position inside a data source but because of features such as paging and sorting might get displayed at say 10th position inside the templated control.
These three variables are wrapped in three read-only properties as shown below:
public object DataItem
{
get { return _DataItem; }
}
public int DataItemIndex
{
get { return _DataItemIndex; }
}
public int DisplayIndex
{
get { return _DisplayIndex; }
}
Since these properties are read-only implement a constructor that will accept the values for these members.
public TemplateItem(object dataItem, int index)
{
_DataItem = dataItem;
_DataItemIndex = _DisplayIndex = index;
}
Notice that in our example we are not making any specific use of DisplayIndex property.
Now open the MyTemplatedControl class and add the following property declaration to it:
private ITemplate _itemtemplate;
[TemplateContainer(typeof(TemplateItem))]
public ITemplate MyItemTemplate
{
get { return _itemtemplate; }
set { _itemtemplate = value; }
}
Here, we declared a variable of type ITemplate. This variable is wrapped in a property MyItemTemplate. The MyTemplate property indicates a template of our custom control. The way inbuilt controls such as GridView and DetailsView define templates such as ItemTemplate and HeaderTemplate we define MyItemTemplate template.
Observe that the MyItemTemplate property is decorated with TemplateContainer attribute. The TemplateContainer attribute specifies a class that is acting as a data item for data binding purposes.
Now override the CreateChildControls() method as shown below:
protected override int CreateChildControls
(System.Collections.IEnumerable dataSource,
bool dataBinding)
{
int index=0;
if (dataBinding)
{
foreach (object dataItem in dataSource)
{
if (_itemtemplate != null)
{
TemplateItem container = new
TemplateItem(dataItem, index);
_itemtemplate.InstantiateIn(container);
Controls.Add(container);
index++;
container.DataBind();
}
}
}
return index;
}
The CreateChildControls() method accepts two parameters viz. the data source and a Boolean value indicating if the CreateChildControls() method was called during data binding.
inside we iterate through the data source. With each iteration we create an instance of TemplateItem class. Remember that data source will be supplied from web form and each element of the data source acts as the data item for the template. Then we call InstantiateIn() method of the ITemplate object and specify that the controls from the template will have TemplateItem object as their container. Finally DataBind() method is called on the TemplateItem object. Notice that the index variable simply keeps track of the item index and is returned as a return value of CreateChildControls() method.
This completes our custom control. Let's use it on some web form. We will bind our control with a generic collection of Employee objects. The Employee class is shown below:
public class Employee
{
public Employee(string fname, string lname)
{
_firstname = fname;
_lastname = lname;
}
private string _firstname;
public string FirstName
{
get { return _firstname; }
set { _firstname = value; }
}
private string _lastname;
public string LastName
{
get { return _lastname; }
set { _lastname = value; }
}
}
The Employee class defines two simple properties namely Firstname and LastName. The constructor initializes the respective variables.
Now open the default web form and add the @Register directive in its markup file.
<%@ Register
Namespace="BinaryIntellect"
TagPrefix="cc1" %>
Then declare one instance of MyTemplatedControl as shown below:
<cc1:MyTemplatedControl runat="server" ID="control1">
<MyItemTemplate>
<h1>
<%# Eval("FirstName") %>
<%# Eval("LastName") %>
</h1>
</MyItemTemplate>
</cc1:MyTemplatedControl>
Notice the use of <MyItemTemplate> markup tag. Recollect that we created MyItemTemplate property in MyTemplatedControl class which is of type ITemplate. Since our custom control class inherits from CompositeDataBoundControl base class we can use Eval() expression inside our template.
After defining the template add the following code in the Page_Load event handler.
protected void Page_Load(object sender, EventArgs e)
{
List<Employee> data = new List<Employee>();
Employee e1 = new Employee("Nancy", "Davalio");
Employee e2 = new Employee("Andrew", "Fuller");
data.Add(e1);
data.Add(e2);
control1.DataSource = data;
control1.DataBind();
}
Here, we simply populate a generic list of Employee objects and bind our templated control with it.
A sample run of the web form should resemble the following screen shot.