A while ago I wrote about a simple method to give web parts an easy to configure design experience. The method I came up with made use of the ASP.NET Page method ParseControl.
In my original example, the developer had to do some manual work like finding the templated control once it was added to the page, this was a tedious and ugly design. At the time it was more of an idea rather than something that I had build seriously, but recently I’ve put a bit more thought into the idea of web parts with a configurable UI template.
I’ve built on the original example, but with the goal of abstracting the ParseControl and ControlBinding implementation.
This example simply writes a title and description:
We want to give end users the ability to change the way this title and description look without any web part code changes.
This is done via a template, which is a property of the web part:
Notice this is ASP.NET syntax
The template can be easily changed:
To produce a different rendering, without the need for any developer assistance:
From a developers point of view, we really want to just declare some variables and be able to use them as if they were part of the web part, without thinking about FindControl or any implementation detail.
I put together a base web part that looks for any private variables with a custom attribute called ControlBinding and via reflection sets this variable to the control instance specified in the attribute, so for the above example the code for the web part would look like:
public class TemplateTest : WebPartTemplateBase
{
[ControlBinding("titleLabel")]
Label titleLabel = null;
[ControlBinding("descriptionLabel")]
Label descriptionLabel = null;
protected override void OnPreRender(EventArgs e)
{
if (titleLabel != null)
{
titleLabel.Text = "Some text set from code";
}
if (descriptionLabel != null)
{
descriptionLabel.Text = "some text description that was set in code";
}
base.OnPreRender(e);
}
}
So now all the developer needs to do is add an attribute to private variables with the name of the templated control they want to obtain an instance to. This fits very much with the code behind / partial class philosophy that web developers are used to working with.
If we look at some more complex scenario’s we start finding a few little issues, for example lets say that we want to use a GridView to do some data binding, in a normal aspx page we would use code nuggets to do the data binding:
<asp:GridView ID="GridView1" runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:label id="dataItemLabel" runat="server"
Text='<%DataBinder.Eval(Container, "SomeValue"%>'></asp:label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
If we used this in our template we would find that it doesn’t work. There is however a work around:
We can subscribe to the RowDataBound event and we can find the controls we wish to data bind to:
[ControlBinding("GridView1")]
GridView gridView = null;
protected override voidOnPreRender(EventArgs e)
{
if(gridView != null)
{
gridView.RowDataBound += newGridViewRowEventHandler(gridView_RowDataBound);
gridView.DataSource = GetData();
gridView.DataBind();
}
base.OnPreRender(e);
}
void gridView_RowDataBound(objectsender, GridViewRowEventArgs e)
{
Label itemLabel = e.Row.FindControl("dataItemLabel") as Label;
if(itemLabel != null)
{
itemLabel.Text = e.Row.DataItem.ToString(); //cast and use the data item
}
}
In which case the template would look like:
<asp:GridView ID="GridView1" runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:label id="dataItemLabel" runat="server"></asp:label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
I’m still in the early stages of experimenting with this pattern, so it could be the case that it’s totally unsuitable for some scenario’s.
If your interested in exploring this pattern a little more, you can download the code to the base class here.