Using Templated Controls Programmatically

An entry about asp.net Publication date 9. October 2007 19:36

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 :)

Currently rated 4.5 by 8 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS