Condividi tramite


Scrittura di codice di accesso ai dati generico in ASP.NET 2.0 e ADO.NET 2.0

 

Dr. Shahram Khosravi
Soluzioni informative

Aprile 2005

Si applica a:

   Microsoft ADO.NET 2.0
   Microsoft ASP.NET 2.0
   Microsoft .NET Framework 2.0
   Microsoft Visual Web Developer 2005 Express Edition Beta
   Linguaggio di programmazione C#

Riepilogo: Usare un approccio dettagliato per imparare a usare diversi ASP.NET 2.0 e ADO.NET 2.0 strumenti e tecniche per la scrittura di codice di accesso ai dati generico. (18 pagine stampate)

Scaricare il codice di esempio associato: GenericDataAccessSample.exe.

Introduzione

La maggior parte delle applicazioni Web contiene codice di accesso ai dati per accedere all'archivio dati sottostante per eseguire operazioni di base sui dati, ad esempio Select, Update, Delete e Insert. Questo articolo usa un approccio dettagliato per illustrare in che modo gli sviluppatori di pagine possono sfruttare diversi ASP.NET 2.0 e ADO.NET 2.0 strumenti e tecniche per scrivere codice di accesso ai dati generico che può essere usato per accedere a diversi tipi di archivi dati. La scrittura di codice di accesso ai dati generico è particolarmente importante nelle applicazioni Web guidate dai dati, perché i dati provengono da molte origini diverse, tra cui Microsoft SQL Server, Oracle, documenti XML, file flat e servizi Web, solo per citarne alcuni.

Questo articolo usa una semplice applicazione Web come letto di test per tutto il codice presentato qui. L'applicazione è costituita da due parti: la prima parte consente all'amministratore di sistema di inviare newsletter a tutti i sottoscrittori di una mailing list. La seconda parte consente agli utenti di sottoscrivere o annullare la sottoscrizione a una mailing list. La prima parte dell'articolo inizia implementando un semplice codice di accesso ai dati (vedere la figura 1) per accedere a Microsoft SQL Server ed estrarre l'elenco dei sottoscrittori. Il codice viene modificato e reso più generico nel corso dell'articolo.

Figura 1. Il metodo GetSubscribers estrae l'elenco di tutti i sottoscrittori.

