Con l'avvento del Web 2.0 e del Web sociale i tag, letteralmente "etichette", sono diventati via via sempre più importanti per l'identificazione e l'organizzazione dei contenuti. Uno dei sistemi per visualizzare in modo ordinato una lista di tag consiste nella "tag cloud", una distribuzione a "nuvola" in cui le dimensioni di ogni elemento variano a seconda della quantità di contenuti catalogati.
In questo articolo vedremo come creare un controllo ASP.NET per visualizzare una tag cloud. Per rendere il controllo utilizzabile nel maggior numero di situazioni possibili verrà implementato anche il supporto ai template e a tutti i diversi tipi di fonte di dati (datasource), siano questi database o oggetti.
Per quanto riguarda il codice HTML ci atterremo ad un definizione semantica della nuvola attraverso l'utilizzo di una lista non ordinata e dei CSS, secondo il markup presentato in un articolo di Alessandro Fulciniti.
Cenni sulla struttura dei controlli
Prima di procedere con la realizzazione pratica, è bene fare un ripasso sull'architettura dei controlli in ASP.NET 2.0. Il Framework .Net ci mette a disposizione diverse classi (tutte reperibili nel namespace System.Web.UI.WebControls
) da cui ereditare per creare diversi tipi di controlli:
- Control
- WebControl
- DataBoundControl
- CompositeControl
- CompositeDataBoundControl
GridView
FormView
- HierarchicalDataBoundControl
Occupiamoci adesso delle funzioni principali di CompositeDataBoundControl
, e del loro ruolo all'interno del ciclo di vita di un controllo ASP.NET. Come già detto, CompositeDataBoundControl verrà utilizzato come base per il nostro controllo personalizzato. Molte delle considerazioni fornite di seguito possono tuttavia essere applicate anche alle altre classi.
Cominciamo dalla funzione CreateChildControl()
, una delle più importanti, dato che ha il compito di costruire la struttura interna del controllo: i template vengono inizializzati all'interno dei loro contenitori e tutti i controlli figli vengono creati (ma non renderizzati, più informazioni su questa differenza di seguito) ed aggiunti alla collection Controls. Viene inoltre effettuato il databind dei dati ad ogni elemento interno.
È bene distinguere subito tra la fase di creazione dei controlli e quella di rendering: al contrario di quanto comunemente si è portati a pensare queste due fasi sono distinte tra loro ed avvengono in due diversi momenti di vita del controllo.
Il rendering del controllo avviene quando la pagina ha completato l'inizializzazione ed esegue un ciclo sui controlli figlio, richiamando per ognuno il metodo Render()
, proprio della classe Control
.
Questo metodo ne richiama a sua volta tre distinti: RenderBeginTag
, RenderContents
e RenderEndTag
. Il primo e l'ultimo creano un contenitore per il controllo, di default un tag HTML span. Più interessante è il secondo che si occupa di generare il codice HTML per il contenuto del controllo. Nella costruzione del nostro controllo non avremo bisogno di sovrascrive questo metodo, lasciando fare tutto il lavoro ad ASP.NET.
TagCloud: struttura
Come già anticipato, il nostro controllo offrirà supporto ai template ed alla definizione dichiarativa dei tag, secondo lo schema riassunto in figura.

