In ASP.NET 1.x, when using a templated control, we have a choice between specifying these templates inline, or loading them dynamically using the LoadTemplate
method. This would allow us to load an .ascx file and get an implementation of the ITemplate
interface which we could then assign to the desired template on the server control.
In ASP.NET 2.0 this obviously still applies, but there's a caveat - if you want to take advantage of the new two-way binding features in controls such as the new FormView control, you can only specify the templates inline. The reason for this is that in order to provide the two-way binding, ASP.NET needs to generate some behind the scene code in order to implement a new IBindableTemplate
interface - just as the ITemplate was used previously. Unfortunately, the ASP.NET team didn't get time to implement an equivalent LoadBindableTemplate
method to fetch an implementation of IBindableTemplate
- and so the only situation in which ASP.NET generates the required code is for the inline scenario.
The Workaround
So, what can we do? Well, the best workaround that I've been able to come up with goes as follows. In the .ascx file we're wanting to load our template from, instead of just providing the contents of the template, we wrap it within an <ItemTemplate>
tag of a FormView or any other two-way bindable control, such as this:
<%@ Control %>
<asp:FormView runat="server">
<ItemTemplate>
<asp:TextBox runat="server" Text=<%# Bind("UserName") %> />
</ItemTemplate>
</asp:FormView>
This means that when we load this control (using the standard LoadControl
function), ASP.NET will have generated the required code and implemented IBindableTemplate
for us. We can then use the following code:
public IBindableTemplate LoadBindableTemplate(string path) {
Control c = Page.LoadControl(path);
FormView fv = c.Controls[0] as FormView;
if (fv==null)
throw new Exception("Required FormView control not found as the first child of specified template");
return (IBindableTemplate)fv.ItemTemplate;
}
Then we can simply use this as we'd have used LoadTemplate
(note you need to do this no later than the OnInit event), on the FormView object (in this case, called myFormView) that we wanted to dynamically load the template for.
myFormView.EditItemTemplate = LoadBindableTemplate("/myBindableTemplate.ascx");
Combining Templates
We can also now consider doing things such as combining two templates into one - for example, loading a common set of input fields from one template, and dynamically loading a second from elsewhere. To do this, we create a class that implements IBindableTemplate
, and takes two IBindableTemplate
implementations in the constructor.
public class JointBindableTemplate : IBindableTemplate
{
IBindableTemplate _firstTemplate;
IBindableTemplate _secondTemplate;
public JointBindableTemplate(IBindableTemplate firstTemplate, IBindableTemplate secondTemplate)
{
this._firstTemplate = firstTemplate;
this._secondTemplate = secondTemplate;
}
public IOrderedDictionary ExtractValues(Control container)
{
// extract the values for each of the templates
IOrderedDictionary d1 = _firstTemplate.ExtractValues(container);
IOrderedDictionary d2 = _secondTemplate.ExtractValues(container);
// copy over to the second collection
foreach (object key in d1.Keys)
d2.Add(key, d1[key]);
// return the combined collection
return d2;
}
public void InstantiateIn(Control container)
{
// create a container control
Control c = new Control();
// instantiate the two templates into this control
_firstTemplate.InstantiateIn(c);
_secondTemplate.InstantiateIn(c);
// add our control to the container we were passed
container.Controls.Add(c);
}
}
We can then use the following:
IBindableTemplate t1 = LoadBindableTemplate("/myCommonTemplate.ascx");
IBindableTemplate t2 = LoadBindableTemplate("/myExtendedTemplate.ascx");
JointBindableTemplate c = new JointBindableTemplate(t1,t2);
myFormView.EditItemTemplate = c;
Voila! If anyone has any comments on this - or knows a better workaround, then please let me know.
Comments