I DataSource di ASP.NET 2.0 sono una nuova famiglia di controlli Web in grado di permettere agli sviluppatori di utilizzare una nuova tecnica di binding dei dati, il databinding dichiarativo.
Tramite questa funzionalità possiamo infatti legare una fonte di dati ad un controllo di presentazione (per esempio una GridView o una DataGrid) direttamente nella pagina Web, senza quindi dover scrivere il codice di databind nel file di code-behind.
Un DataSource control, in parole povere, non è altro che un archivio di informazioni che possono essere lette, modificate o cancellate attraverso l'esecuzione di metodi pre-confezionati.
Le informazioni esposte, possono essere prelevate da una qualsiasi fonte di dati, come può essere un database SQL Server o MS Access, la site map del proprio sito Web, file XML o collezioni di oggetti di business personalizzate.
I controlli DataSource già presenti all'interno del Framework sono:
- SqlDataSource - Che permette di prelevare informazioni da un qualsiasi database via ADO.NET (quindi SQL, OLEDB, ODBC o Oracle).
- AccessDataSource - Che permette di prelevare informazioni da un database MS Access.
- ObjectDataSource - Che permette di prelevare informazioni da proprie collezioni di oggetti di business.
- XmlDataSource - Che permette di prelevare informazioni da file XML.
- SiteMapDataSource - Che permette di prelevare informazioni da file .sitemap.
Ogni controllo di questo tipo eredita dalla classe DataSourceControl
. Proprio per questo può essere associato ad altri controlli per effettuare il DataBind dei dati attraverso la proprietà DataSourceID
; tale proprietà è stata aggiunta in tutti i controlli di presentazione come Repeater
, DataGrid
, DataList
e GridView
. Ecco un esempio d'uso del controllo AccessDataSource
:
Listato 1. Esempio d'uso di un DataSource
<asp:AccessDataSource ID="AccessDataSource1" runat="server"
DataFile="~/App_Data/database.mdb"
SelectCommand="SELECT * FROM [Tabella]" />
<asp:GridView ID="GridView1" DataSourceID="AccessDataSource1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID" >
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="Titolo" HeaderText="Titolo" SortExpression="Titolo" />
</Columns>
</asp:GridView>
In questo caso, il controllo di tipo AccessDataSource
rappresenta una connessione al database MS Access "database.mdb", posto nella directory "App_Data"; tale connessione fornisce poi i dati risultanti dal comando di selezione (definito dalla proprietà SelectCommand) alla GridView
sottostante.
Il legame tra il controllo di presentazione e la fonte di dati, avviene attraverso la valorizzazione della proprietà DataSourceID
, mentre il binding dei vari valori viene effettuato tramite la definizione di tante colonne (BoundField
) quanti sono i campi ritornati dalla query di selezione.
Per l'utilizzo del controllo ObjectDataSource
, rimandiamo alla lettura dell'articolo sui DataSet tipizzati mentre per l'utilizzo del controllo SiteMapDataSource
, vedere l'articolo sulla gestione della navigazione di un sito ASP.NET.
Creare un controllo DataSource personalizzato
I controlli DataSource che offre il .NET Framework risultano utilissimi per la connessione a fonti di dati tramite ADO.NET, quindi a basi di dati, sitemap, file XML e oggetti di business.
Sappiamo benissimo però che le operazioni di binding sopra descritte non bastano a soddisfare le innumerevoli esigenze che si hanno nella costruzione di un sito Web.
Proprio per questo, abbiamo la possibilità di estendere questo sistema di databind, creando dei controlli DataSource
personalizzati che prelevino le informazioni da fonti di dati alternative, come per esempio dei Web service, altre tipologie di database o strutture dati custom.
Per creare questo tipo di controlli, dobbiamo ereditare dalla classe base cui dipendono tutti i DataSource presenti nel Framework, la classe DataSourceControl (presente nel namespace System.Web.UI
).
Nell'esempio creiamo un DataSource control per la lettura di feed Rss e Atom. L'esempio è abbastanza semplice, ma spiega precisamente i passi di base necessari per l'implementazione di DataSource control personalizzati.
Un DataSource control in realtà non fa tanto, in quanto tutta la logica relativa alla lettura o alla modifica delle informazioni è lasciata ad un oggetto derivato dalla classe DataSourceView
. Questa classe deve obbligatoriamente implementare i meccanismi di SELECT
dalla fonte di dati, e possiamo decidere se implementare o meno quelli di UPDATE
, INSERT
e DELETE
.
Nota: nel nostro caso, implementiamo solamente il comando di selezione delle informazioni da un feed, in quanto non sono previsti metodi di modifica dei contenuti di tali oggetti.
La classe che eredita da DataSourceControl
avrà invece la definizione di tutte le proprietà utili a rendere il controllo il più dinamico possibile.
È importante ricordare di creare le classi nella directory App_Code, in modo tale da poterle utilizzare all'interno del nostro progetto Visual Studio (oppure possiamo sempre decidere di inserirle in assembly separati).
Vediamo ora il codice delle due classi principali usate per questo esempio (l'implementazione di alcune classi o metodi ridondanti sarà omessa, ma comunque presente nell'applicazione d'esempio da scaricare).
La prima delle due rappresenta un controllo (FeedDataSource
) che eredita da DataSourceControl
e che contiene solamente la definizione delle proprietà per la personalizzazione del comportamenti e, fondamentale, l'implementazione del metodo GetView(). Questo metodo è importante perché ritorna un'istanza della classe FeedDataSourceView
, classe che eredita da DataSourceView
e che incapsula la logica di gestione delle informazioni prelevate dalla fonte di dati. Ne proponiamo uno stralcio.
Nota: le porzioni di codice omesse saranno presenti nell codice dell'esempio da scaricare
Listato 2. La classe FeedDataSource (DataSourceControl)
using Peppe.BOL;
namespace Peppe.Web.UI.WebControls
{
public class FeedDataSource : DataSourceControl
{
private FeedDataSourceView view;
public static readonly string ViewName = "View";
// rileva o imposta il tipo di feed
public FeedType FeedType { //implementazione omessa }
// ritorna o imposta l'indirizzo
public string FeedUrl { //implementazione omessa }
// rileva o imposta la View
private FeedDataSourceView View
{
get {
if (view == null) view = new FeedDataSourceView(this, ViewName);
return view;
}
set { view = value; }
}
// costruttore vuoto
public FeedDataSource(){}
// il tanto atteso override
protected override DataSourceView GetView(string viewName)
{
if (String.IsNullOrEmpty(viewName) || (String.Compare(viewName, ViewName, StringComparison.OrdinalIgnoreCase) == 0))
{
return View;
}
throw new ArgumentOutOfRangeException("viewName");
}
// Ritorna il nome che vedremo ...
protected override System.Collections.ICollection GetViewNames()
{
return new string[] { ViewName };
}
}
}
Il metodo principale della classe FeedDataSourceView
, invece, è il metodo ExecuteSelect()
, che appunto, esegue la SELECT
sulle informazioni a disposizione. Questo viene poi chiamato in automatico una volta che viene effettuato il DataBind da un secondo Web control.
Questo metodo vuole come tipo di ritorno una collezione di oggetti che implementi l'interfaccia IEnumerable, per questo ho inserito nel progetto d'esempio le classi RssItemCollection
e AtomCollection
che rappresentano rispettivamente collezioni di oggetti di tipo RssItem e di tipo Atom (oggetti anche questi custom, utili a salvare le informazioni lette dai file xml).
Listato 3. La classe FeedDataSourceView (DataSourceView)
using Peppe.BOL;
namespace Peppe.Web.UI.WebControls
{
public class FeedDataSourceView : DataSourceView
{
private FeedDataSource control = null;
public FeedDataSourceView(FeedDataSource control, string viewName)
: base(control, viewName)
{
this.control = control;
}
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
{
RssItemCollection rssColl = null;
AtomCollection atomColl = null;
switch (this.control.FeedType)
{
case FeedType.RSS:
rssColl = GetRss(control.FeedUrl);
return rssColl;
case FeedType.ATOM:
atomColl = GetAtom(control.FeedUrl);
return atomColl;
default:
return null;
}
}
internal RssItemCollection GetRss(string url) { // implementazione omessa }
internal AtomCollection GetAtom(string url) { //implementazione omessa }
internal void RaiseChangedEvent()
{
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
Ora, siamo gia in grado di utilizzare il nostro DataSource personalizzato come fonte di dati per qualche controllo di presentazione.
Prima però, dobbiamo registrare il namespace contenente il controllo, all'interno del Web.config; così facendo, possiamo utilizzarlo in tutte le pagine della nostra applicazione ASP.NET e avvalerci del supporto per l'intellisense fornito da Visual Studio.
Listato 4. Registrare il controllo
<system.Web>
<compilation debug="false" />
<pages>
<controls>
<add tagPrefix="pep" namespace="Peppe.Web.UI.WebControls" assembly="__code"/>
</controls>
</pages>
</system.Web>
L'utilizzo, abbinato ad un controllo di presentazione, è esattamente lo stesso dei DataSource
offerti dal Framework, con l'unica differenza che nel nostro caso dobbiamo settare le proprietà di personalizzazione che abbiamo descritto sopra (FeedUrl
e FeedType
).
Listato 5. Impostare il controllo nella Web Form
<pep:FeedDataSource
FeedUrl="http://www.peppedotnet.it/Blog/Rss.aspx"
FeedType="RSS"
ID="myFeedDataSource"
runat="server" />
<asp:Repeater ID="rep" runat="server" DataSourceID="myFeedDataSource">
<ItemTemplate>
<a href='<%# Eval("Url") %>' title='<%# Eval("Title") %>'><%# Eval("Title") %></a><br />
</ItemTemplate>
</asp:Repeater>
In questo caso, i campi su cui effettuare le operazioni di databind sono rappresentati dalle proprietà della classe RssItem
, vista proprio come singolo item della collezione di informazioni prelevati dal nostro DataSource personalizzato.
Utilizzo di parametri
Tra le funzionalità più utili proprie dei controlli DataSource c'è l'utilizzo dei parametri. Possiamo filtrare la fonte di dati scelta tramite l'utilizzo di speciali controlli che prelevano le informazioni chiave dall'esterno del DataSource; per esempio, possiamo filtrare una query SQL in base al valore di una proprietà di un altro controllo Web presente all'interno della stessa pagina.
Riprendiamo l'esempio del controllo AccessDataSource
, aggiungendo un parametro.
Listato 6. AccessDataSource con parametri
<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
<asp:ListItem>1</asp:ListItem>
<asp:ListItem>2</asp:ListItem>
</asp:DropDownList>
<asp:AccessDataSource ID="AccessDataSource1" runat="server"
DataFile="~/App_Data/database.mdb"
SelectCommand="SELECT * FROM [Tabella] WHERE ([ID] = ?)">
<SelectParameters>
<asp:ControlParameter
ControlID="DropDownList1" Name="ID"
PropertyName="SelectedValue"
Type="Int32" />
</SelectParameters>
</asp:AccessDataSource>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" DataSourceID="AccessDataSource1">
<Columns>
...
</Columns>
</asp:GridView>
In questo caso, abbiamo utilizzato come parametro il controllo ControlParameter
, in grado appunto di prelevare informazioni da proprietà di altri controlli Web (nell'esempio, da una DropDownList
), in modo tale da filtrare la nostra fonte di dati.
Altri tipi di parametri offerti dal Framework sono:
ControlParameter
- preleva informazioni da una proprietà di un altro controllo presente all'interno della pagina.CookieParameter
- preleva informazioni da un cookie.FormParameter
- preleva informazioni da un campo form di una richiesta HTTP.Parameter
- classe base di tutti i parametri.ProfileParameter
- preleva informazioni da una delle proprietà del sistema dei profili.QueryStringParameter
- preleva informazioni da una querystring.SessionParameter
- preleva informazioni dalla sessione, tramite una chiave.
Detto questo possiamo aggiungere il supporto dei parametri al nostro DataSource personalizzato.
Anzitutto dobbiamo aggiungere una proprietà che rappresenti la collezione di parametri propria del controllo. Lo facciamo dichiarando una proprietà di classe ParameterCollection
.
Dobbiamo dichiarare che questa è la proprietà principale del nostro controllo e che quest'ultimo può supportare elementi figli. Qui entrano gioco le direttive DefaultProperty
e ParseChildren
Infine, dobbiamo implementare l'override di tutti quei metodi per la gestione del ViewState, in modo tale da poter salvare i valori selezionati dai parametri e in seguito recuperarli.
Listato 7. Classe FeedDataSource con parametri
namespace Peppe.Web.UI.WebControls
{
[DefaultProperty("Parameters"), ParseChildren(true), PersistChildren(false)]
public class FeedDataSource : DataSourceControl
{
private FeedDataSourceView view;
public static readonly string ViewName = "View";
public static readonly string FeedUrlParameterName = "FeedUrl";
private ParameterCollection parameters;
// rileva o imposta il tipo di feed
public FeedType FeedType { //implementazione omessa }
// ritorna o imposta l'indirizzo
public string FeedUrl { //implementazione omessa }
// rileva o imposta la View
private FeedDataSourceView View { //implementazione omessa }
// Rileva i parametri
public ParameterCollection Parameters
{
get {
if (parameters == null)
{
parameters = new ParameterCollection();
parameters.ParametersChanged += new EventHandler(this.OnParametersChanged);
if (IsTrackingViewState) ((IStateManager)parameters).TrackViewState();
}
return parameters;
}
}
public FeedDataSource() { }
protected override DataSourceView GetView(string viewName)
{
if (String.IsNullOrEmpty(viewName) || (String.Compare(viewName, ViewName, StringComparison.OrdinalIgnoreCase) == 0))
return View;
throw new ArgumentOutOfRangeException("viewName");
}
protected override System.Collections.ICollection GetViewNames()
{
return new string[] { ViewName };
}
public string GetFeedUrl()
{
if (parameters != null)
{
Parameter feedUrlParam = parameters[FeedUrlParameterName];
if (feedUrlParam != null)
{
IOrderedDictionary parameterValues = parameters.GetValues(Context, this);
return (string)parameterValues[feedUrlParam.Name];
}
}
return FeedUrl;
}
// Override del carircamento del ViewState
protected override void LoadViewState(object state)
{
object baseState = null;
if (state != null)
{
Pair p = (Pair)state;
baseState = p.First;
if (p.Second != null) {
((IStateManager)Parameters).LoadViewState(p.Second);
}
}
base.LoadViewState(baseState);
}
// override dell'inizializzazione
protected override void OnInit(EventArgs e)
{
Page.LoadComplete += new EventHandler(this.OnPageLoadComplete);
}
private void OnPageLoadComplete(object sender, EventArgs e)
{
if (parameters != null)
parameters.UpdateValues(Context, this);
}
private void OnParametersChanged(object sender, EventArgs e)
{
View.RaiseChangedEvent();
}
// override del salvataggio del ViewState
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
object parameterState = null;
if (parameters != null)
parameterState = ((IStateManager)parameters).SaveViewState();
if ((baseState != null) || (parameterState != null))
return new Pair(baseState, parameterState);
return null;
}
protected override void TrackViewState()
{
base.TrackViewState();
if (parameters != null) ((IStateManager)parameters).TrackViewState();
}
}
}
L'unica modifica che va fatta alla vista (classe FeedDataSourceView
) è semplicemente quella di prelevare l'indirizzo del feed da visualizzare tramite la chiamata al metodo GetFeedUrl()
al posto che della proprietà FeedUrl
.
Se non saranno utilizzati dei parametri, il DataSource prenderà comunque il valore di tale proprietà per il parsing del feed scelto.
Listato 8. Modifiche a FeedDataSourceView
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
{
RssItemCollection rssColl = null;
AtomCollection atomColl = null;
switch (this.control.FeedType)
{
case FeedType.RSS:
rssColl = GetRss(control.GetFeedUrl());
return rssColl;
case FeedType.ATOM:
atomColl = GetAtom(control.GetFeedUrl());
return atomColl;
default:
return null;
}
}
Infine, inseriamo i parametri nel nostro DataSource personalizzato.
Listato 9. Utilizzare il parametro custom
<pep:FeedDataSource FeedType="RSS" ID="myFeedDataSource" runat="server">
<Parameters>
<asp:QueryStringParameter Name="FeedUrl" QueryStringField="feed" />
</Parameters>
</pep:FeedDataSource>
<asp:Repeater ID="rep" runat="server" DataSourceID="myFeedDataSource">
<ItemTemplate>
<a href='<%# Eval("Url") %>' title='<%# Eval("Title") %>'><%# Eval("Title") %></a><br />
</ItemTemplate>
</asp:Repeater>
Conclusioni
L'utilizzo dei controlli DataSource ha sicuramente semplificato la maggior parte delle operazioni di sviluppo di un sito Web ASP.NET, infatti attraverso l'utilizzo del binding dichiarativo, possiamo evitare la scrittura di tutte quelle righe di codice che servivano per la lettura delle informazioni dalla fonte di dati e per il databind.
Inoltre, grazie alla possibilità di estensione propria di tutto il .NET Framework 2.0, siamo in grado di scrivere i nostri controlli DataSource personalizzati per andare a pescare informazioni da strutture dati differenti da quelle offerte di default dal sistema e di filtrarle, attraverso l'implementazione dei parametri, in base a valori scelti a run-time.
È inoltre possibile implementare il supporto a design-time e per selezioni di informazioni in maniera asincrona.