Server control templates
Data: 7/29/2006
Categoria: ASP.NET 2.0
Codice d'esempio


In ASP.NET esistono dei controlli creati appositamente per il binding dei dati, controlli come il DataGrid, il DataList, la nuova GridView o il Repeater, che permettono di rappresentare dei dati prelevati da una qualsiasi fonte di dati.
Ognuno di questi controlli, ad ogni riga della fonte di dati, associa un nuovo item. Per decidere come questo item vada rappresentato, viene utilizzato il concetto di Template.
Un template non ha interfaccia grafica propria, ma è stato creato per svolgere la funzione di contenitore di altri controlli web. Solitamente questi vengono definiti nella parte di markup di una pagina ASP.NET, o di uno user control, in questo modo:

<asp:Repeater id="rep" runat="server">

<ItemTemplate>
<asp:Label id="lbl" runat="server">
<%# DataBinder.Eval(Container.DataItem, "Title") %>
</asp:Label>
<br />
</ItemTemplate>
</asp:Repeater>

Il problema sorge nel momento in cui si voglia definire il contenuto di questi template via codice, magari cercando di inserire un repeater all'interno di un proprio web server control.
Per far ciò, il .NET Framework mette a disposizione l'interfaccia ITemplate. Tale interfaccia espone il metodo InstantiateIn, che viene chiamato ad ogni ciclo sulla fonte di dati.
Vediamo un semplice esempio su come creare un custom template e come associarlo ad un repeater:

SimpleTemplate.cs
public class SimpleTemplate : ITemplate

{
private int itemnumber = 0;

public SimpleTemplate()
{}

public void InstantiateIn(Control container)
{
Literal l = new Literal();
l.Text = String.Format("Item #{0}", itemnumber);
itemnumber++;
container.Controls.Add(l);
container.Controls.Add(new LiteralControl("<hr />"));
}
}

Default.aspx
<asp:SiteMapDataSource ID="siteMap" runat="server" ShowStartingNode="false" />

<asp:Repeater ID="rep1" runat="server" DataSourceID="siteMap">
</asp:Repeater>

Default.aspx.cs
rep1.ItemTemplate = new SimpleTemplate();

rep1.DataBind();

In questo caso è stato instanziato un nuovo template personalizzato per la proprietà ItemTemplate, che rappresenta un singolo item della collezione. E' inoltre possibile scrivere un template che gestisca tutti i possibili tipi di template definiti dai controlli databound (nel nostro caso, dal controllo Repeater).
Una possibilità era anche quella di scrivere una classe per ogni tipo di template, ma questo è sicuramente un tipo di scelta propria per template decisamente più complicati di quanto andremo ora a vedere.
La multigestione dei vari template in un'unica classe è applicabile passando ad ogni nuova istanza del template, il tipo di template che quella specifica istanza dovrà rappresentare. Il tipo di template è dato dall’enumaration ListItemType.

public class AdvancedTemplate : ITemplate

{
private ListItemType type;

public AdvancedTemplate(ListItemType type)
{
this.type = type;
}

public void InstantiateIn(Control container)
{
switch (this.type)
{
case ListItemType.AlternatingItem:
Literal l = new Literal();
l.Text = "<div style='background-color: yellow'>";
l.Text += "Alternating Item";
l.Text += "</div>";
container.Controls.Add(l);
break;
case ListItemType.Footer:
LinkButton lnk = new LinkButton();
lnk.Text = "http://www.peppedotnet.it";
lnk.OnClientClick = "return confirm('Vuoi aprire il sito di Marchi Giuseppe ?');";
lnk.Click += new EventHandler(lnk_Click);
container.Controls.Add(new LiteralControl("<hr />"));
container.Controls.Add(lnk);
break;
case ListItemType.Header:
l = new Literal();
l.Text = "<h2>Header</h2>";
container.Controls.Add(l);
break;
case ListItemType.Item:
l = new Literal();
l.Text = "Item";
container.Controls.Add(l);
break;
case ListItemType.Separator:
container.Controls.Add(new LiteralControl("<hr />"));
break;
default:
break;
}
}

void lnk_Click(object sender, EventArgs e)
{
HttpContext.Current.Response.Redirect("http://www.peppedotnet.it");
}
}

Come gia detto, va creata una nuova istanza del template per ogni proprietà del Repeater cui si voglia associare:

rep2.ItemTemplate = new AdvancedTemplate(ListItemType.Item);

rep2.AlternatingItemTemplate = new AdvancedTemplate(ListItemType.AlternatingItem);
rep2.SeparatorTemplate = new AdvancedTemplate(ListItemType.Separator);
rep2.HeaderTemplate = new AdvancedTemplate(ListItemType.Header);
rep2.FooterTemplate = new AdvancedTemplate(ListItemType.Footer);
rep2.DataBind();


Cosa manca ancora ?
Beh, direi la cosa più importante... il DataBinding !
Fin’ora infatti, i due template visti negli esempi, sono stati creati per visualizzare dati statici, lasciando perdere i dati veri e propri forniti dal datasource.
Esistono vari modi per effettuare il DataBinding in questo tipo di situazioni; quello che vedremo in questo esempio, consiste nel catturare l'evento DataBinding di ogni controllo che viene aggiunto al Repeater ed "attaccare" le informazioni prese dalla fonte di dati alle proprietà fornite dal controllo aggiunto.

public class DatabindingTemplate : ITemplate, INamingContainer

{
public DatabindingTemplate()
{}

public void InstantiateIn(Control container)
{
Literal l = new Literal();
l.DataBinding += new EventHandler(l_DataBinding);
container.Controls.Add(l);
container.Controls.Add(new LiteralControl("<br />"));
}

void l_DataBinding(object sender, EventArgs e)
{
Literal l = (Literal)sender;
if (l != null)
{
RepeaterItem container = (RepeaterItem)l.NamingContainer;
string title = String.Format("{0}", DataBinder.Eval(container.DataItem, "Title"));
string description = String.Format("{0}", DataBinder.Eval(container.DataItem, "Description"));
string url = String.Format("{0}", DataBinder.Eval(container.DataItem, "Url"));
l.Text = String.Format("<a href='{0}' title='{1}'>{2}</a>", url, description, title);
}
}
}


Con questa serie di 3 esempi, abbiamo visto le basi della creazione di custom template per il databound di server controls. Queste tecniche possono risultare molto utili nella creazione di web control personalizzati, dove non si ha la possibilità di inserire il codice per il databinding all’interno del markup di una pagina ASP.NET o di uno user control.