I controlli Data-Bound sono preziosi elementi della grande famiglia dei controlli lato server di ASP.NET, sin dalla prima versione del framework. A differenza degli altri controlli, offrono una funzionalità a dir poco fondamentale: il collegamento ad una fonte di dati, quindi la gestione del legame tra lo strato contenente le informazioni e lo strato di presentazione del sito.
Attraverso questi controlli infatti, siamo in grado di rendere dinamica la nostra applicazione, caratteristica ormai fondamentale, seguendo una logica affine alla programmazione ad oggetti (Modello MVC).
È abbastanza comune l'uso di controlli come Repeater, DataList, DataGrid o GridView; questi sono tutti Data-Bound Controls, controlli che permettono di rappresentare dati prelevati da una qualsiasi fonte (DB, XML, etc.) con funzioni e modelli predefiniti o personalizzati, grazie a template di visualizzazione.
Possiamo classificare i controlli Data-Bound in 3 tipi:
- Controlli di tipo lista - Sono quei controlli che presentano i dati in una struttura grafica predefinita, con cui l'utente finale non ha alcuna possibilità di interazione (e quindi di modifica visiva). Per capirci meglio, sono dei controlli di tipo lista i controlli
RadioButtonList
,CheckBoxList
eBulletedList
. - Controlli data-bound semplici - Sono tutti quei controlli che danno la possibilità allo sviluppatore di effettuare il databind su una o più delle loro proprietà. Il semplice controllo
Label
, ad esempio, permette di effettuare il binding di dati sulla sua proprietàText
. Allo stesso modo si comportano, tutti i controlli con delle proprietà decorate con l'attributoBindable
impostato sutrue
. - Controlli data-bound complessi - Sono quei controlli che offrono funzionalità di rendering dei dati avanzate, senza alcun limite sul numero dei dati prelevati o sul relativo schema. Questi sono dei controlli compositi con aggiunte le funzionalità di binding dei dati. I più famosi sono
GridView
,DetailsView
,FormView
(i 3 nuovi controlli aggiunti con la versione 2.0 di ASP.NET),DataGrid
,DataList
eRepeater
.
L'utilizzo così diffuso di Data-Bound Controls nasce da concrete necessità di controlli avanzati per semplificare il lavoro del team di sviluppo.
La possibilità di estensione, propria del framework, si applica anche a questa famiglia di controlli, perciò abbiamo la possibilità di sviluppare controlli custom per soddisfare le più svariate esigenze.
Prima di passare alla parte pratica, esaminiamo in dettaglio come funziona il meccanismo di binding dei dati in ASP.NET 2.
Anzitutto bisogna specificare che esistono due tipi di data-bind, il primo viene effettuato attraverso l'utilizzo della proprietà DataSource
:
GridView1.DataSource = dataset;
GridView1.DataBind();
tale meccanismo, controlla che la fonte di dati implementi l'interfaccia IEnumerable
(propria delle collezione di oggetti) o IListSource
(che sarebbe l'interfaccia implementata dalle classi DataSet
e DataTable
) e necessita della chiamata manuale al metodo DataBind()
del controllo.
Il secondo modo è invece quello di valorizzare la proprietà DataSourceID
con l'ID di un controllo di tipo DataSource
(per una panoramica su tali controlli, rimandiamo a questo articolo).
<asp:GridView ID="GridView1" DataSourceID="ds" runat="server">
</asp:GridView>
In questo modo, è il template engine di ASP.NET che si preoccupa di collegare la fonte di dati prelevata dal controllo DataSource
all'omonima proprietà del controllo e di chiamare il metodo DataBind()
, il tutto senza scrivere alcuna riga di codice all'interno del file di code-behind.
Se scegliamo uno dei due metodi appena descritti, siamo impossibilitati ad utilizzare l'altro, poiché le proprietà DataSource
e DataSourceID
non possono convivere assieme allo stesso tempo.
Creare un controllo Data Bound
Le proprietà DataSource
e DataSourceID
, sono definite all'interno della classe BaseDataBoundControl
, che si occupa di ridefinire il metodo DataBind()
ereditato dalla classe Control
e di controllare che il binding dei dati venga effettuato correttamente anche a fronte di eventuali PostBack della pagina.
Se decidessimo però di ereditare da questa classe per lo sviluppo del nostro controllo Data-Bound, ci dovremmo mettere a carico lo sviluppo dei meccanismi di validazione della fonte di dati (ridefinendo il metodo ValidateDataSource
) e di esecuzione dell'operazione di selezione delle informazioni (ridefinendo il metodo PerformSelect
).
Come al solito, ASP.NET ci viene in aiuto con la definizione di una seconda classe posta ad un livello logico più alto, la classe DataBoundControl, che ha già implementati i metodi appena descritti. Ereditando da tale classe infatti, è solamente necessario:
- definire le proprietà di mappatura dei campi da selezionare,
- implementare il metodo PerformDataBinding(), per elaborare i dati restituiti dall'operazione di selezione,
- implementare il metodo Render(), per fornire la presentazione scelta dei dati.
Nell'esempio che proponiamo, realizziamo un controllo Data-Bound in grado di visualizzare un grafico a barre orizzontali; come al solito l'esempio deve essere visto come un punto di partenza per sviluppi futuri.
Per prima cosa dobbiamo ereditare dalla classe DataBoundControl
:
public class ChartControl : DataBoundControl
Tipicamente possiamo pensare al datasource in ingresso come a una tabella (il risultato di una query) con diversi campi. Avremo bisogno quindi, di sapere quale dei campi è il campo numerico da rappresentare con il grafico. Inoltre possiamo prevedere che a ciascun numero sia associata un'etichetta. Lasciamo che l'utente del controllo imposti questi due parametri fornendogli due proprietà: DataTextField
e DataValueField
.
public BarCollection Bars
{
get {
if (bars == null) bars = new BarCollection();
return bars;
}
}
public string DataTextField
{
get {
object o = ViewState["DataTextField"];
return (o == null) ? String.Empty : (string)o;
}
set { ViewState["DataTextField"] = value; }
}
public string DataValueField
{
get {
object o = ViewState["DataValueField"];
return (o == null) ? String.Empty : (string)o;
}
set { ViewState["DataValueField"] = value; }
}
La proprietà Bars
ci permette, secondo una struttura ben definita (data dalle classi di supporto Bar
e BarCollection
, di cui abbiamo omesso l'implementazione, presente nell'esempio da scaricare), di salvare la collezione di righe restituite dall'operazione di selezione delle informazioni.
Dobbiamo ora implementare il metodo PerformDataBinding
e popolare le strutture dati appena definite leggendo i vari campi dal DataSource legato.
protected override void PerformDataBinding(System.Collections.IEnumerable data)
{
if (DataTextField == null)
throw new ArgumentNullException("DataTextField", "La proprietà DataTextField è necessaria");
if (DataValueField == null)
throw new ArgumentException("DataValueField", "La proprietà DataValueField è necessaria");
if (data != null)
{
foreach (object item in data)
{
Bar b = new Bar();
b.Text = DataBinder.GetPropertyValue(item, DataTextField, null);
int result = 0;
Int32.TryParse(DataBinder.GetPropertyValue(item, DataValueField, null), out result);
b.Value = result;
Bars.Add(b);
}
}
}
Per prelevare il singolo campo da ogni singola riga abbiamo utilizzato il metodo GetPropertyValue
della classe DataBinder
e gli abbiamo passato la singola riga (item
) e il nome del campo scelto (prelevato dalle proprietà DataTextField
e DataValueField
).
Fatto questo, abbiamo popolato le nostre strutture dati (la collezione Bars) e siamo pronti per presentarle a video. La fase di rappresentazione delle informazioni è chiaramente legata alla definizione del metodo Render
del controllo. Tale operazione risulta possibile in quanto, nel ciclo di vita di un controllo Data-Bound, il metodo PerformDataBinding
viene richiamato sempre prima del metodo Render
.
All'interno del metodo per la rappresentazione, scorriamo la collezione e che abbiamo a disposizione e avvalendoci del markup HTML, realizziamo la grafica prescelta.
protected override void Render(HtmlTextWriter writer)
{
if (Bars.Count <= 0) return;
writer.RenderBeginTag(HtmlTextWriterTag.Table); // apro table
foreach (Bar b in Bars)
{
// calcolo il valore in scala
int valoreInScala = ((int)Width.Value * b.Value) / 100;
writer.RenderBeginTag(HtmlTextWriterTag.Tr); // apro tr
writer.RenderBeginTag(HtmlTextWriterTag.Td); // apro td barra
writer.AddAttribute(HtmlTextWriterAttribute.Style,
String.Format("width: {0}px; background-color: {1}; " +
"float: left; margin-right: 5px",
valoreInScala,
ColorTranslator.ToHtml(BarColor)), false);
writer.RenderBeginTag(HtmlTextWriterTag.Div); //apro div barra
writer.RenderEndTag(); //chiudo div
writer.RenderBeginTag(HtmlTextWriterTag.Div); //apro div testo
writer.Write(b.Text);
writer.RenderEndTag(); //chiudo div
writer.RenderEndTag(); //chiudo td
writer.RenderEndTag(); //chiudo tr
}
writer.RenderEndTag(); //chiudo table
}
A questo punto il nostro controllo Data-Bound è pronto per essere utilizzato a pieno regime all'interno di un'applicazione ASP.NET. Come sempre possiamo decidere se salvare la classe all'interno della directory App_Code
dell'applicazione o se inserirla all'interno di un'assembly .NET specifico, in ogni caso dobbiamo ricordarci di registrare il controllo: all'interno del web.config o direttamente nella pagina che lo contiene.
Possiamo collegare il nostro controllo Data-Bound ad un controllo di tipo DataSource:
<peppe:ChartControl ID="ChartControl1" runat="server"
DataSourceID="AccessDataSource1"
DataTextField="Squadra" DataValueField="g22" />
<asp:AccessDataSource ID="AccessDataSource1" runat="server"
DataFile="~/App_Data/classifica.mdb"
SelectCommand="SELECT * FROM [Classifica]" />
Oppure, possiamo scrivere il codice per la connessione alla fonte di dati a mano ed attaccarla al nostro grafico a barre attraverso la proprietà DataSource
e la chiamata al metodo DataBind()
.
<peppe:ChartControl ID="ChartControl2" runat="server"
DataValueField="g22" DataTextField="Squadra"/>
protected void Page_Load(object sender, EventArgs e)
{
string strConn = @"...";
string sql = @"SELECT * FROM [Classifica]";
OleDbConnection conn = new OleDbConnection(strConn);
using (conn)
{
OleDbCommand cmd = new OleDbCommand(sql, conn);
using (cmd)
{
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
DataTable dt = new DataTable();
adapter.Fill(dt);
ChartControl2.DataSource = dt;
ChartControl2.DataBind();
}
}
}
In entrambi i casi, il risultato è il medesimo e, nonostante la semplicità, l'impatto visivo è già gradevole.
Conclusioni
Il data-binding è fondamentale nello sviluppo di applicazioni Web con ASP.NET 2.0: avere a disposizione oggetti predefiniti in grado di rappresentare informazioni da database, XML o oggetti di business è fondamentale. Con i Data-Bound controls, siamo in grado di progettare oggetti riusabili ed adattabili alle esigenze grafiche dell'applicazione indipendentemente da quale sia lo schema delle informazioni che abbiamo intenzione di rappresentare.