Compartir a través de


Escritura de código de acceso a datos genéricos en ASP.NET 2.0 y ADO.NET 2.0

 

Dr. Shahram Khosravi
Soluciones de información

Abril de 2005

Se aplica a:

   Microsoft ADO.NET 2.0
   Microsoft ASP.NET 2.0
   Microsoft .NET Framework 2.0
   Microsoft Visual Web Developer 2005 Express Edition Beta
   Lenguaje de programación de C#

Resumen: Use un enfoque paso a paso para aprender a usar diferentes herramientas y técnicas de ASP.NET 2.0 y ADO.NET 2.0 para escribir código de acceso a datos genéricos. (18 páginas impresas)

Descargue el código de ejemplo asociado: GenericDataAccessSample.exe.

Introducción

La mayoría de las aplicaciones web contienen código de acceso a datos para acceder al almacén de datos subyacente para realizar operaciones de datos básicas, como Seleccionar, Actualizar, Eliminary Insertar. En este artículo se usa un enfoque paso a paso para mostrar cómo los desarrolladores de páginas pueden aprovechar diferentes ASP.NET 2.0 y ADO.NET 2.0 herramientas y técnicas para escribir código de acceso a datos genéricos que se pueden usar para acceder a diferentes tipos de almacenes de datos. Escribir código de acceso a datos genéricos es especialmente importante en las aplicaciones web controladas por datos porque los datos proceden de muchos orígenes diferentes, incluidos Microsoft SQL Server, Oracle, documentos XML, archivos planos y servicios web, entre otros.

En este artículo se usa una aplicación web sencilla como una cama de prueba para todo el código que se presenta aquí. La aplicación consta de dos partes: la primera parte permite al administrador del sistema enviar boletines a todos los suscriptores de una lista de correo. La segunda parte permite a los usuarios suscribirse o cancelar la suscripción de una lista de correo. La primera parte del artículo comienza implementando un código de acceso a datos simple (consulte la figura 1) para acceder a Microsoft SQL Server y extraer la lista de suscriptores. El código se modifica y se convierte en más genérico durante el transcurso del artículo.

Figura 1. El método GetSubscribers extrae la lista de todos los suscriptores.

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;
}

Dado que el código de acceso a datos de la figura 1 contiene código para crear los objetos ADO.NET, como la SqlConnection, SqlCommandy instancias de SqlDataAdapter. El código de acceso a datos no se puede usar para recuperar la lista de suscriptores de otros almacenes de datos, como una base de datos de Oracle. Los desarrolladores de páginas tienen que modificar el código de acceso a datos (mediante el método GetSubscribers) cada vez que necesitan acceder a un nuevo almacén de datos. A continuación, vea cómo ADO.NET 2.0 usa el patrón de proveedor para ayudar a los desarrolladores a escribir código de acceso a datos genéricos para acceder a diferentes tipos de almacenes de datos.

Patrón de proveedor en ADO.NET 2.0

El principal problema con el método GetSubscribers es que contiene el código para crear los objetos ADO.NET. Según el patrón de proveedor, el código de acceso a datos debe delegar la responsabilidad de proporcionar el código para crear los objetos ADO.NET a otra clase. Me refiero a esta clase como la "clase de proveedor de código" porque proporciona el código para crear los objetos ADO.NET. La clase de proveedor de código expone métodos como CreateConnection, CreateCommandy CreateDataAdapter, donde cada método proporciona el código para crear el objeto ADO.NET correspondiente.

Dado que la clase de proveedor de código contiene el código real, no se puede usar la misma clase para acceder a diferentes almacenes de datos. Por lo tanto, el código de acceso a datos (el método GetSubscribers) debe modificarse y reconfigurarse para delegar la responsabilidad de proporcionar el código a una nueva clase de proveedor de código cada vez que se usa para acceder a un nuevo almacén de datos. El método GetSubscribers todavía está vinculado al código aunque no contenga el código.