public IEnumerable GetSubscribers()
{
    SqlConnection con = new SqlConnection();
    con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";

    SqlCommand com = new SqlCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    SqlDataAdapter ad = new SqlDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

Poiché il codice di accesso ai dati nella figura 1 contiene il codice per la creazione degli oggetti ADO.NET, ad esempio le istanze SqlConnection, SqlCommand e SqlDataAdapter . Il codice di accesso ai dati non può essere usato per recuperare l'elenco di sottoscrittori da altri archivi dati, ad esempio un database Oracle. Gli sviluppatori di pagine devono modificare il codice di accesso ai dati (usando il metodo GetSubscribers ) ogni volta che devono accedere a un nuovo archivio dati. Vedere quindi come ADO.NET 2.0 usa il modello di provider per aiutare gli sviluppatori di pagine a scrivere codice di accesso ai dati generici per accedere a diversi tipi di archivi dati.

Modello di provider in ADO.NET 2.0

Il problema principale con il metodo GetSubscribers è che contiene il codice per la creazione degli oggetti ADO.NET. In base al modello del provider, il codice di accesso ai dati deve delegare la responsabilità di fornire il codice per creare gli oggetti ADO.NET a un'altra classe. Si fa riferimento a questa classe come "classe del provider di codice" perché fornisce il codice per la creazione degli oggetti ADO.NET. La classe del provider di codice espone metodi come CreateConnection, CreateCommand e CreateDataAdapter, in cui ogni metodo fornisce il codice per la creazione dell'oggetto ADO.NET corrispondente.

Poiché la classe del provider di codice contiene il codice effettivo, non è possibile usare la stessa classe per accedere a archivi dati diversi. Pertanto, il codice di accesso ai dati (metodo GetSubscribers) deve essere modificato e riconfigurato per delegare la responsabilità di fornire il codice a una nuova classe del provider di codice ogni volta che viene usato per accedere a un nuovo archivio dati. Il metodo GetSubscribers è ancora associato al codice anche se non contiene il codice.

Il modello di provider offre una soluzione a questo problema ed è costituito dai passaggi seguenti:

  1. Progettare e implementare una classe di provider di base astratta.

  2. Derivare tutte le classi del provider di codice dalla classe del provider di base astratta.

  3. Fare in modo che il codice di accesso ai dati (metodo GetSubscribers) usi la classe base astratta anziché le singole classi del provider di codice.

    La classe base astratta delega la responsabilità di fornire il codice per la creazione degli oggetti ADO.NET alla sottoclasse appropriata. La classe base astratta è denominata DbProviderFactory. Di seguito vengono presentati alcuni dei metodi di questa classe:

    public abstract class DbProviderFactory
    {
            public virtual DbConnection CreateConnection();
            public virtual DbCommand CreateCommand();
            public virtual DbDataAdapter CreateDataAdapter();
    }
    

    Ogni sottoclasse fornisce il codice per creare gli oggetti ADO.NET appropriati per un determinato archivio dati. Ad esempio, la sottoclasse SqlClientFactory fornisce il codice per la creazione degli oggetti ADO.NET per accedere a Microsoft SQL Server, come illustrato nella figura 2.

    Figura 2. Classe SqlClientFactory e alcuni dei relativi metodi

    public class SqlClientFactory : DbProviderFactory
    {
            public override DbConnection CreateConnection()
            {
                    return new SqlConnection();
            }
    
           public override DbCommand CreateCommand()
           {
                    return new SqlCommand();
           }
    
           public override DbDataAdapter CreateDataAdapter()
           {
                 return new SqlDataAdapter();
           }
    }
    

Il modello di provider consente al codice di accesso ai dati di trattare tutte le sottoclassi uguali perché sono tutte sottoclassi della stessa classe di base. Per quanto riguarda il codice di accesso ai dati, tutte le sottoclassi sono di tipo DbProviderFactory. Il codice di accesso ai dati non consente di conoscere il tipo specifico della sottoclasse in uso. Questo introduce un nuovo problema. Se il codice di accesso ai dati (metodo GetSubscribers) non conosce il tipo di sottoclasse, come può creare un'istanza della sottoclasse?

La soluzione del modello di provider a questo problema è costituita dalle tre parti seguenti:

  1. Per identificare ogni sottoclasse viene usata una stringa univoca. ADO.NET 2.0 usa lo spazio dei nomi della sottoclasse come ID stringa univoco. Ad esempio, le sottoclassi SqlClient e System.Data.OracleClient dell'ID stringa univoco identificano rispettivamente le sottoclassi SqlClientFactory e OracleClientFactory .
  2. Un file di testo (in genere un file XML) viene usato per archiviare informazioni su tutte le sottoclassi. ADO.NET 2.0 usa i file machine.config e web.config per archiviare le informazioni necessarie. Le informazioni su una sottoclasse contengono, tra l'altro, l'ID stringa univoco e il nome del tipo della sottoclasse. Ad esempio, le informazioni sulla sottoclasse SqlClientFactory includono l'ID stringa univoco System.Data.SqlClient e il nome del tipo della sottoclasse, ad esempio System.Data.SqlClient.SqlClientFactory.
  3. Un metodo statico è progettato e implementato. Il metodo può far parte della classe base astratta o parte di una classe separata. ADO.NET 2.0 usa una classe separata denominata DbProviderFactories che espone il metodo statico GetFactory . Il metodo accetta l'ID stringa univoco della sottoclasse desiderata come unico argomento e cerca nel file machine.config una sottoclasse con l'ID stringa univoco specificato. Il metodo estrae il nome del tipo della sottoclasse desiderata e usa la reflection per creare dinamicamente un'istanza della sottoclasse.

Il codice di accesso ai dati (metodo GetSubscribers) chiama il metodo statico GetFactory e passa l'ID stringa univoco appropriato per accedere all'istanza della sottoclasse corrispondente. Dopo che il metodo GetSubscribers accede all'istanza, chiama i metodi di creazione appropriati, ad esempio CreateConnection(), CreateCommand() e così via, per creare un'istanza degli oggetti ADO.NET appropriati, come illustrato nella figura 3.

Figura 3. Versione del metodo GetSubscribers che usa il nuovo modello di provider ADO.NET

public IEnumerable GetSubscribers()
{
    DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
    DbConnection con = provider.CreateConnection();
    con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf";
    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

Il codice di accesso ai dati (metodo GetSubscribers) delega la responsabilità di fornire il codice per la creazione degli oggetti ADO.NET all'istanza della classe del provider di codice di cui il metodo GetFactory crea un'istanza e restituisce. Di conseguenza, lo stesso codice di accesso ai dati può essere usato per accedere a archivi dati diversi, ad esempio Microsoft SQL Server e Oracle.

Il codice per creare gli oggetti ADO.NET corretti è specifico dell'archivio dati. Il modello di provider in ADO.NET 2.0 rimuove queste parti specifiche dell'archivio dati dal codice di accesso ai dati (metodo GetSubscribers) per renderle più generiche. Tuttavia, il modello di provider non rimuove tutte le parti specifiche dell'archivio dati. Un'analisi più approfondita del metodo GetSubscribers rivela le parti rimanenti specifiche dell'archivio dati:

  1. Stringa di connessione
  2. ID stringa univoco che identifica la classe del provider di codice sottostante
  3. Testo del comando
  4. Tipo di comando

A meno che non vengano eseguite operazioni sulle parti precedenti, il codice di accesso ai dati è ancora associato a un particolare tipo di archivio dati. Il modello del provider in ADO.NET 2.0 non aiuta a risolvere questo problema. Tuttavia, ADO.NET 2.0 offre altri strumenti e tecniche per rimuovere le prime due parti specifiche dell'archivio dati, ad esempio la stringa di connessione e l'ID stringa univoca dal metodo GetSubscribers.

Stringhe di connessione

Le stringhe di connessione sono alcune delle risorse più importanti in un'applicazione Web. Sono così importanti che .NET Framework 2.0 li considera come "cittadini di prima classe". Il file web.config supporta ora una nuova sezione denominata <connectionStrings> che contiene tutte le stringhe di connessione usate in un'applicazione. Di conseguenza, la stringa di connessione verrà spostata dal metodo GetSubscribers a questa sezione:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
          <add 
            name="MySqlConnectionString" 
            connectionString="Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
      providerName="System.Data.SqlClient"/>
    </connectionStrings>
</configuration>

Il <sottoelemento add> dell'elemento <connectionStrings> espone i tre attributi importanti seguenti:

  • Nome: nome descrittivo della stringa di connessione
  • connectionString: stringa di connessione effettiva
  • providerName: ID stringa univoco o invariante della classe del provider di codice

NET Framework 2.0 fornisce il codice di accesso ai dati (metodo GetSubscribers) con gli strumenti appropriati per estrarre in modo generico il valore della stringa di connessione dal file web.config, come descritto di seguito. Lo spazio dei nomi System.Configuration in .NET Framework 2.0 include una nuova classe denominata Configuration. Questa classe rappresenta l'intero contenuto di un web.config o di un file di machine.config. Il codice di accesso ai dati non può usare il nuovo operatore per creare direttamente un'istanza di questa classe.

La classe stessa espone un metodo statico denominato GetWebConfiguration che accetta il percorso del file web.config e restituisce un'istanza della classe Configuration che rappresenta l'intero contenuto del file web.config:

Configuration configuration = Configuration.GetWebConfiguration("~/");

Una classe che eredita dalla classe ConfigurationSection rappresenta ogni sezione del file web.config. Il nome della classe è costituito dal nome della sezione e dalla parola chiave Section. Ad esempio, la classe ConnectionStringsSection rappresenta il contenuto della <sezione connectionStrings> del file web.config. Il codice di accesso ai dati (metodo GetSubscribers) non può usare il nuovo operatore per creare direttamente un'istanza della classe ConnectionStringsSection. La classe Configuration espone una proprietà di raccolta denominata Sections che contiene tutti gli oggetti che rappresentano sezioni diverse del file web.config:

ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];

Poiché Sections è una raccolta di oggetti ConfigurationSection, il codice di accesso ai dati deve digitare il cast dell'istanza restituita. Dopo che il metodo GetSubscribers accede all'oggetto ConnectionStringsSection e lo usa per accedere al valore della stringa di connessione:

string connectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;

La figura 4 mostra la nuova versione del metodo GetSubscribers che contiene il codice necessario per estrarre la stringa di connessione in modo generico.

Figura 4. Versione del metodo GetSubscribers che estrae la stringa di connessione dal file web.config

public IEnumerable GetSubscribers()
{
    DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
    DbConnection con = provider.CreateConnection();

    Configuration configuration = Configuration.GetWebConfiguration("~/");
    ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
    con.ConnectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;

    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

Il metodo GetSubscribers include ora la stringa MySqlConnectionString , quindi è comunque necessario modificare il metodo GetSubscribers per aggiungere il supporto per un archivio dati diverso, ad esempio il database Oracle. Sembra che siamo tornati al quadrato uno. Risposta errata. Sono stati ottenuti alcuni vantaggi importanti spostando la stringa di connessione dal codice di accesso ai dati al file web.config:

  • La stringa di connessione è ora il valore dell'attributo connectionString dell'elemento <> add sub dell'elemento connectionStrings del file web.config, ovvero un documento XML. L'aspetto importante di un documento XML è che è possibile crittografare un singolo elemento nel documento. Non è necessario crittografare l'intero documento se è necessario proteggere solo una piccola parte di esso. .NET Framework 2.0 include uno strumento che consente di crittografare la <sezione connectionStrings> per proteggere la risorsa più importante, le stringhe di connessione. Immagina quanto danno un hacker può fare al nostro prezioso database se ottiene la mano sulle nostre stringhe di connessione. Tenere presente che le stringhe di connessione sono tutte necessarie agli hacker per accedere al database.

  • Potrebbe sembrare che tutto quello che abbiamo fatto è sostituire la stringa seguente

    "Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
    

    con la nuova stringa MySqlConnectionString. Tuttavia, c'è una grande differenza. La prima stringa contiene le informazioni specifiche del database SQL Server che non si applicano a un altro database, ad esempio Oracle, ma la seconda stringa è solo un nome descrittivo.

Tuttavia, il nome descrittivo potrebbe comunque causare problemi perché fa riferimento a una stringa di connessione specifica all'interno della <sezione connectionStrings> del file web.config. In questo esempio fa riferimento alla stringa di connessione usata per accedere a Microsoft SQL Server. Ciò significa che il metodo GetSubscribers (il codice di accesso ai dati) deve essere modificato per usare un nome descrittivo diverso per accedere a un archivio dati diverso, ad esempio Oracle.

Per evitare di modificare il codice di accesso ai dati, è possibile spostare il nome descrittivo dal codice di accesso ai dati alla <sezione appSettings> del file web.config e fare in modo che il codice di accesso ai dati lo estragga dinamicamente in runtime come indicato di seguito:

string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];

Il nome del provider viene anche spostato nella <sezione appSettings> :

string providerName = ConfigurationSettings.AppSettings["ProviderName"];

Gli sviluppatori di pagine modificano semplicemente l'attributo value dell'elemento <add> subelement dell'elemento <appSettings> allo stesso codice di accesso ai dati per accedere a un archivio dati diverso senza apportare modifiche al codice di accesso ai dati stesso.

La figura 5 presenta la versione del codice di accesso ai dati (metodo GetSubscribers) che contiene le modifiche recenti.

Figura 5. Versione del metodo GetSubscribers per estrarre il nome del provider e il nome descrittivo della stringa di connessione dal file web.config

public IEnumerable GetSubscribers()
{
    string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
    string providerName = ConfigurationSettings.AppSettings["ProviderName"];
    Configuration configuration = Configuration.GetWebConfiguration("~/");
    ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];

    

    DbProviderFactory provider = DbProviderFactories.GetFactory(providerName);
    DbConnection con = provider.CreateConnection();
    con.ConnectionString = section.ConnectionStrings[connectionStringName].ConnectionString;

    DbCommand com = provider.CreateCommand();
    com.Connection = con;
    com.CommandText = "Select * From Subscribers";
    com.CommandType = CommandType.Text;

    DataSet ds = new DataSet();
    DbDataAdapter ad = provider.CreateDataAdapter();
    ad.SelectCommand = com;

    con.Open();
    ad.Fill(ds);
    con.Close();

    return ds.Tables[0].DefaultView;
}

