Saturday, July 28, 2007

Implementing ITemplate as Anonymous Method

ITemplate interface is used by multiple ASP.NET controls to setup repeatable or dynamically instantiated parts of a control. 

The ITemplate interface consists of a single method InstantiateIn which takes single parameter container.  Implementation is expected to instantiate template controls in the given container when InstantiateIn method is invoked.

public interface ITemplate {
void InstantiateIn(Control container);
}

 


ASP.NET runtime supports ITemplate by providing built-in facilities for creating instances of ITemplate from markup.  ASP.NET runtime assigns UpdatePanel.ContentTemplate property a reference to ITemplate implementation which instantiates a Button.

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Button ID="Button1" runat="server" Text="Button" />
</ContentTemplate>
</
asp:UpdatePanel>

Most often ITemplate interface implemented by providing ASP.NET markup for server control properties as in the previous example or as UserControls. 


However, if you often need to implement ITemplate interface in code it becomes quite boring to create a new class for each case. Such classes typically look like:

using System.Web.UI;
using System.Web.UI.WebControls;

public class TemplateImplementation : ITemplate {
private string m_someUsefulData;

public TemplateImplementation(string someUsefulParameter) {
m_someUsefulData = someUsefulParameter;
}

public void InstantiateIn(Control container) {
Button b = new Button();
b.ID = "B";
b.Text = m_someUsefulData;
container.Controls.Add(b);
}
}

 


and then used like:

emailField.ItemTemplate = new TemplateImplementation("Some Text");

This class constructor takes parameters and store them in private fields for use them in the InstantiateIn method.  You need to pass everything you need to instantiate controls into the constructor.


Fortunately, compiler can do all this routine for us allowing to write straightforward code to instantiate required controls in context of the place where ITemplate property is assigned.  This can be accomplished by creating generic ITemplate implementation taking delegate to a method creating controls as constructor parameter.

public delegate void InstantiateTemplateDelegate(Control container);
public class GenericTemplateImplementation : ITemplate {
private InstantiateTemplateDelegate m_instantiateTemplate;

public void InstantiateIn(Control container) {
m_instantiateTemplate(container);
}

public GenericTemplateImplementation(InstantiateTemplateDelegate instantiateTemplate) {
m_instantiateTemplate = instantiateTemplate;
}
}

This simple ITemplate implementation can be used to assign ItemTemplate property of a TemplateField column in the GridView.  Your code will be similar to:

string buttonText = "Get e-mail";
TemplateField emailField = new TemplateField();
emailField.HeaderText = "Dynamically Added E-Mail Field";
emailField.ItemTemplate = new GenericTemplateImplementation(
delegate(Control container)
{
Button b = new Button();
b.ID = "b";
b.Text = buttonText;
container.Controls.Add(b);
});
GridView1.Columns.Add(emailField);

 


The code creating button in the previous sample can access buttonText variable which is defined in the context where ItemTemplate property is assigned.  You, therefore, avoid creating new class and passing the entire required context to its constructor. 

4 comments:

Crazy T-Mack said...

Thanks...this really helped out with creating dynamic controls.

colinbashbash said...

How would you add a Eval statement to this? Take:

[ asp:LinkButton ID="lnk" runat="server" CommandName="lnk"
CommandArgument='< % #Eval("Id") % > '] < % #Eval("Name") % > [ / asp:LinkButton]

Julien Patte said...

Thanks for the tip!

I also needed to add a "Eval-like" statement to this, to be able to dynamically bind the content of the TemplateField with the data object.

A solution is to use the DataBinding event of the template's controls, like this:

TemplateField emailField = new TemplateField();
emailField.HeaderText = "Dynamically Added E-Mail Field";
emailField.ItemTemplate = new GenericTemplateImplementation( delegate( Control container )
{
Button btn = new Button();
btn.DataBinding += new EventHandler(btn_DataBinding);
container.Controls.Add(btn);
} );
GridView1.Columns.Add( emailField );

-----

void btn_DataBinding( object sender, EventArgs e )
{
Button btn = (Button)sender;
object dataItem = DataBinder.GetDataItem(btn.NamingContainer);
btn.Text = (string)DataBinder.Eval(dataItem, "TheBoundedProperty");

// or rather, if you know the dataItem's type (called 'MyDataType' here):
MyDataType dataItem = (MyDataType)DataBinder.GetDataItem(btn.NamingContainer);
btn.Text = dataItem.TheBoundedProperty;
}

Anonymous said...

Check out the following link, I used it to do an Eval from code-behind... basically you add a handler for databinding and use the DataBinder.Eval() function to pull it off.


http://forums.asp.net/t/1298261.aspx

Good luck!