El patrón de proveedor ofrece una solución a este problema y consta de los pasos siguientes:

  1. Diseñe e implemente una clase de proveedor base abstracta.

  2. Derive todas las clases de proveedor de código de la clase de proveedor base abstracta.

  3. Tener el código de acceso a datos (el método GetSubscribers) para usar la clase base abstracta en lugar de las clases de proveedor de código individuales.

    La clase base abstracta delega la responsabilidad de proporcionar el código para crear los objetos ADO.NET a la subclase adecuada. La clase base abstracta se denomina DbProviderFactory. A continuación se presentan algunos de los métodos de esta clase:

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

    Cada subclase proporciona el código para crear los objetos ADO.NET adecuados para un almacén de datos determinado. Por ejemplo, la subclase SqlClientFactory proporciona el código para crear los objetos ADO.NET para acceder a Microsoft SQL Server, como se muestra en la figura 2.

    Figura 2. La clase SqlClientFactory y algunos de sus métodos

    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();
           }
    }
    

El patrón de proveedor permite que el código de acceso a datos trate todas las subclases iguales porque todas son subclases de la misma clase base. En lo que respecta al código de acceso a datos, todas las subclases son de tipo DbProviderFactory. El código de acceso a datos no tiene forma de conocer el tipo específico de la subclase que se está usando. Esto introduce un nuevo problema. Si el código de acceso a datos (el método GetSubscribers) no conoce el tipo de subclase, ¿cómo puede crear instancias de una instancia de la subclase?

La solución del patrón de proveedor para este problema consta de las tres partes siguientes:

  1. Se usa una cadena única para identificar cada subclase. ADO.NET 2.0 usa el espacio de nombres de la subclase como identificador de cadena único. Por ejemplo, el identificador de cadena único System.Data.SqlClient y System.Data.OracleClient identificar SqlClientFactory y subclases OracleClientFactory, respectivamente.
  2. Un archivo de texto (normalmente un archivo XML) se usa para almacenar información sobre todas las subclases. ADO.NET 2.0 usa los archivos machine.config y web.config para almacenar la información necesaria. La información sobre una subclase contiene, entre otras cosas, el identificador de cadena único y el nombre del tipo de la subclase. Por ejemplo, la información sobre la subclase SqlClientFactory incluye el identificador de cadena único System.Data.SqlClient y el nombre del tipo de la subclase, es decir, System.Data.SqlClient.SqlClientFactory.
  3. Se ha diseñado e implementado un método estático. El método podría formar parte de la clase base abstracta o parte de una clase independiente. ADO.NET 2.0 usa una clase independiente denominada DbProviderFactories que expone el método estático GetFactor y. El método toma el identificador de cadena único de la subclase deseada como único argumento y busca en el archivo machine.config para una subclase con el identificador de cadena único especificado. El método extrae el nombre del tipo de la subclase deseada y usa la reflexión para crear dinámicamente una instancia de la subclase.

El código de acceso a datos (el método GetSubscribers) llama al método estático GetFactory y pasa el identificador de cadena único adecuado para acceder a la instancia de la subclase correspondiente. Una vez que el método GetSubscribers tiene acceso a la instancia, llama a los métodos de creación adecuados, como CreateConnection(), CreateCommand(), etc., para crear instancias de los objetos ADO.NET adecuados, como se muestra en la figura 3.

Figura 3. Versión del método GetSubscribers que usa el nuevo patrón de proveedor de 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;
}

El código de acceso a datos (el método GetSubscribers) delega la responsabilidad de proporcionar el código para crear los objetos ADO.NET a la instancia de clase de proveedor de código que el método GetFactory crea instancias y devuelve. Por lo tanto, se puede usar el mismo código de acceso a datos para acceder a diferentes almacenes de datos, como Microsoft SQL Server y Oracle.

El código para crear los objetos ADO.NET correctos es específico del almacén de datos. El patrón de proveedor de ADO.NET 2.0 quita estos elementos específicos del almacén de datos del código de acceso a datos (el método GetSubscribers) para que sea más genérico. Sin embargo, el patrón de proveedor no quita todas las partes específicas del almacén de datos. La inspección más detallada del método GetSubscribers revela las siguientes partes específicas del almacén de datos restantes:

  1. Cadena de conexión
  2. Identificador de cadena único que identifica la clase de proveedor de código subyacente
  3. Texto del comando
  4. Tipo de comando

A menos que se haga algo sobre las partes anteriores, el código de acceso a datos todavía está vinculado a un tipo determinado de almacén de datos. El patrón de proveedor de ADO.NET 2.0 no ayuda con este problema. Sin embargo, ADO.NET 2.0 nos proporciona otras herramientas y técnicas para quitar las dos primeras partes específicas del almacén de datos, como la cadena de conexión y el identificador de cadena único del método GetSubscribers.

Cadenas de conexión

Las cadenas de conexión son algunos de los recursos más valiosos de una aplicación web. Son tan importantes que .NET Framework 2.0 los trata como "ciudadanos de primera clase". El archivo web.config ahora admite una nueva sección denominada <connectionStrings> que contiene todas las cadenas de conexión usadas en una aplicación. Por lo tanto, moveremos la cadena de conexión del método GetSubscribers a esta sección:

<?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>

El <agregar> subelemento del <elemento connectionStrings> expone los tres atributos importantes siguientes:

  • Nombre: nombre descriptivo de la cadena de conexión
  • connectionString: la cadena de conexión real
  • providerName: identificador de cadena único o invariable de la clase de proveedor de código

NET Framework 2.0 proporciona el código de acceso a datos (el método GetSubscribers) con las herramientas adecuadas para extraer genéricamente el valor de la cadena de conexión del archivo web.config tal como se describe en lo siguiente. El espacio de nombres System.Configuration en .NET Framework 2.0 incluye una nueva clase denominada Configuration. Esta clase representa todo el contenido de un archivo web.config o machine.config. El código de acceso a datos no puede usar el nuevo operador para crear directamente una instancia de esta clase.

La propia clase expone un método estático denominado GetWebConfiguration que toma la ruta de acceso al archivo web.config y devuelve una instancia de la clase Configuration que representa todo el contenido del archivo web.config:

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

Una clase que hereda de la clase ConfigurationSection de representa cada sección del archivo web.config. El nombre de la clase consta del nombre de la sección más la palabra clave Section. Por ejemplo, la clase connectionStringsSection de representa el contenido de la sección connectionStrings de del archivo web.config. El código de acceso a datos (el método GetSubscribers) no puede usar el nuevo operador para crear directamente una instancia de la clase ConnectionStringsSection. La clase Configuration expone una propiedad de colección denominada Sections que contiene todos los objetos que representan secciones diferentes del archivo web.config:

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

Dado que Sections es una colección de objetos ConfigurationSection, el código de acceso a datos debe escribir la conversión de la instancia devuelta. Una vez que el método GetSubscribers tiene acceso al objeto ConnectionStringsSection y lo usa para acceder al valor de la cadena de conexión:

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

En la figura 4 se muestra la nueva versión del método GetSubscribers que contiene el código necesario para extraer la cadena de conexión de forma genérica.

Figura 4. Versión del método GetSubscribers que extrae la cadena de conexión del archivo 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;
}

El método GetSubscribers ahora incluye la cadena de MySqlConnectionString, por lo que todavía tenemos que modificar el método GetSubscribers para agregar compatibilidad con un almacén de datos diferente, como la base de datos de Oracle. Parecería que estamos de vuelta al cuadrado uno. La verdad es que no. Hemos obtenido algunas ventajas importantes moviendo la cadena de conexión del código de acceso a datos al archivo web.config:

  • La cadena de conexión es ahora el valor del atributo connectionString del <agregar> sub elemento del elemento connectionStrings del archivo web.config, que es un documento XML. Lo bueno de un documento XML es que podemos cifrar un solo elemento en el documento. No es necesario cifrar todo el documento si solo necesitamos proteger una pequeña parte de él. .NET Framework 2.0 incluye una herramienta que nos permite cifrar la sección de <connectionStrings> para proteger nuestro recurso más importante, las cadenas de conexión. Imagine cuánto daño puede hacer un hacker a nuestra base de datos valiosa si recibe su mano en nuestras cadenas de conexión. Recuerde que las cadenas de conexión son todas las necesidades de un hacker para acceder a nuestra base de datos.

  • Puede parecer que todo lo que hemos hecho es reemplazar la siguiente cadena

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

    con la nueva cadena, MySqlConnectionString. Sin embargo, hay una gran diferencia. La cadena anterior contiene la información específica de la base de datos de SQL Server que no se aplica a otra base de datos, como Oracle, pero la última cadena es solo un nombre descriptivo.

Sin embargo, el nombre descriptivo podría seguir causando problemas porque hace referencia a una cadena de conexión específica dentro de la sección connectionStrings de <> del archivo web.config. En nuestro ejemplo, hace referencia a la cadena de conexión que se usa para acceder a Microsoft SQL Server. Esto significa que el método GetSubscribers (el código de acceso a datos) debe modificarse para usar un nombre descriptivo diferente para acceder a un almacén de datos diferente, como Oracle.

Para evitar modificar el código de acceso a datos, podemos mover el nombre descriptivo del código de acceso a datos al <appSettings> sección del archivo de web.config y hacer que el código de acceso a datos lo extraiga dinámicamente en tiempo de ejecución de la siguiente manera:

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

También movemos el nombre del proveedor a la sección <appSettings>:

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

Los desarrolladores de páginas simplemente cambian el valor de atributo del <agregar> subelemento del elemento <appSettings> al mismo código de acceso a datos para acceder a un almacén de datos diferente sin realizar ningún cambio en el propio código de acceso a datos.

La figura 5 presenta la versión del código de acceso a datos (el método GetSubscribers) que contiene los cambios recientes.

Figura 5. Versión del método GetSubscribers para extraer el nombre del proveedor y el nombre descriptivo de la cadena de conexión del archivo 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;
}

Controles de origen de datos