Controlli origine dati

La versione più recente del metodo GetSubscribers, come illustrato nella figura 5, non è ancora generica a causa dei problemi seguenti:

  • Il metodo contiene informazioni specifiche, ad esempio il testo del comando e il tipo di comando. Pertanto, gli sviluppatori di pagine devono comunque modificare il metodo prima di poterlo usare per accedere a un database diverso.
  • Il metodo restituisce un'istanza della classe DataView ai chiamanti in modo che il metodo inserisca i dati tabulari dei chiamanti. È possibile considerarlo come un contratto tra il metodo GetSubscribers e i chiamanti. I chiamanti prevedono che il metodo GetSubscribers rispetta il contratto in tutte le circostanze anche quando l'archivio dati sottostante stesso non è tabulare. Poiché il metodo GetSubscribers usa ADO.NET oggetti per accedere all'archivio dati sottostante, non può accedere a un archivio dati gerarchico, ad esempio un file XML, in cui le istanze delle classi nel System.Xml e i relativi spazi dei nomi secondari devono essere usati anziché ADO.NET oggetti .

Il problema principale con il codice di accesso ai dati usato nel metodo GetSubscribers è che contiene direttamente il codice effettivo che estrae i dati dall'archivio dati sottostante. Questo è esattamente il tipo di problema che il modello di provider .NET Framework 2.0 è stato progettato appositamente per risolvere. In base al modello del provider, il metodo GetSubscribers deve delegare la responsabilità di fornire il codice per l'accesso all'archivio dati a una classe diversa. Viene chiamata classe del provider di codice. Il tipo di classe del provider di codice dipende dal tipo di archivio dati a cui accede. Queste classi del provider di codice sono note collettivamente come controlli origine dati. ASP.NET 2.0 include diversi tipi di controlli origine dati, inclusi i controlli SqlDataSource, AccessDataSource, ObjectDataSource, XmlDataSource e SiteMapDataSource.

Il controllo SqlDataSource è progettato specificamente per aggiornare, eliminare, inserire ed estrarre dati da archivi dati relazionali, ad esempio Microsoft SQL Server e Oracle. Il controllo AccessDataSource è una sottoclasse del controllo SqlDataSource che sa come usare i database di Microsoft Access. Il controllo ObjectDataSource, d'altra parte, usa oggetti business in memoria come archivio dati. Il controllo XmlDataSource è progettato specificamente per estrarre dati da documenti XML. Tuttavia, il controllo XmlDataSource non fornisce l'accesso in scrittura al documento XML sottostante.

Ogni controllo origine dati espone una o più viste dell'archivio dati sottostante. Ogni visualizzazione è un'istanza di una classe appropriata. Ad esempio, i controlli SqlDataSource, AccessDataSource e ObjectDataSource espongono le viste che sono istanze delle classi SqlDataSourceView, AccessDataSourceView e ObjectDataSourceView , rispettivamente. Le viste nascondono il tipo reale dell'archivio dati sottostante e lo rendono simile al tipo previsto dal codice di accesso ai dati. Il metodo GetSubscribers, ad esempio, prevede un tipo tabulare di archivio dati perché inserisce i dati tabulari dei client. Le viste tabulari consentono al metodo GetSubscribers di estrarre dati tabulari dall'archivio dati sottostante anche quando l'archivio dati stesso è un'origine dati gerarchica, ad esempio un documento XML. In questo modo il metodo GetSubscribers può trattare sia gli archivi dati tabulari che gerarchici come archivi dati tabulari.

I controlli origine dati possono fornire ai client due tipi di viste: tabulare e gerarchica. ASP.NET 2.0 include due controlli origine dati che forniscono entrambi i tipi di viste, XmlDataSource e SiteMapDataSource. Il resto dei controlli origine dati, Ovvero SqlDataSource, AccessDataSource e ObjectDataSource, presenta solo visualizzazioni tabulari. Tuttavia, possono essere estese per fornire visualizzazioni tabulari e gerarchici.

Controlli origine dati tabulari

Un controllo origine dati tabulare rende l'archivio dati sottostante come un archivio dati tabulare indipendentemente dal fatto che l'archivio dati sia tabulare. Un archivio dati tabulare è costituito da tabelle di righe e colonne in cui ogni riga rappresenta un elemento di dati. Il nome di una tabella identifica in modo univoco e individua la tabella tra le altre tabelle nell'archivio dati tabulare. Una vista tabulare agisce come una tabella, il che significa che le viste sono denominate.

Come illustrato in precedenza, ogni classe del controllo origine dati e la relativa classe di visualizzazione associata (ad esempio, la classe SqlDataSource e la classe SqlDataSourceView associata) forniscono il codice effettivo per l'aggiornamento, l'eliminazione, l'inserimento e l'estrazione di dati dall'archivio dati sottostante. Ovviamente, il codice per ogni controllo origine dati e la classe di visualizzazione associata è progettato in modo specifico per funzionare con un tipo specifico di archivio dati. Di conseguenza, ogni classe del controllo origine dati e la classe di visualizzazione associata sono specifiche dell'archivio dati. Ciò costituisce un problema grave per il metodo GetSubscribers che usa controlli origine dati e le relative viste tabulari per accedere all'archivio dati sottostante perché collega il metodo a un tipo specifico di archivio dati, il che significa che lo stesso metodo non può essere usato per accedere a tipi diversi di archivi dati.

ASP.NET 2.0 offre una soluzione che usa il modello di provider per:

  1. Introdurre l'interfaccia IDataSource e la classe astratta DataSourceView
  2. Derivare tutti i controlli origine dati tabulari dall'interfaccia IDataSource
  3. Derivare tutte le viste tabulari dalla classe astratta DataSourceView

L'interfaccia IDataSource e la classe astratta DataSourceView delegano la responsabilità di fornire il codice effettivo per l'aggiornamento, l'eliminazione, l'inserimento e l'estrazione dei dati dall'archivio dati alle sottoclassi appropriate. Il codice di accesso ai dati, ad esempio il metodo GetSubscribers, deve usare i metodi e le proprietà dell'interfaccia IDataSource e della classe astratta DataSourceView. Non devono usare metodi o proprietà specifici di una determinata classe del controllo origine dati, ad esempio SqlDataSource o una classe di vista origine dati specifica, ad esempio SqlDataSourceView. Il modello di provider consente al codice di accesso ai dati di gestire tutti i controlli origine dati e le rispettive viste origine dati in modo generico. Per quanto riguarda il codice di accesso ai dati, tutti i controlli origine dati sono di tipo IDataSource e tutte le viste origine dati sono di tipo DataSourceView. Il codice di accesso ai dati non consente di conoscere il tipo effettivo del controllo origine dati e dell'oggetto vista origine dati in uso. Ciò causa un nuovo problema. Se il codice di accesso ai dati (metodo GetSubscribers) non conosce il tipo del controllo origine dati, come può creare un'istanza di tale oggetto?

Come illustrato in precedenza, il modello di provider fornisce una soluzione costituita dai passaggi seguenti:

  1. Per identificare ogni classe del controllo origine dati viene usato un ID stringa univoco.
  2. Un file di testo (in genere un file XML) viene usato per archiviare informazioni su tutte le classi di controllo origine dati.
  3. Un meccanismo è progettato e implementato che cerca una sottoclasse nel file XML con un ID stringa specificato.

Vediamo ora come ASP.NET 2.0 implementa le tre attività precedenti del modello di provider. ASP.NET 2.0 deriva tutti i controlli origine dati dalla classe Control . Perché i controlli origine dati derivano dalla classe Control se non eseguono il rendering del testo di markup HTML? Derivano dalla classe Control in modo che possano ereditare le tre funzionalità importanti seguenti:

  1. Possono essere create istanze in modo dichiarativo.
  2. Salvano e ripristinano i valori delle proprietà tra postback.
  3. Vengono aggiunti all'albero dei controlli della pagina contenitore.

La prima funzionalità consente agli sviluppatori di pagine di creare istanze dichiarative dei controlli origine dati nei rispettivi file aspx. Di conseguenza, il file aspx funge da file di testo o XML richiesto dal secondo passaggio del modello di provider. L'architettura del controllo ASP.NET 2.0 crea dinamicamente un'istanza del controllo origine dati dichiarato e assegna l'istanza a una variabile il cui nome è il valore della proprietà ID del controllo origine dati dichiarato. Questa operazione si occupa dei primi e dei terzi passaggi precedenti richiesti dal modello di provider.

La figura 6 mostra la versione del metodo GetSubscribers che usa i metodi e le proprietà dell'interfaccia IDataSource e della classe astratta DataSourceView per accedere all'archivio dati sottostante:

Figura 6. Versione del metodo GetSubscribers che usa i metodi e le proprietà dell'interfaccia IDataSource e della classe astratta DataSourceView

void GetSubscribers()
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    DataSourceSelectArguments args = new DataSourceSelectArguments();
    if (dv.CanSort)
        args.SortExpression = "Email";
    DataSourceViewSelectCallback callback = new DataSourceViewSelectCallback(SendMail);
    dv.Select(args, callback);
}

La prima riga del metodo GetSubscribers mostra chiaramente che il metodo considera il controllo origine dati come oggetto di tipo IDataSource. Il metodo non deve e non deve preoccuparsi del tipo reale del controllo origine dati, ad esempio se si tratta di un controllo SqlDataSource, AccessDataSource, ObjectDataSource o XmlDataSource. In questo modo gli sviluppatori di pagine potranno passare da un controllo origine dati a un altro senza dover modificare il codice di accesso ai dati (metodo GetSubscribers). La sezione successiva illustra questo problema importante in altri dettagli.

Il metodo GetSubscribers chiama il metodo GetView dell'oggetto IDataSource per accedere al relativo oggetto visualizzazione tabulare predefinito. Si noti che il metodo GetView restituisce un oggetto di tipo DataSourceView. Il metodo GetSubscribers non deve preoccuparsi del tipo reale dell'oggetto view, ad esempio se è un oggetto SqlDataSourceView, AccessDataSourceView, ObjectDataSourceView o XmlDataSourceView.

Successivamente, il metodo GetSubscribers crea un'istanza della classe DataSourceSelectArguments per richiedere operazioni aggiuntive, ad esempio l'inserimento, il paging o il recupero del conteggio totale delle righe sui dati restituiti dall'operazione Select . Il metodo deve prima controllare il valore della proprietà CanInsert, CanPage o CanRetrieveTotalRowCount della classe DataSourceView per assicurarsi che l'oggetto view supporti la rispettiva operazione prima di effettuare la richiesta.

Poiché l'operazione Select è asincrona, il metodo GetSubscribers registra il metodo SendMail come callback. Il metodo Select chiama automaticamente il metodo SendMail dopo aver eseguito query sui dati e passa i dati come argomento, come illustrato nella figura 7.

Figura 7. Il metodo SendMail enumera i dati ed estrae le informazioni necessarie.

void SendMail(IEnumerable data)
{
    string firstName = String.Empty;
    string lastName = String.Empty;
   
    IEnumerator iter = data.GetEnumerator();
    while (iter.MoveNext())
    {
        MailMessage message = new MailMessage();
        message.From = "admin@greatnews.com";
        message.To = DataBinder.Eval(iter.Current, "Email").ToString();
        message.Subject = "NewsLetter";
        firstName = DataBinder.Eval(iter.Current, "FirstName").ToString();
        lastName = DataBinder.Eval(iter.Current, "LastName").ToString();
        string mes = "Dear " + firstName + " " + lastName + ",<br/>";
        mes += MessageBody.Text;
        message.Body = mes;
        message.BodyFormat = MailFormat.Html;
      SmtpMail.SmtpServer = "<myserver>";
      SmtpMail.Send(message);
    }
}

Il metodo SendMail chiama il metodo GetEnumerator dell'oggetto passato come primo argomento per accedere all'oggetto enumeratore e usa l'enumeratore per enumerare i dati. Il metodo SendMail usa il metodo Eval della classe DataBinder per estrarre l'indirizzo di posta elettronica, il nome e il cognome di ogni sottoscrittore e invia la lettera di notizie a ogni sottoscrittore.

Passaggio da un controllo origine dati a un altro

Come illustrato in precedenza, l'architettura di controllo ASP.NET crea dinamicamente un'istanza del controllo origine dati dichiarata nella rispettiva pagina aspx e la assegna alla variabile il cui nome è il valore della proprietà ID del controllo origine dati dichiarato. Tale istanza dinamica del controllo origine dati dichiarata isola il metodo GetSubscribers dal tipo effettivo del controllo origine dati e consente al metodo di trattare tutti i controlli origine dati come oggetti di tipo IDataSource. In questo modo gli sviluppatori di pagine possono passare da un tipo di controllo origine dati a un altro senza modificare il codice di accesso ai dati (metodo GetSubscribers). Questa sezione presenta un esempio per questo caso.

Si supponga che l'applicazione Web usi il metodo GetSubscribers insieme al controllo ObjectDataSource per recuperare l'elenco di sottoscrittori da un archivio dati relazionale, ad esempio Microsoft SQL Server:

<asp:SqlDataSource ID="MySource" Runat="Server"
ConnectionString="<%$ ConnectionStrings:MySqlConnectionString %>"
SelectCommand="Select * From Subscribers" />

Si supponga che l'applicazione Web funzioni in un ambiente in cui l'elenco di sottoscrittori potrebbe anche venire da un documento XML. Il documento XML potrebbe essere un file XML locale o una risorsa remota a cui si accede tramite URL. Pertanto, l'applicazione deve essere in grado di recuperare l'elenco di sottoscrittori da documenti XML. Ovviamente il controllo ObjectDataSource non è progettato per recuperare i dati tabulari dai documenti XML, il che significa che è necessario usare un controllo origine dati che può recuperare i dati tabulari dai documenti XML, ad esempio il controllo XmlDataSource.

Il metodo GetSubscribers non usa alcuna proprietà o metodo specifico per le classi ObjectDataSource e ObjectDataSourceView e usa solo i metodi e le proprietà della classe astratta IDataSource e DataSourceView per gestire i controlli origine dati. È possibile passare facilmente dal controllo ObjectDataSource al controllo XmlDataSource e usare lo stesso codice di accesso ai dati del metodo GetSubscribers per recuperare l'elenco dei sottoscrittori:

<asp:XmlDataSource ID="MySource" Runat="Server"
      DataFile="data.xml" XPath="/Subscribers/Subscriber" />

Il valore dell'attributo XPath del controllo XmlDataSource è impostato su /Sottoscrittore/Sottoscrittore per selezionare tutti i sottoscrittori.

Operazione di inserimento ed eliminazione

Ricordare che l'applicazione Web è costituita da due parti. La seconda parte dell'applicazione consente agli utenti di sottoscrivere/annullare la sottoscrizione da un elenco di indirizzi. Il metodo Subscribe viene chiamato quando viene fatto clic sul pulsante Sottoscrizioni, come illustrato nella figura 8.

Figura 8. Il metodo Subscribe viene chiamato quando viene fatto clic sul pulsante Sottoscrizioni.

void Subscribe(Object sender, EventArgs e)
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    KeyedList values = new KeyedList();
    values.Add("Email", Email.Text);
    values.Add("FirstName", FirstName.Text);
    values.Add("LastName", LastName.Text);
    DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
    if (dv.CanInsert)
        dv.Insert(values, callback);
}

La prima riga del metodo Subscribe mostra che il metodo non si preoccupa del tipo reale del controllo origine dati. È pertanto possibile passare a un controllo origine dati diverso per supportare un nuovo archivio dati senza dover modificare il codice nel metodo Subscribe.

Il metodo usa un'istanza della classe KeyedList per raccogliere il nome, il nome e il cognome del sottoscrittore. Non è necessario usare la classe KeyedList. È possibile usare qualsiasi classe che implementa l'interfaccia IDictionary , tra cui ArrayList, KeyedList e così via.

Il metodo Subscribe controlla il valore della proprietà CanInsert dell'oggetto vista origine dati per assicurarsi che l'oggetto view supporti l'operazione Insert prima di chiamare il metodo Insert . Il metodo Subscribe passa l'istanza KeyedList come primo argomento al metodo Insert.

Il metodo Unsubscribe funziona in modo simile al metodo Subscribe. La differenza principale è che il metodo Annulla chiama il metodo Delete del rispettivo oggetto view per rimuovere la sottoscrizione dall'archivio dati sottostante, come illustrato nella figura 9.

Figura 9. Il metodo Annulla sottoscrizione viene chiamato quando viene fatto clic sul pulsante Annulla sottoscrizione.

void Unsubscribe(Object sender, EventArgs e)
{
    IDataSource ds = (IDataSource)MySource;
    DataSourceView dv = ds.GetView(String.Empty);
    KeyedList keys = new KeyedList();
    keys.Add("Email", Email.Text);
    KeyedList oldValues = new KeyedList();
    oldValues.Add("Email", Email.Text);
    DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
    if (dv.CanDelete)
        dv.Delete(keys, oldValues, callback);
}

Controlli origine dati gerarchici

Il metodo GetSubscribers (il codice di accesso ai dati) invia dati tabulari ai chiamanti. Tuttavia, esistono momenti in cui il codice di accesso ai dati deve restituire dati gerarchici ai chiamanti. Questa sezione presenta l'implementazione della versione del metodo GetSubscribers che restituisce dati gerarchici. Possiamo pensare a questo come contratto tra il metodo e i suoi chiamanti. I chiamanti prevedono che il metodo restituisca dati gerarchici da archivi dati gerarchici e tabulari.

ASP.NET 2.0 usa il modello del provider per isolare il metodo GetSubscribers dal tipo effettivo dell'archivio dati sottostante e presenta il metodo con le visualizzazioni gerarchiche dell'archivio dati. Ciò consente al metodo di trattare archivi dati gerarchici e tabulari come archivi dati gerarchici.

Ogni controllo origine dati gerarchico è progettato in modo specifico per lavorare con un archivio dati specifico. Tuttavia, poiché tutti i controlli dell'origine dati gerarchici implementano l'interfaccia IHierarchicalDataSource e tutte le viste origine dati gerarchiche derivano dalla classe GerarchicaDataSourceView , il metodo GetSubscribers non deve gestire le specifiche di ogni controllo origine dati e può trattarli in modo generico.

IHierarchicalEnumerable GetSubscribers()
{
    IHierarchicalDataSource ds = (IHierarchicalDataSource)MySource;
    HierarchicalDataSourceView dv = ds.GetHierarchicalView("/Subscribers");
    return dv.Select();
}

La prima riga del metodo GetSubscribers mostra che il metodo considera il controllo origine dati come oggetto di tipo IHierarchicalDataSource e non si occupa del tipo reale del controllo origine dati. In questo modo gli sviluppatori di pagine potranno passare a un nuovo controllo origine dati gerarchico per aggiungere il supporto per un nuovo archivio dati senza dover modificare il codice nel metodo GetSubscribers.

Il metodo GetSubscribers chiama quindi il metodo GetHierarchicalView della classe HierarchicalDataSourceView per accedere alla visualizzazione gerarchica con il percorso specificato, ad esempio "/Sottoscrittori". Si noti che il metodo Select non è asincrono. L'applicazione passa i dati restituiti dal metodo GetSubscribers al metodo SendMail (vedere La figura 15). Si noti che i dati sono di tipo IHierarchicalEnumerable.

IHierarchicalEnumerable implementa IEnumerable, ovvero espone il metodo GetEnumerator . Il metodo SendMail chiama il metodo GetEnumerator per accedere al rispettivo oggetto IEnumerator, usato successivamente per enumerare i dati. IHierarchicalEnumerable espone anche un metodo denominato GetHierarchyData che accetta l'oggetto enumerato e restituisce l'oggetto IHierarchyData associato.

L'interfaccia IHierarchyData espone una proprietà importante denominata Item, che non è altro che l'elemento di dati. Il metodo SendMail usa il metodo Eval della classe XPathBinder per valutare le espressioni XPath rispetto all'oggetto Item.

Figura 10. Il metodo SendMail enumera i dati, estrae le informazioni necessarie e invia la newsletter a ogni sottoscrittore.

void SendMail(IHierarchicalEnumerable data)
{
    string firstName = String.Empty;
    string lastName = String.Empty;

    IEnumerator iter = data.GetEnumerator();
    while (iter.MoveNext())
    {
        IHierarchyData ihdata = data.GetHierarchyData(iter.Current);
        MailMessage message = new MailMessage();
        message.From = "admin@subscribers.com";
        message.To = XPathBinder.Eval(ihdata.Item, "@Email").ToString();
        message.Subject = "NewsLetter";
        firstName = XPathBinder.Eval(ihdata.Item, "@FirstName").ToString();
        lastName = XPathBinder.Eval(ihdata.Item, "@LastName").ToString();
        string mes = "Hi " + firstName + " " + lastName + ",<br/>";
        mes += MessageBody.Text;
        message.Body = mes;
        message.BodyFormat = MailFormat.Html;
        SmtpMail.SmtpServer = "MyServer";
        SmtpMail.Send(message);
    }
}

Conclusione

Usando un approccio dettagliato che mostra diversi ASP.NET 2.0 e ADO.NET strumenti e tecniche 2.0, questo articolo illustra come gli sviluppatori di pagine possono scrivere codice di accesso ai dati generici che possono essere usati per accedere a diversi tipi di archivi dati.

Dr. Shahram Khosraviè un senior software engineer con Schlumberger Information Solutions (SIS). Shahram è specializzata in ASP.NET, servizi Web XML, tecnologie .NET, tecnologie XML, Grafica computer 3D, HI/Usability, Modelli di progettazione e sviluppo di controlli e componenti del server ASP.NET. Ha più di 10 anni di esperienza nella programmazione orientata agli oggetti. Usa diversi strumenti e tecnologie Microsoft, ad esempio SQL Server e ADO.NET. Shahram ha scritto articoli su .NET e tecnologie di ASP.NET per asp.netPRO magazine.