Tutorial ASP.NET 2.0 - Parte terza: accesso ai dati
a cura di Antonio Catucci e Mario De Ghetto (requisiti: conoscenza di ADO.Net e SQL)

ASP.NET, sin dalla versione 1.0, ha messo a disposizione una serie di controlli per la visualizzazione e modifica dei dati provenienti da un database. Tutti questi controlli sono basati sul meccanismo del Data Binding, cioè sulla capacità di consumare dati semplicemente impostandone la proprietà DataSource. Purtroppo, però, esso si limita esclusivamente alla visualizzazione dei dati e non anche alla modifica.
Il controllo DataGrid di ASP.NET 1.x, ad esempio, mette a disposizione una serie di funzionalità che consentono di modificare i dati nel controllo stesso, ma la persistenza sul database deve essere implementata manualmente dal programmmatore negli eventi XxxxCommand, richiamando gli oggetti opportuni per interagire con il database. Se poi consideriamo l'inserimento, la modifica e la cancellazione, si fa in fretta a scrivere un bel po' di codice per di più simile per tutte le tabelle che vogliamo gestire!

Con ASP.NET 2.0 le cose sono decisamente migliorate grazie all'introduzione di nuovi controlli in grado di gestire un data binding "bidirezionale", ovvero sia dal database verso i controlli (visualizzazione dei dati) che viceversa (dai controlli al database). La novità è rappresentata dai controlli DataSource.

Accedere ai dati con il DataSourceControl
Prima di mostrare come sfruttare questi nuovi controlli spieghiamo brevemente cosa sono e come funzionano.
Si tratta fondamentalmente di controlli che fungono da "proxy" tra i controlli che consumano dati e una sorgente che può essere un database, un web service o un oggetto business e forniscono una interfaccia comune per la selezione, la modifica e l'ordinamento dei dati. La classe base di questi controlli è DataControlSource e ASP.NET mette a disposizione alcuni controlli già pronti per collegarsi a database come SQL Server (SqlDataSource) e Access (AccessDataSource) oppure ad un oggetto business custom (ObjectDataSource).

Nel corso di questo articolo e di quelli successivi useremo l'ObjectDataSource, perché è il più flessibile e astratto rispetto a quelli ottimizzati per uno specifico database. Infatti questi ultimi hanno il grosso svantaggio di inserire codice SQL direttamente all'interno delle pagine rendendone difficile la manutenzione e annullando il disaccoppiamento tra l'interfaccia grafica e le classi di accesso ai dati. Con l'ObjectDataSource, invece, abbiamo a disposizione una interfaccia generica che ci consente di utilizzare delle classi custom realizzate ad hoc senza dipendere dal database sottostante.

Le classi di accesso ai dati
Per gli esempi useremo un approccio a livelli, cioè ci sarà un livello di accesso dati chiamato Data Access Layer (DAL) che interagisce direttamente con il database ed un livello di business chiamato Business Logic Layer (BLL) per la rappresentazione logica dei dati. Naturalmente il BLL userà il DAL per accedere ai dati.
Per motivi pratici, l'esempio non segue un pattern rigoroso proprio perché è a scopo dimostrativo e perché questo non è un articolo sui design pattern. Rappresenta, però, uno spunto per sviluppare le proprie classi di accesso ai dati.

Il database scelto è Northwind nella versione per SQL server 2005 (che trovate nell'esempio allegato), utilizzato sfruttando una delle novità di SQL Server 2005, ovvero facendo l'attach direttamente al file .mdf un po' come avviene con Microsoft Access.
Il primo passo, dunque, è aggiungere il database al progetto nella cartella speciale App_Data usata proprio per questo scopo. Se questa cartella non è presente, selezionate il file di progetto nel Solution Explorer e dal menu Website> Add ASP.NET Folder selezionate App_Data. A questo punto è possibile aggiungere il file di database dal menu Add Existing Item...

Il Data Access Layer
Il Data Access Layer (DAL) e il Business Logic Layer (BLL) sono stati creati nella cartella App_Code. In questo articolo non vedremo nel dettaglio come realizzare queste strutture, preferendo illustrare solo alcune parti di esso man mano che se ne renderà necessario, per rendere più chiari i concetti esposti. In ogni caso trovate il codice completo nell'esempio allegato.

Per la costruzione del DAL abbiamo optato per i DataSet tipizzati, in quanto veloci e pratici da usare, anche se in applicazioni più complesse è preferibile realizzarlo mediante classi custom o usando uno dei tanti ORM (Object Role Modeling) presenti in rete. Nella figura seguente potete vedere lo schema del DAL:


   Data Access Layer utilizzando i DataSet tipizzati

Quando creiamo un nuovo Dataset, Visual Studio ci propone di salvare la stringa di connessione nel web.config, così da poterla modificare agevolmente all'occorenza senza toccare il codice. In ASP.NET 2.0 esiste una nuova sezione specifica per questo scopo e si chiama <connectionStrings> contenente una collection di oggetti ConnectionStringSettings. Nel nostro sito di esempio utilizzeremo questa sezione (in questa pagina, la stringa la connectionString è divisa su due righe solo per leggibilità, nel codice reale deve essere una riga sola, ovviamente):

  <connectionStrings>
    <add name="NORTHWNDConnectionString"
       connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF;
                         Integrated Security=True;User Instance=True"
       providerName="System.Data.SqlClient" />
  </connectionStrings>

Si tratta di impostare tre attributi che sono name, connectionString e providerType (il tipo di provider ADO.NET da utilizzare). Da notare il parametro AttachDbFilename nella connectionString che indica l'uso di un file mdf e il valore |DataDirectory| che indica ad ASP.NET che il file si trova nella cartella speciale App_Data.

Il Business Logic Layer
Il BLL è costituito da classi custom che riprendono la struttura delle tabelle, incapsulando anche la logica di utilizzo.
Ad esempio la classe Order contiene una collection di OrderDetail (OrderDetailCollection) ed espone un tipo Customer rappresentato a sua volta da una classe. Ecco lo schema del BLL:


   Struttura del Business Layer

Avrete notato che tutte le classi Business derivano dalla classe astratta BusinessObject. Lo scopo di questa classe è di fornire l'accesso ai vari provider contenuti nel DAL per la gestione dei dati agli oggetti business.
Un estratto della classe è il seguente:

  Public MustInherit Class BusinessObject

    Private Shared m_Database As Northwind

    Protected Shared ReadOnly Property Database() As Northwind
      Get
        If m_Database Is Nothing Then
          m_Database = New Northwind
        End If
        Return m_Database
      End Get
    End Property

    Private Shared m_CustomerProvider As NorthwindTableAdapters.CustomersTableAdapter

    Protected Shared ReadOnly Property CustomerProvider() As NorthwindTableAdapters.CustomersTableAdapter
      Get
        If m_CustomerProvider Is Nothing Then
          m_CustomerProvider = New NorthwindTableAdapters.CustomersTableAdapter
        End If
        Return m_CustomerProvider
      End Get
    End Property

    ' Altri provider come CustomerProvider [omissis]
  End Class

Molto semplicemente viene esposto l'intero Dataset Northwind ed i singoli TableAdapter come Provider per accedere alle singole tabelle.

Ora che abbiamo tutti gli oggetti necessari per la gestione del database, non ci resta che vedere com utilizzarli nel nostro sito web di esempio con i controlli Data Bound messi a disposizione da ASP.NET 2.0.
In questo articolo introdurremo semplicemente l'argomento, mostrando come configurare un ObjectDataSource per la visualizzazione dei dati rimandando agli articoli successivi tutti gli approfondimenti del caso.

Visualizzare i dati con ObjectDataSource
Nella stragrande maggioranza dei siti web (se non in tutti) lo scopo principale è visualizzare informazioni recuperate tipicamente da una sorgente dati rappresentata da un database. Ebbene, ASP.NET 2.0 introduce e migliora diversi controlli "Data oriented" in grado di facilitare questa operazione riducendo a zero il codice da scrivere.
Vediamo come sia semplice visualizzare l'anagrafica dei clienti in una GridView.

Innanzitutto creiamo una nuova cartella "3_ObjectDataSource" nel sito web dove inserire la pagina di esempio ed aggiungiamo una nuova pagina web chiamata ObjectDataSource.aspx

Trasciniamo il controllo ObjectDataSource che troviamo nella scheda "Data" della Toolbox, e selezionamo "Configure Data Source..." dallo SmartTag del controllo stesso...

...Si avvia il wizard per la selezione della fonte dati.

Il primo passo consiste nel selezionare l'oggetto business da utilizzare, scegliendo tra quelli elencati nella combobox. L'elenco comprende tutte le classi del progetto e degli assembly referenziati che hanno l'attributo <DataSource>, essendo per default spuntata l'opzione "Show only data components". Dato che nel nostro esempio le classi business non hanno questo attributo, togliamo la spunta, selezioniamo la classe Customer nel namespace VBTT.Tutorial.BL e proseguiamo premendo "Next >".

La seconda fase prevede la scelta dei metodi da utilizzare per le quattro operazioni possibili, cioè SELECT, INSERT, UPDATE e DELETE.


   Wizard per la selezione del metodo di SELECT

La combobox elenca tutti i metodi dell'oggetto business. Dovendo configurare solo il comando SELECT, sceglieremo il metodo GetCustomers() che restituisce una collection di oggetti Customer di tipo CustomerCollection. I metodi elencati sono sia quelli statici (Shared) che di istanza (quest'ultimi devono avere un costruttore senza parametri dato che il controllo crea e distrugge l'oggetto tutte le volte che lo utilizza).

Vediamo come è fatto il metodo GetCustomers della classe Customer nel BLL:

        Public Shared Function GetCustomers() As CustomerCollection
            Dim dt As Northwind.CustomersDataTable = CustomerProvider.GetCustomers()

            Dim Customers As New CustomerCollection
            For Each row As DataRow In dt.Rows
                Customers.Add(Customer.LoadDataFromDataRow(row))
            Next
            Return Customers
        End Function

I dati vengono recuperati mediante il DAL utilizzando l'oggetto CustomerProvider messo a disposizione dalla classe base BusinessObject. La funzione privata LoadDataFromDataRow, invece, estrae i dati da un DataRow per riempire un oggetto Customer da inserire nella collection CustomerCollection.
Ritornando al nostro wizard per l'inserimento dell'ObjectDataSource, premendo Finish otteniamo questo markup:

    <asp:ObjectDataSource ID="odsCustomers" runat="server" SelectMethod="GetCustomers"
        TypeName="VBTT.Tutorial.BLL.Customer">
    </asp:ObjectDataSource>

La proprietà TypeName identifica il nome dell'oggetto (completo di namespace) da utilizzare mentre SelectMethod il nome del metodo da invocare per le operazioni di SELECT cioè di richiesta dati.

Visualizza dati con il GridView
Ora che abbiamo il nostro DataSourceControl configurato, non ci resta che usare uno dei tanti controlli offerti da ASP.NET per visualizzare i dati. Useremo il controllo GridView (il successore del DataGrid potenziato e ottimizzato per lavorare con i DataSourceControl) per visualizzare l'elenco completo dei clienti.

L'operazione è davvero semplice: trasciniamo il controllo (che trovare nella scheda "Data" della ToolBox) sulla pagina e, mediante lo SmartTag, impostiamo il Data Source scegliendolo dalla combo "Choose Data Source". Fatto questo, immediatamente il GridView si configura automaticamente, mostrando tante colonne quante sono le proprietà pubbliche definite nell'oggetto Customer:


   GridView collegato all'ObjectDataSource

Il markup generato è il seguente:

        <asp:GridView ID="gvCustomers" AutoGenerateColumns="False" runat="server" 
            DataSourceID="odsCustomers" Width="100%">
        </asp:GridView>

Come vedete, per collegare un controllo a un DataSource basta impostare la proprietà DataSourceID con il nome del DataSource.

Eseguendo la pagina, vedremo l'elenco di tutti i clienti restituiti dal metodo GetCustomers().


   Risultato della pagina con GridView collegato ad un ObjectDataSource

Non è male come risultato, considerando che non abbiamo scritto codice... e lo stesso risultato avremmo potuto ottenere nel modo classico, ovvero impostando la proprietà DataSource e invocando il metodo DataBind(), senza utilizzare l'ObjectDataSource.

Formattare il GridView
Il risultato naturalmente può essere migliorato mediante l'uso del designer del GridView, che consente di modificare gran parte dell'aspetto grafico e dei dati da visualizzare.
Ad esempio, per modificare le colonne da visualizzare selezioniamo "Edit Columns..." dallo SmartTag del GridView per accedere al comodo designer delle colonne:


   Editor dei campi del GridView

Togliamo la spunta all'opzione "Auto-generate fields", così da impostare manualmente le colonne da visualizzare selezionandole dall'elenco "All fields" in alto e premendo il pulsante "Add" per ciascun campo che si desidera visualizzare. Scegliamo di visualizzare i campi CustomerID, CompanyName, Address, City e Country. Per ciascuno di essi è possibile impostare diverse proprietà come l'intestazione, la formattazione dei dati ed il layout.
Il codice risultante è il seguente:

        <asp:GridView ID="gvCustomers" AutoGenerateColumns="False" runat="server" 
                      DataSourceID="odsCustomers" Width="100%">
        
            <Columns>
                <asp:BoundField DataField="CustomerID" HeaderText="ID" SortExpression="CustomerID">
                    <HeaderStyle Width="20px" />
                </asp:BoundField>
                <asp:BoundField DataField="CompanyName" HeaderText="Company Name" 
                                SortExpression="CompanyName" >
                    <HeaderStyle HorizontalAlign="Left" />
                </asp:BoundField>
                <asp:BoundField DataField="Address" HeaderText="Address" SortExpression="Address">
                    <HeaderStyle HorizontalAlign="Left" Width="200px" />
                </asp:BoundField>
                <asp:BoundField DataField="City" HeaderText="City" SortExpression="City">
                    <HeaderStyle HorizontalAlign="Left" Width="100px" />
                </asp:BoundField>
                <asp:BoundField DataField="Country" HeaderText="Country" SortExpression="Country">
                    <HeaderStyle HorizontalAlign="Left" Width="100px" />
                </asp:BoundField>
            </Columns>
        </asp:GridView>

Le colonne vengono definite all'interno di <Columns> mediante il tag <asp:BoundField> che indica una colonna collegata a dati. Le proprietà fondamentali sono DataField e HeaderText, che indicano rispettivamente il campo della sorgente dati a cui collegarsi e il nome dell'intestazione della colonna.
Infine, ci sono gli oggetti di stile <XxxxStyle> per definire il layout delle colonne.
Naturalmente possiamo formattare la griglia utilizzando i temi definendo uno skin generico per il controllo GridView nel file default.skin:

<asp:GridView GridLines="None" CssClass="grid" runat="server">
    <HeaderStyle CssClass="gridHeader" />
    <AlternatingRowStyle BackColor="#dce4f9" />
</asp:GridView>

Il risultato finale è il seguente:

Conclusioni
Seppur brevemente, abbiamo visto come sia piuttosto semplice realizzare delle pagine che visualizzano dati senza scrivere nemmeno una riga di codice. E, badate bene, l'assenza di codice non sminuisce il risultato, anzi evidenzia le potenzialità dei nuovi strumenti di ASP.NET.

Negli articoli successivi approfondiremo maggiormente il funzionamento dei DataSourceControl e vedremo come è possibile, oltre che visualizzare, anche inserire, modificare e cancellare dati.
Tutto il codice relativo a questo esempio è scaricabile dall'area download.
In merito a questo articolo, potete scrivere agli autori Antonio Catucci e Mario De Ghetto.