La versión más reciente del método GetSubscribers, como se muestra en la figura 5, sigue sin ser genérica debido a los siguientes problemas:

  • El método contiene el almacén de datos( información específica, es decir, el texto del comando y el tipo de comando. Por lo tanto, los desarrolladores de páginas todavía tienen que modificar el método para poder usarlo para acceder a una base de datos diferente.
  • El método devuelve una instancia de la clase DataView a sus autores de llamada para que el método alimente sus datos tabulares de llamada. Podemos considerar esto como un contrato entre el método GetSubscribers y sus autores de llamada. Los autores de llamadas esperan que el método GetSubscribers respete el contrato en todas las circunstancias, incluso cuando el propio almacén de datos subyacente no sea tabular. Dado que el método GetSubscribers usa ADO.NET objetos para acceder al almacén de datos subyacente, no puede tener acceso a un almacén de datos jerárquico, como un archivo XML, donde las instancias de las clases de System.Xml y sus sub espacios de nombres deben usarse en lugar de ADO.NET objetos.

El principal problema con el código de acceso a datos usado en el método GetSubscribers es que contiene directamente el código real que extrae los datos del almacén de datos subyacente. Este es exactamente el tipo de problema que el patrón de proveedor de .NET Framework 2.0 está diseñado específicamente para resolver. Según el patrón de proveedor, el método GetSubscribers debe delegar la responsabilidad de proporcionar el código para acceder al almacén de datos a otra clase. Se denomina clase de proveedor de código. El tipo de clase de proveedor de código depende del tipo del almacén de datos al que accede. Estas clases de proveedor de código se conocen colectivamente como controles de origen de datos. ASP.NET 2.0 incluye varios tipos diferentes de controles de origen de datos, como SqlDataSource, AccessDataSource, ObjectDataSource, XmlDataSource y controles SiteMapDataSource.

El control SqlDataSource está diseñado específicamente para actualizar, eliminar, insertar y extraer datos de almacenes de datos relacionales, como Microsoft SQL Server y Oracle. El control AccessDataSource es una subclase del control SqlDataSource que sabe cómo trabajar con bases de datos de Microsoft Access. El control ObjectDataSource, por otro lado, usa objetos empresariales en memoria como almacén de datos. El control XmlDataSource está diseñado específicamente para extraer datos de documentos XML. Sin embargo, el control XmlDataSource no proporciona acceso de escritura al documento XML subyacente.

Cada control de origen de datos expone una o varias vistas de su almacén de datos subyacente. Cada vista es una instancia de una clase adecuada. Por ejemplo, los controles SqlDataSource, AccessDataSource y ObjectDataSource exponen vistas que son instancias de SqlDataSourceView, AccessDataSourceViewy clases objectDataSourceView, respectivamente. Las vistas ocultan el tipo real del almacén de datos subyacente y hacen que actúe como el tipo que espera el código de acceso a datos. Por ejemplo, el método GetSubscribers espera un tipo tabular de almacén de datos porque alimenta los datos tabulares de sus clientes. Las vistas tabulares permiten al método GetSubscribers extraer datos tabulares del almacén de datos subyacente incluso cuando el propio almacén de datos es un origen de datos jerárquico, como un documento XML. Esto permite al método GetSubscribers tratar los almacenes de datos tabulares y jerárquicos como almacenes de datos tabulares.

Los controles de origen de datos pueden proporcionar a sus clientes dos tipos de vistas: tabulares y jerárquicos. ASP.NET 2.0 incluye dos controles de origen de datos que proporcionan ambos tipos de vistas, XmlDataSource y SiteMapDataSource. El resto de los controles de origen de datos (SqlDataSource, AccessDataSource y ObjectDataSource) solo presentan vistas tabulares. Sin embargo, se pueden extender para proporcionar vistas tabulares y jerárquicas.

Controles de origen de datos tabulares

Un control de origen de datos tabulares hace que su almacén de datos subyacente actúe como un almacén de datos tabular, independientemente de si el almacén de datos es tabular o no. Un almacén de datos tabulares consta de tablas de filas y columnas donde cada fila representa un elemento de datos. El nombre de una tabla identifica y localiza de forma única la tabla entre otras tablas del almacén de datos tabulares. Una vista tabular actúa como una tabla, lo que significa que se denominan vistas.

Como se explicó antes, cada clase de control de origen de datos y su clase de vista asociada (por ejemplo, la clase SqlDataSource y su clase SqlDataSourceView asociada) proporcionan el código real para actualizar, eliminar, insertar y extraer datos del almacén de datos subyacente. Obviamente, el código de cada control de origen de datos y su clase de vista asociada está diseñado específicamente para trabajar con un tipo específico de almacén de datos. Por lo tanto, cada clase de control de origen de datos y su clase de vista asociada son específicas del almacén de datos. Esto supone un problema grave para el método GetSubscribers que usa controles de origen de datos y sus vistas tabulares para acceder a su almacén de datos subyacente porque vincula el método a un tipo específico de almacén de datos, lo que significa que no se puede usar el mismo método para acceder a diferentes tipos de almacenes de datos.

ASP.NET 2.0 ofrece una solución que usa el patrón de proveedor para:

  1. Presenta la interfaz IDataSource y clase abstracta DataSourceView
  2. Derivar todos los controles de origen de datos tabulares de la interfaz IDataSource
  3. Derivar todas las vistas tabulares de la clase abstracta DataSourceView

La interfaz IDataSource y la clase abstracta DataSourceView delegan la responsabilidad de proporcionar el código real para actualizar, eliminar, insertar y extraer los datos del almacén de datos a sus subclases adecuadas. El código de acceso a datos, como el método GetSubscribers, debe usar los métodos y propiedades de la interfaz IDataSource y la clase abstracta DataSourceView. No deben usar ningún método o propiedad específico de una clase de control de origen de datos determinada, como SqlDataSource o una clase de vista de origen de datos determinada, como SqlDataSourceView. El patrón de proveedor permite que el código de acceso a datos trate todos los controles de origen de datos y sus respectivas vistas de origen de datos de forma genérica. En lo que respecta al código de acceso a datos, todos los controles de origen de datos son de tipo IDataSource y todas las vistas del origen de datos son de tipo DataSourceView. El código de acceso a datos no tiene forma de saber el tipo real del control de origen de datos y el objeto de vista del origen de datos que se está usando. Esto provoca un nuevo problema. Si el código de acceso a datos (el método GetSubscribers) no conoce el tipo del control de origen de datos, ¿cómo puede crear una instancia de ella?

Como se ha descrito antes, el patrón de proveedor proporciona una solución que consta de los pasos siguientes:

  1. Se usa un identificador de cadena único para identificar cada clase de control de origen de datos.
  2. Un archivo de texto (normalmente un archivo XML) se usa para almacenar información sobre todas las clases de control de origen de datos.
  3. Un mecanismo está diseñado e implementado que busca en el archivo XML una subclase con un identificador de cadena determinado.

Ahora veamos cómo ASP.NET 2.0 implementa las tres tareas anteriores del patrón de proveedor. ASP.NET 2.0 deriva todos los controles de origen de datos de la clase Control de . ¿Por qué los controles de origen de datos derivan de la clase Control si no representan texto de marcado HTML? Derivan de la clase Control para que puedan heredar las tres características importantes siguientes:

  1. Se pueden crear instancias declarativamente.
  2. Guardan y restauran sus valores de propiedad en post backs.
  3. Se agregan al árbol de control de la página contenedora.

La primera característica permite a los desarrolladores de páginas crear instancias declarativamente de los controles de origen de datos en sus respectivos archivos de .aspx. Por lo tanto, el archivo .aspx actúa como texto o archivo XML que requiere el segundo paso del patrón de proveedor. La arquitectura de control ASP.NET 2.0 crea dinámicamente una instancia del control de origen de datos declarado y asigna la instancia a una variable cuyo nombre es el valor de la propiedad ID del control de origen de datos declarado. Esto se encarga de los pasos anteriores primero y tercer que requiere el patrón de proveedor.

En la figura 6 se muestra la versión del método GetSubscribers que usa los métodos y propiedades de la interfaz IDataSource y la clase abstracta DataSourceView para acceder al almacén de datos subyacente:

Figura 6. Versión del método GetSubscribers que usa los métodos y propiedades de la interfaz IDataSource y la clase abstracta 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 primera línea del método GetSubscribers muestra claramente que el método trata el control de origen de datos como un objeto de tipo IDataSource. El método no tiene y no debe preocuparse por el tipo real del control de origen de datos, como si es un control SqlDataSource, AccessDataSource, ObjectDataSource o XmlDataSource. Esto permitirá a los desarrolladores de páginas cambiar de un control de origen de datos a otro sin tener que modificar el código de acceso a datos (el método GetSubscribers). En la sección siguiente se describirá este problema importante en más detalles.

El método GetSubscribers llama al método GetView del objeto IDataSource para tener acceso a su objeto de vista tabular predeterminado. Observe que el método GetView devuelve un objeto de tipo DataSourceView. El método GetSubscribers no tiene y no debe preocuparse por el tipo real del objeto de vista, como si es un objeto SqlDataSourceView, AccessDataSourceView, ObjectDataSourceView o XmlDataSourceView.

A continuación, el método GetSubscribers crea una instancia de la clase DataSourceSelectArguments para solicitar operaciones adicionales, como insertar, paginar o recuperar el recuento total de filas en los datos que devuelve la operación Select . El método primero debe comprobar el valor de la CanInsert, CanPageo CanRetrieveTotalRowCount propiedad de la clase DataSourceView para asegurarse de que el objeto view admite la operación respectiva antes de realizar la solicitud.

Dado que la operación Select es asincrónica, el método GetSubscribers registra el método SendMail como devolución de llamada. El método Select llama automáticamente al método SendMail después de consultar los datos y pasa los datos como argumento, como se muestra en la figura 7.

Figura 7. El método SendMail enumera los datos y extrae la información necesaria.

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);
    }
}

El método SendMail llama al método GetEnumerator del objeto pasado como primer argumento para tener acceso a su objeto enumerador y usa el enumerador para enumerar los datos. El método SendMail usa el método Eval del clase dataBinder para extraer la dirección de correo electrónico, el nombre y el apellido de cada suscriptor, y envía la carta de noticias a cada uno.

Cambio de un control de origen de datos a otro

Como se explicó anteriormente, la arquitectura de control de ASP.NET crea dinámicamente una instancia del control de origen de datos que se declara en la página de .aspx correspondiente y la asigna a la variable cuyo nombre es el valor del identificador de propiedad del control de origen de datos declarado. Esta creación de instancias dinámicas del control de origen de datos declarado aísla el método GetSubscribers del tipo real del control de origen de datos y permite al método tratar todos los controles de origen de datos como objetos de tipo IDataSource. Esto permite a los desarrolladores de páginas cambiar de un tipo de control de origen de datos a otro sin modificar el código de acceso a datos (el método GetSubscribers). En esta sección se presenta un ejemplo para este tipo de caso.

Supongamos que nuestra aplicación web usa el método GetSubscribers junto con el control ObjectDataSource para recuperar la lista de suscriptores de un almacén de datos relacional, como Microsoft SQL Server:

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

Supongamos que nuestra aplicación web funciona en un entorno en el que la lista de suscriptores también puede provenir de un documento XML. El documento XML puede ser un archivo XML local o un recurso remoto al que se accede a través de la dirección URL. Por lo tanto, nuestra aplicación debe poder recuperar la lista de suscriptores de documentos XML. Obviamente, el control ObjectDataSource no está diseñado para recuperar datos tabulares de documentos XML, lo que significa que tenemos que usar un control de origen de datos que pueda recuperar datos tabulares de documentos XML, como el control XmlDataSource.

El método GetSubscribers no usa ninguna propiedad o método específico de las clases ObjectDataSource y ObjectDataSourceView, y solo usa los métodos y propiedades de la interfaz IDataSource y la clase abstracta DataSourceView para tratar los controles de origen de datos. Podemos cambiar fácilmente de ObjectDataSource al control XmlDataSource y usar el mismo código de acceso a datos que el método GetSubscribers para recuperar la lista de suscriptores:

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

El valor del atributo XPath del control XmlDataSource se establece en /Subscriber/Subscriber para seleccionar todos los suscriptores.

Operación de inserción y eliminación

Recuerde que nuestra aplicación web consta de dos partes. La segunda parte de la aplicación permite a los usuarios suscribirse o cancelar la suscripción de una lista de correo. Se llama al método Subscribe cuando se hace clic en el botón Suscribir , como se muestra en la figura 8.

Figura 8. Se llama al método Subscribe cuando se hace clic en el botón Suscribir.

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 primera línea del método Subscribe muestra que el método no se preocupa por el tipo real del control de origen de datos. Por lo tanto, podemos cambiar a otro control de origen de datos para admitir un nuevo almacén de datos sin tener que cambiar el código en el método Subscribe.

El método usa una instancia de la clase KeyedList para recopilar el correo electrónico, el nombre y el apellido del suscriptor. No es necesario usar la clase KeyedList. Podemos usar cualquier clase que implemente la interfaz de IDictionary, incluida ArrayList, KeyedList, etc.

El método Subscribe comprueba el valor de la propiedad CanInsert del objeto de vista del origen de datos para asegurarse de que el objeto view admite la operación insert antes de llamar al método Insert. El método Subscribe pasa la instancia keyedList como primer argumento al método Insert.

El método Cancelar suscripción funciona de forma similar al método Subscribe. La principal diferencia es que el método Unsubscribe llama al método Delete del objeto de vista correspondiente para quitar la suscripción del almacén de datos subyacente, como se muestra en la figura 9.

Figura 9. Se llama al método Unsubscribe cuando se hace clic en el botón Cancelar suscripción.

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);
}

Controles jerárquicos de origen de datos

El método GetSubscribers (el código de acceso a datos) alimenta los datos tabulares a sus autores de llamada. Sin embargo, hay ocasiones en las que el código de acceso a datos debe devolver datos jerárquicos a sus autores de llamada. En esta sección se presenta la implementación de la versión del método GetSubscribers que devuelve datos jerárquicos. Podemos considerar esto como un contrato entre el método y sus llamadores. Los autores de llamadas esperan que el método devuelva datos jerárquicos de almacenes de datos jerárquicos y tabulares.

ASP.NET 2.0 usa el patrón de proveedor para aislar el método GetSubscribers del tipo real del almacén de datos subyacente y presenta el método con las vistas jerárquicas del almacén de datos. Esto permite que el método trate almacenes de datos jerárquicos y tabulares como almacenes de datos jerárquicos.

Cada control de origen de datos jerárquico está diseñado específicamente para trabajar con un almacén de datos específico. Sin embargo, dado que todos los controles de origen de datos jerárquicos implementan la interfaz IHierarchicalDataSource y todas las vistas de origen de datos jerárquicas derivan de la clase HierarchDataSourceView , el método GetSubscribers no tiene que tratar con los detalles de cada control de origen de datos y puede tratar todos ellos de forma genérica.

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

La primera línea del método GetSubscribers muestra que el método trata el control de origen de datos como un objeto de tipo IHierarchicalDataSource y no le importa el tipo real del control de origen de datos. Esto permitirá a los desarrolladores de páginas cambiar a un nuevo control de origen de datos jerárquico para agregar compatibilidad con un nuevo almacén de datos sin tener que modificar el código en el método GetSubscribers.

A continuación, el método GetSubscribers llama al método GetHierarchicalView de la clase HierarchDataSourceView para acceder a la vista jerárquica con la ruta de acceso especificada, como "/Subscribers". Observe que el método Select no es asincrónico. La aplicación pasa los datos devueltos desde el método GetSubscribers al método SendMail (vea la figura 15). Observe que los datos son de tipo IHierarchicalEnumerable.

IHierarchicalEnumerable implementa IEnumerable, lo que significa que expone el método GetEnumerator. El método SendMail llama al método GetEnumerator para tener acceso al objeto IEnumerator correspondiente, que se usa posteriormente para enumerar los datos. IHierarchicalEnumerable también expone un método denominado GetHierarchyData que toma el objeto enumerado y devuelve el objeto IHierarchyData asociado.

La interfaz IHierarchyData expone una propiedad importante denominada Item, que no es más que el elemento de datos. El método SendMail usa el método Eval de la clase XPathBinder para evaluar expresiones XPath en el objeto Item.

Figura 10. El método SendMail enumera los datos, extrae la información necesaria y envía el boletín a cada suscriptor.

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);
    }
}

Conclusión

Con un enfoque paso a paso que muestra diferentes herramientas y técnicas de ASP.NET 2.0 y ADO.NET 2.0, en este artículo se muestra cómo los desarrolladores de páginas pueden escribir código de acceso a datos genéricos que se pueden usar para acceder a diferentes tipos de almacenes de datos.

Dr. Shahram Khosravies ingeniero de software senior con Schlumberger Information Solutions (SIS). Shahram se especializa en ASP.NET, servicios web XML, tecnologías .NET, tecnologías XML, gráficos de equipos 3D, HI/facilidad de uso, patrones de diseño y desarrollo de controles y componentes de servidor ASP.NET. Tiene más de 10 años de experiencia en programación orientada a objetos. Usa diversas herramientas y tecnologías de Microsoft, como SQL Server y ADO.NET. Shahram ha escrito artículos sobre .NET y tecnologías de ASP.NET para la revista asp.netPRO.