A great feature of ASP.NET is its templating system, which allow controls to outsource whole or parts of their layout definition. Using templates, we can declaratively define how the Repeater, GridView and other templated controls render the data bound to them. However, creating instances of such controls programmatically and dynamically adding them to a page becomes a bit trickier because of their templated nature. In this post, I'll show you how it can be done.
Let us take the Repeater as the basis for our example. What we want to accomplish, is to programmatically assign a template to a Repeater control and bind some data to it. The Repeater allows several kinds of templates to be assigned to it - we'll stick to the ItemTemplate and SeparatorTemplate for this exercise in order to create a comma-separated list of names. This will let us cover the basics of creating and databinding templates programmatically without polluting the code with lots of verbose control-tree manipulations.
The ITemplate Interface
If we look at the ItemTemplate and SeparatorTemplate properties on the Repeater class, we'll see that they both expect an object of type ITemplate. Rather obviously then, we need to implement this interface. And what do you know, its only got a single method. This is looking promising already - the InstantiateIn method exposed by the ITemplate interface is surely our doorway to programmatically populating the template.
What this method does, is pass us the container that should contain all the controls in the template. If we were creating a template declaratively (in our .aspx page), then ASP.NET would dynamically generate a class that implemented this ITemplate interface and populated the container as specified by our markup. That's exactly what we want to handle by ourselves here...
The CompiledTemplateBuilder
Okay, so I lied - we don't even have to implement ITemplate. Sure, we could if we wanted to - it would be very easy, and if you have a very specific kind of template that you want to build then I would probably recommend it. But for this example we just want a generic way of building our template, and the ASP.NET framework already caters for that with the CompiledTemplateBuilder class. So lets use that then. Here's what the Page_Load method looks like for our example:
protected void Page_Load(object sender, EventArgs e)
{
Repeater repeater = new Repeater();
repeater.ID = "_repeater";
this.Controls.Add(repeater);
// create the templates
repeater.ItemTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(BuildItemTemplate));
repeater.SeparatorTemplate = new CompiledTemplateBuilder(new BuildTemplateMethod(BuildSeparatorTemplate));
CompiledTemplateBuilder builder =
// bind the data
repeater.DataSource = new Person[] { new Person("Fredrik"), new Person("Jack"), new Person("Maria") };
repeater.DataBind();
}
Here, we've created a new Repeater instance, assigned two CompiledTemplateBuilders to the ItemTemplate and SeparatorTemplates, and bound some data to it. Next, we need to actually implement the BuildItemTemplate and BuildSeparatorTemplate methods. Lets start with the SeparatorTemplate - its dead easy:
void BuildSeparatorTemplate(Control container)
{
container.Controls.Add(new LiteralControl(", "));
}
All we've done, is add the literal string that should appear between each item. The ItemTemplate is a bit more tricky, because its got databound content.
Databound Controls
Databound content is fairly simple to accomplish with declarative templates, thanks to the helpful DataBinder class and some nifty syntax that ASP.NET understands. If we wanted to create the item template for this example declaratively, it would look something like this:
<ItemTemplate>
<asp:Literal
ID="_name"
runat="server"
Text='<%# Eval("Name") %>' />
</ItemTemplate>
The question then, is how do we translate that into code for the BuildItemTemplate method? Its not as hard as you may think - the part to pay attention to is the handling of the DataBinding event:
void BuildItemTemplate(Control container)
{
Label name = new Label();
container.Controls.Add(name);
name.ID = "_name";
name.DataBinding += new EventHandler(
delegate
{
RepeaterItem item = container as RepeaterItem;
Person person = item.DataItem as Person;
name.Text = person.Name;
});
}
The DataBinding event gets raised when the DataBind() call up there in the Page_Load method happens - one time for each row in the list of data we've bound to the Repeater. Since we're dealing with a Repeater control, we know that the container will be a RepeaterItem, and from there its easy as pie to get to the DataItem (which in this example is an instance of the Person class) and then the value we want. Alternatively, we could use the DataBinder helper class here, which is what we're essentially doing in the declarative template (DataBinder.Eval). An alternative to handling databinding for each control in the template separately, would be to handle the ItemDataBound event of the Repeater and looking up the controls there to assigning their values:
void repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
Label label = e.Item.FindControl("_name") as Label;
label.Text = (e.Item.DataItem as Person).Name;
}
Which way you do it is really down to preference, although having to use FindControl in the ItemDataBound means not only do you loose type safety and introduce the possibility of a null reference exception by misspelling the ID in the string literal, but its questionable whether creating multiple delegates is really more expensive than executing FindControl - especially for larger templates.
The Possibilities, Man!
In this post, I've outlined one way to programmatically instantiate a template for a templated control. And while its a perfectly usable solution, you should be aware that it is not the only one, nor is it the perfect one. Remember that templated controls can dynamically load declarative templates from special template files out of the box, and you can easilly write some code to load a declarative template from a string (stored in a database, perhaps?). And then there's the question of whether to use instances of the Repeater class directly, or to create your own specific-purpose classes that inherit from a Repeater and wraps a view of something neatly into one reusable control - for this example it could be a PersonList control or something like that. And what about dealing with scenarios where the datasource you're binding to the Repeater is empty? A NoDataTemplate would be quite handy to have then... Just writing this paragraph I can think of a dosen topics pertaining to templated controls that well deserve their own post - so take this example for what it is, and make sure you go at least one or two steps further when you decide to employ the tricks told here :)