I template di cui avremo bisogno sono quindi tre: <HeaderTemplate>
per l'intestazione (utile ad esempio per un titolo), <FooterTemplate>
per il piede ed <EmptyDataTemplate>
per visualizzare un messaggio nel caso in cui datasource sia vuoto.
La collection Items
rappresenta invece la lista dei tag da visualizzare, che può essere riempita, come succede ad esempio con il controllo DropDownList
, in modo dichiarativo, da codice e attraverso un datasource.
Per i template e le proprietà ho scelto di utilizzare nomi in inglese per mantenere maggiore affinità con gli altri controlli di ASP.NET che offrono funzionalità simili.
Le proprietà che verranno implementate nel controllo sono le seguenti:
AppendDataBoundItems
DataTextField
DataTextFormatString
DataSizeField
TagCloud: template e proprietà
Iniziamo la scrittura del controllo creando una nuova classe TagCloud
(l'esempio allegato può servire come riferimento immediato e completo al codice che analizzeremo):
Decorazione della classe TagCloud
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal), ToolboxData("<{0}:TagCloud runat="server" />"), Description("Displays a tag cloud.")] public sealed class TagCloud : CompositeDataBoundControl { ... }
Soffermiamoci un attimo sugli attributi assegnati alla classe, in particolare su AspNetHostingPermission
che indica il livello di sicurezza richiesto dal codice del controllo per funzionare: assegnando come livello AspNetHostingPermissionLevel.Minimal
specifichiamo che il codice non fa uso di funzionalità che hanno bisogno di trust più elevato (come, ad esempio, la Reflection). In questo modo il controllo potrà essere utilizzato con qualsiasi livello di protezione.
I rimanenti attributi ToolboxData
e Description
rappresentano, rispettivamente, il codice che viene inserito nella pagina quando trasciniamo il controllo dalla barra degli strumenti di Visual Studio (o Visual Web Developer Express) e la descrizione del controllo.
Occupiamoci adesso dei template. Per implementare questa funzionalità avremo bisogno di definire un campo ed una proprietà di tipo ITemplate
per ciascun template che vogliamo utilizzare:
private ITemplate headerTemplate; [Browsable(false), PersistenceMode(PersistenceMode.InnerProperty), DefaultValue(typeof(ITemplate), ""), Description("Header Template"), TemplateContainer(typeof(TagCloudTemplateContainer))] public ITemplate HeaderTemplate { ... }
Anche in questo caso soffermiamoci sugli attributi:
Browsable
false
PersistenceMode
PersistenceMode.InnerProperty
DefaultValue
Description
- infine,
TemplateContainer
Un template non è un controllo (abbiamo usato l'interfaccia ITemplate
e non una classe derivata da Control
) e, come avremo modo di vedere parlando della funzione CreateChildControls
, i template hanno bisogno di essere instanziati all'interno di un altro controllo (nel nostro caso una classe creata ad-hoc, TagCloudTemplateContainer
) per poter essere visualizzati nella pagina.
La classe TagCloudTemplateContainer
è molto semplice:
public sealed class TagCloudTemplateContainer : Control, INamingContainer { ... }
Implementando l'interfaccia INamingContainer
ci assicuriamo che gli ID dei controlli contenuti all'interno del template non interferiscano con quelli di altri controlli della pagina, creando uno spazio a se stante.
Per quanto riguarda la lista dei tag, abbiamo bisogno anche in questo caso di un campo e di una proprietà:
private Collection<TagCloudItem> items = new Collection<TagCloudItem>(); [PersistenceMode(PersistenceMode.InnerProperty)] public Collection<TagCloudItem> Items { ... }
La lista degli elementi è dichiarata come una Collection
generica di oggetti TagCloudItem
, definiti come segue:
public sealed class TagCloudItem { private string text; private int size; public string Text { ... } public int Size { ... } }
Anche nel caso dei tag la proprietà non sarà visualizzata come attributo del server tag ma internamente. Infine, le altre proprietà sono memorizzate nel ViewState
del controllo e non necessitano quindi di campi privati:
public bool AppendDataBoundItems { get { object o = ViewState["AppendDataBoundItems"]; return (o != null) ? (bool)o : false; } set { ViewState["AppendDataBoundItems"] = value; } }
TagCloud: le funzioni
Eccoci finalmente giunti alla realizzazione della logica interna del controllo. Per cominciare effettuiamo l'override della funzione PerformDataBinding
, che si occupa di prelevare i dati dal datasource per inserirli nella collection Items
:
protected override void PerformDataBinding(IEnumerable dataSource) { if (dataSource != null) { if (!AppendDataBoundItems) this.Items.Clear(); // Per migliorare le prestazioni copiamo i valori dei campi // in modo da non dover scorrere continuamente la collection // del ViewState
Il codice è semplice: per prima cosa svuotiamo la lista degli elementi soltanto se AppendDataBoundItems
è uguale a false
; poi con un ciclo foreach
creiamo un oggetto TagCloudItem
per ogni elemento presente nel datasource, utilizzando i valori delle proprietà DataTextField
e DataSizeField
per estrarre i dati richiesti.
Passiamo adesso alla creazione dei controlli figlio. Come già accennato il cuore di questa operazione è la funzione CreateChildControls
, propria di CompositeDataBoundControl
e che andiamo quindi a sovrascrivere:
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding) { int num = 0; // Ci assicuriamo che la lista dei controlli sia vuota // Creiamo i nuovi controlli // La funzione CreateControlHierarchy serve soltanto per mantenere // il codice più leggibile // Assegnamo a ChildControlsCreated il valore true, // in modo da evitare che la lista dei controlli sia costruita di nuovo // Puliamo il viewstate dei controlli
Per mantenere il codice più pulito è preferibile spezzare il codice, inserendo la creazione dei controlli in un'altra funzione, CreateControlHierarchy
. Si può fare riferimento ai commenti all'interno del codice per maggiori informazioni sulle altre istruzioni.
Il codice di CreateControlHierarchy
è molto complesso e per motivi di spazio non sarà possibile riportarlo per intero (la versione completa è disponibile nell'allegato). Vediamone i passaggi principali:
private int CreateControlHierarchy(IEnumerable dataSource, bool dataBinding) { // Numero dei controlli creati, verranno conteggiati // solo i tag // Header // È presente un template per l'header // Creiamo il contenitore // Aggiungiamo il contenitore alla lista dei controlli // Inseriamo il contenuto del template nel contenitore // Contenuto // Se stiamo eseguendo il databind e se la lista // degli elementi non è vuota // Contenitore // Aggiungiamo gli elementi alla lista dei controlli // Testo // Dimensione del testo // Il datasource è vuoto e non ci sono elementi dichiarati // nel codice: uso l'EmptyDataTemplate se presente // Il procedimento è identico a quello utilizzato per l'header
Iniziamo con l'instanziare il template per l'intestazione nel caso in cui questo sia diverso da null (in altri parole, nel caso in cui il template sia stato specificato). Creiamo quindi un nuovo oggetto TagCloudTemplateContainer
, lo aggiungiamo alla lista dei controlli e tramite la funzione InstantiateIn
, instanziamo il template. Lo stesso sistema verrà utilizzato al termine della funzione anche per il footer.
A questo punto visualizziamo gli elementi nel caso in cui la collection Items
, riempita precedentemente da PerformDataBinding
, non sia vuota, visualizzando alternativamente il template EmptyDataTemplate
. Creiamo quindi la lista non ordinata (<ul>
) e per ogni elemento nella collection (foreach
) aggiungiamo un elemento <li>
.
Il controllo è pronto! Non resta che creare una pagina di prova registrando il namespace che contiene il controllo e collegando quest'ultimo ad un qualsiasi datasource.
<%@ Register TagPrefix="custom" Namespace="Controls" %> <custom:TagCloud ID="TagCloud1" runat="server" DataTextField="Name" DataSizeField="PostCount" CssClass="tag-cloud" DataSourceID="SqlDataSource1" AppendDataBoundItems="true"> <HeaderTemplate> <h1>Tags</h1> </HeaderTemplate> <EmptyDataTemplate> Nessun tag presente! </EmptyDataTemplate> <Items> <custom:TagCloudItem Text="Aggiunto da codice!" Size="6" /> </Items> </custom:TagCloud> <asp:SqlDataSource ID="SqlDataSource1" runat="server" SelectCommand="SELECT * FROM Tags" ConnectionString="<%$ ConnectionStrings: esempio %>" />
Per mostrare le potenzialità del controllo appena realizzato, nell'esempio allegato all'articolo è compreso un piccolo database e una pagina di esempio con un SqlDataSource
ed un ObjectDataSource
, insieme alle classi necessarie al funzionamento di quest'ultimo.

Conclusioni
Dopo una rapida introduzione alla struttura dei controlli di ASP.NET siamo passati a realizzare un controllo personalizzato basato su CompositeDataBoundControl
, implementando il supporto ai template e a un datasource generico. Per semplicità alcune funzionalità (ad esempio i link sui tag visualizzato) non sono state incluse nel controllo finale.