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