Click here to Skip to main content
Click here to Skip to main content

Dynamically create item templates server-side

, 23 Jun 2012
Rate this:
Please Sign up or sign in to vote.
How to dynamically create item templates on the server-side in ASP.NET.

Anyone who has ever developed with ASP.NET coming from classic ASP or some other embedded/inline based web development platform knows that ASP.NET is quite different. ASP.NET, along with other Microsoft-brewed development platforms like WPF are attempting to make it easier (or at least more intuitive) to separate the Presentation Layer from the logic layer or data access layer.

This is all good and fun, but sometimes when you are developing a website where you need to do something that would be quite simple and mindless in something like PHP or classic ASP, in ASP.NET it ends up being rather nontrivial.

One such issue is when you start dynamically changing the presentation of data, based on logic applied to the data itself. Often times, ASP.NET works rather well for this type of thing – inline Item Templates in the markup with databound controls such as the ListView, Repeater, etc., are a wonderfully intuitive way to accomplish this. The issue comes when this isn’t just simple logic anymore, but rather complex logic. Even more, what if you don’t know what your template is going to look like? What if the actual template is being created dynamically?

In instances like this, you must abandon the ItemTemplate pattern in the markup, and you must implement the ITemplate interface or the IBindableTemplate interface. These interfaces are brilliant creations (for the most part) on Microsoft’s part, but it then becomes extremely difficult (or at least verbose) to introduce standard HTML objects into the template.

This was a problem that particularly bothered me in a recent project I was working on, so I wrote up a little implementation of the IBindableTemplate interface which took care of this problem rather simply for me.

public class LiteralItemTemplate : IBindableTemplate
{
    private string formatString;
    public delegate string LambdaExpr(object container);
    public Dictionary<string,> LambdaDictionary = new Dictionary<string,>();

    public LiteralItemTemplate(string FormatString)
    {
        formatString = FormatString;
    }

    public void InstantiateIn(Control container)
    {
        LiteralControl ctl = new LiteralControl();
        ctl.DataBinding += new EventHandler(OnDataBinding);
        container.Controls.Add(ctl);
    }

    public void OnDataBinding(object sender, EventArgs e)
    {
        LiteralControl target = (LiteralControl)sender;
        Control item = target.BindingContainer;
        target.Text = Regex.Replace(formatString, @"(\{.+?\})",
                m =>
                {
                    string word = m.Groups[1].Value;
                    int ind = word.IndexOf(':');
                    return string.Format(
                        string.Format("{{0{0}}}",
                            (ind > 0) ? word.Substring(
                               ind, word.Length - ind - 1) : string.Empty
                            ),
                        evaluateKey(item.DataItem, word.Substring(
                                  1, ((ind>0)?(ind-1):(word.Length - 2))))
                        );
                });
    }

    private object evaluateKey(object container, string key)
    {
        return (LambdaDictionary.ContainsKey(key))?
          LambdaDictionary[key](container):DataBinder.Eval(container, key);
    }

    public System.Collections.Specialized.IOrderedDictionary ExtractValues(Control container)
    {
        System.Collections.Specialized.OrderedDictionary _table = 
           new System.Collections.Specialized.OrderedDictionary();
        return _table;
    }
}

This class is pretty simple. I have implemented 2 of the 3 necessary members of IBindableTemplate. The third, ExtractValues, is only necessary for two-way databinding, which I did not need at the time.

For instance, we can replace the simple ItemTemplate created in the markup below:

<asp:Repeater ID="myDataBoundControl" runat="server">
    <ItemTemplate>
        <tr>
            <td>
                <a href='<%# Eval("WebsiteUrl") %>'>
                <%# Eval("CompanyName") %>
                </a>
            </td>
            <td><%# Eval("IndustrySector") %></td>
            <td><%# string.Format("{0:#,0}",Eval("MarketCap") %></td>
        </tr>
    </ItemTemplate>
</asp:Repeater>

and replace it with the following:

protected void Page_Load(object sender, EventArgs e)
{
    Repeater myDataBoundControl = new Repeater();

    myDataBoundControl.ItemTemplate = new LiteralItemTemplate("<tr><td>" + 
      "<a href=\"{WebsiteUrl}\">{CompanyName}</a></td>" + 
      "<td>{IndustrySector}</td><td>{MarketCap:#,0}</td></tr>");

    myDataBoundControl.DataSource = GetDataSource();
    myDataBoundControl.DataBind();
}

In this case, you use syntax similar to the string.Format() syntax, but slightly different. If I would like to apply DataBinder.Eval(“FieldName”), I would pass a string with {FieldName} inside the constructor.

Further, you can use a format string preceded by a colon just like in string.Format. For instance, string.Format(“{0:#,0}”,DataBinder.Eval(“FieldName”)) is equivalent to using the format string {FieldName:#,0} inside the constructor parameter.

Further, if you would like to apply a certain logic to the incoming data, rather than directly display it, you can do this very easily using lambda expressions.

For example:

protected void Page_Load(object sender, EventArgs e)
{
    Repeater myDataBoundControl = new Repeater();

    LiteralItemTemplate t = 
       new LiteralItemTemplate("<tr class=\"{LambdaCssClass}\"><td>" + 
       "<a href=\"{WebsiteUrl}\">{CompanyName}</a></td>" + 
       "<td>{IndustrySector}</td><td>{LambdaField}</td></tr>");

    t.LambdaDictionary.Add("LambdaField", 
      c => BusinessLogicToString(DataBinder.Eval(c, "CompanySize")));
    t.LambdaDictionary.Add("LambdaCssClass", 
      c => CompanyIDToCssClass(DataBinder.Eval(c, "CompanyID")));

    myDataBoundControl.ItemTemplate = t;

    myDataBoundControl.DataSource = GetDataSource();
    myDataBoundControl.DataBind();
}

The lambda expressions accept the contract:

public delegate string LambdaExpr(object container);

Here, container is the DataBinding container, which can then be used inside the lambda expression with the DataBinder.Eval function to retrieve any necessary contextual data.

I would love to hear some potential improvements anyone has to offer. I have not tested the performance, but it would also be interesting to see how these templates stack up to the inline templates in the markup.

Some known issues

  • Getting the right Databinding container is sometimes a problem.
  • The ExtractValues method needs to be implemented to allow for two-way data binding.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Leland Richardson
Founder
United States United States
My name is Leland Richardson. I love learning. At the time of writing this I am 23 years old and live in Houston, TX. I was born in West Palm Beach, Florida, grew up in St. Louis, Missouri, and went to school in Houston, Texas at Rice University.
 
At Rice I received two degrees: one in Physics and one in Mathematics. I love both. I never received any formal education on Computer Science, however, you will find that most of this blog will be about programming and web development. Nevertheless, I think being a good programmer is about being good at learning, and thinking logically about how to solve problems - of which I think my educational background has more than covered.
 
Since high-school, I had found that the easiest way to make money was by programming. Programming started off as a hobby and small interest, and slowly grew into a passion.
 
I have recently started working on a new startup here in Houston, TX. I wont bore you with the details of that just yet, but I am very excited about it and I think we can do big things. We plan to launch our project this year at SXSW 2013. What I will say for now, is that we would like to create a company of talented software developers who are similarly ambitious and want to create cool stuff (and have fun doing it).
Follow on   Twitter

Comments and Discussions

 
GeneralMessage Automatically Removed PinmemberRavi Kumar30-Apr-14 20:46 
QuestionMultiple controls with explicit IDs in each RepeaterItem? Pinmemberliycncn31-Oct-12 6:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 24 Jun 2012
Article Copyright 2011 by Leland Richardson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid