Condividi tramite


Scrittura di codice di accesso ai dati generici 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 strumenti ADO.NET e tecniche di accesso ai dati generici ASP.NET 2.0 e 2.0. (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, Deletee 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 generici è 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 lista di distribuzione. La seconda parte consente agli utenti di sottoscrivere o annullare la sottoscrizione a una lista di distribuzione. 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 codice per la creazione degli oggetti ADO.NET, ad esempio SqlConnection, SqlCommande istanze di 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 di provider, il codice di accesso ai dati deve delegare la responsabilità di fornire il codice per la creazione degli 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, CreateCommande CreateDataAdapter, dove 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. Di conseguenza, il codice di accesso ai dati (metodo GetSubscribers) deve essere modificato e riconfigurato per delegare 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. Disporre del codice di accesso ai dati (metodo GetSubscribers) per usare 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 sono illustrati 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 archivio dati specifico. 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 ha modo 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. Una stringa univoca viene usata per identificare ogni sottoclasse. ADO.NET 2.0 usa lo spazio dei nomi della sottoclasse come ID stringa univoco. Ad esempio, l'ID stringa univoco System.Data.SqlClient e System.Data.OracleClient identificare rispettivamente SqlClientFactory e OracleClientFactory sottoclassi.
  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 le altre cose, 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 Get Factory. 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 creata e restituita dal metodo GetFactory. Di conseguenza, è possibile usare lo stesso codice di accesso ai dati 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 di provider in ADO.NET 2.0 non è utile per questo problema. Tuttavia, ADO.NET 2.0 fornisce 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 <aggiungere> sottoelemento 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 di 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 file di web.config o 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 più la 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 di 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. Non proprio. Sono stati ottenuti alcuni vantaggi importanti spostando la stringa di connessione dal codice di accesso ai dati al file di web.config:

  • La stringa di connessione è ora il valore dell'attributo connectionString del <aggiungere> sottoelemento dell'elemento connectionStrings del file web.config, ovvero un documento XML. La cosa 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.

  • Può 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 stringa precedente contiene le informazioni specifiche del database di 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 di web.config. In questo esempio si riferisce 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 di web.config e avere il codice di accesso ai dati estrarlo in modo dinamico in runtime come indicato di seguito:

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

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

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

Gli sviluppatori di pagine modificano semplicemente il valore attributo del <aggiungere> sottoelemento 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 l'archivio dati, ovvero 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. Possiamo pensare a questo come un contratto tra il metodo GetSubscribers e i relativi 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 oggetti ADO.NET 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 in System.Xml e i relativi spazi dei nomi secondari devono essere usati anziché ADO.NET oggetti.

Il problema principale del 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 è progettato specificamente per risolvere. In base al modello di 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, tra cui SqlDataSource, AccessDataSource, ObjectDataSource, XmlDataSourcee controlli 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, invece, 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 viste che sono istanze di SqlDataSourceView, AccessDataSourceViewe classi 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. Ad esempio, il metodo GetSubscribers 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 gli archivi dati tabulari e gerarchici come archivi dati tabulari.

I controlli origine dati possono fornire ai client due tipi di visualizzazioni: 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 funge da tabella, il che significa che le viste sono denominate.

Come descritto in precedenza, ogni classe di 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 specificamente 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 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 di controllo origine dati, ad esempio SqlDataSource o una determinata classe di vista origine dati, ad esempio SqlDataSourceView. Il modello di provider consente al codice di accesso ai dati di trattare 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 ha modo 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. Viene usato un ID stringa univoco per identificare ogni classe del controllo origine dati.
  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 nel file XML una sottoclasse 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 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. Pertanto, 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 preoccuparsi del tipo reale del controllo origine dati, ad esempio se si tratta di un controllo SqlDataSource, AccessDataSource, ObjectDataSource o XmlDataSource. Ciò consentirà agli sviluppatori di pagine di passare da un controllo origine dati a un altro senza dover modificare il codice di accesso ai dati (metodo GetSubscribers). La sezione successiva illustrerà questo importante problema in modo più dettagliato.

Il metodo GetSubscribers chiama il metodo GetView dell'oggetto IDataSource per accedere all'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 si tratta di un oggetto SqlDataSourceView, AccessDataSourceView, ObjectDataSourceView o XmlDataSourceView.

Il metodo GetSubscribers crea quindi 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, CanPageo CanRetrieveTotalRowCount della classe DataSourceView per assicurarsi che l'oggetto view supporti l'operazione corrispondente 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 che esegue una 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 al relativo oggetto enumeratore e usa l'enumeratore per enumerare i dati. Il metodo SendMail utilizza 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 del 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 dell'ID proprietà del controllo origine dati dichiarato. Tale creazione dinamica di un'istanza del controllo origine dati dichiarato 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 un caso di questo tipo.

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 provenire anche 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 dai documenti XML. Ovviamente il controllo ObjectDataSource non è progettato per recuperare dati tabulari da documenti XML, il che significa che è necessario utilizzare un controllo origine dati in grado di recuperare dati tabulari da documenti XML, ad esempio il controllo XmlDataSource.

Il metodo GetSubscribers non usa proprietà o metodi specifici delle classi ObjectDataSource e ObjectDataSourceView e usa solo i metodi e le proprietà dell'interfaccia IDataSource e della classe astratta DataSourceView per gestire i controlli origine dati. È possibile passare facilmente da 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 /Subscribers/Subscriber 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 una lista di distribuzione. Il metodo Subscribe viene chiamato quando si fa clic sul pulsante Subscribe Subscribe , come illustrato nella figura 8.

Figura 8. Il metodo Subscribe viene chiamato quando si fa clic sul pulsante Subscribe.

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 indica che il metodo non è interessato al 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 utilizza un'istanza della classe KeyedList per raccogliere il messaggio di posta elettronica, 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 annulla sottoscrizione funziona in modo simile al metodo Subscribe. La differenza principale è che il metodo Unsubscribe 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 Unsubscribe viene chiamato quando si fa 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) inserisce i dati tabulari ai chiamanti. Tuttavia, in alcuni casi, 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 un contratto tra il metodo e i relativi chiamanti. I chiamanti prevedono che il metodo restituisca dati gerarchici da archivi dati gerarchici e tabulari.

ASP.NET 2.0 usa il modello di provider per isolare il metodo GetSubscribers dal tipo effettivo dell'archivio dati sottostante e presenta il metodo con le visualizzazioni gerarchiche dell'archivio dati. In questo modo il metodo può trattare gli archivi dati gerarchici e tabulari come archivi dati gerarchici.

Ogni controllo origine dati gerarchico è progettato specificamente per l'uso con un archivio dati specifico. Tuttavia, poiché tutti i controlli origine dati gerarchici implementano l'interfaccia IHierarchicalDataSource e tutte le viste origine dati gerarchiche derivano dalla classe HierarchicalDataSourceView, il metodo GetSubscribers non deve gestire le specifiche di ogni controllo origine dati e può trattarle 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 preoccupa 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 "/Subscribers". 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, il che significa che espone il metodo GetEnumerator. Il metodo SendMail chiama il metodo GetEnumerator per accedere al rispettivo oggetto IEnumerator, che viene successivamente utilizzato 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 illustra diversi ASP.NET 2.0 e ADO.NET 2.0 strumenti e tecniche, questo articolo illustra come gli sviluppatori di pagine possono scrivere codice di accesso ai dati generico che può essere usato per accedere a diversi tipi di archivi dati.

Dr. Shahram Khosraviè senior software engineer con Schlumberger Information Solutions (SIS). Shahram è specializzata in ASP.NET, servizi Web XML, tecnologie .NET, tecnologie XML, grafica computer 3D, HI/Usabilità, Modelli di progettazione e sviluppo di controlli server e componenti 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 sulle tecnologie .NET e ASP.NET per asp.netPRO magazine.