Escrevendo código de acesso a dados genéricos no ASP.NET 2.0 e ADO.NET 2.0
Dr. Shahram Khosravi
Soluções de informações
Abril de 2005
Aplica-se a:
Microsoft ADO.NET 2.0
Microsoft ASP.NET 2.0
Microsoft .NET Framework 2.0
Microsoft Visual Web Developer 2005 Express Edition Beta
Linguagem de programação em C#
Resumo: Use uma abordagem passo a passo para aprender a usar diferentes ferramentas e técnicas do ASP.NET 2.0 e ADO.NET 2.0 para escrever código de acesso a dados genérico. (18 páginas impressas)
Baixe o código de exemplo associado: GenericDataAccessSample.exe.
Introdução
A maioria dos aplicativos Web contém código de acesso a dados para acessar o armazenamento de dados subjacente para executar operações básicas de dados, como Selecionar, Atualizar, Excluir e Inserir. Este artigo usa uma abordagem passo a passo para mostrar como os desenvolvedores de páginas podem aproveitar diferentes ASP.NET 2.0 e ADO.NET 2.0 ferramentas e técnicas para escrever código de acesso a dados genérico que pode ser usado para acessar diferentes tipos de armazenamentos de dados. Escrever código de acesso a dados genéricos é especialmente importante em aplicativos Web controlados por dados porque os dados vêm de várias fontes diferentes, incluindo Microsoft SQL Server, Oracle, documentos XML, arquivos simples e serviços Web, apenas para citar alguns.
Este artigo usa um aplicativo Web simples como uma cama de teste para todo o código apresentado aqui. O aplicativo consiste em duas partes: a primeira parte permite que o administrador do sistema envie boletins informativos para todos os assinantes de uma lista de endereçamento. A segunda parte permite que os usuários assinem ou cancelem a assinatura de uma lista de endereçamento. A primeira parte do artigo começa implementando um código de acesso a dados simples (consulte a Figura 1) para acessar o Microsoft SQL Server e extrair a lista de assinantes. O código é modificado e tornado mais genérico ao longo do artigo.
Figura 1. O método GetSubscribers extrai a lista de todos os assinantes.
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;
}
Como o código de acesso a dados na Figura 1 contém código para criar objetos ADO.NET, como as instâncias SqlConnection, SqlCommand e SqlDataAdapter . O código de acesso a dados não pode ser usado para recuperar a lista de assinantes de outros armazenamentos de dados, como um banco de dados Oracle. Os desenvolvedores de página precisam modificar o código de acesso a dados (usando o método GetSubscribers ) sempre que precisarem acessar um novo armazenamento de dados. Em seguida, veja como o ADO.NET 2.0 usa o padrão de provedor para ajudar os desenvolvedores de páginas a escrever código de acesso a dados genéricos para acessar diferentes tipos de armazenamentos de dados.
Padrão de provedor no ADO.NET 2.0
O main problema com o método GetSubscribers é que ele contém o código para criar os objetos ADO.NET. De acordo com o padrão do provedor, o código de acesso a dados deve delegar a responsabilidade de fornecer o código para criar os objetos ADO.NET para outra classe. Refiro-me a essa classe como a "classe de provedor de código" porque ela fornece o código para criar os objetos ADO.NET. A classe de provedor de código expõe métodos como CreateConnection, CreateCommand e CreateDataAdapter, em que cada método fornece o código para criar o objeto ADO.NET correspondente.
Como a classe de provedor de código contém o código real, a mesma classe não pode ser usada para acessar armazenamentos de dados diferentes. Portanto, o código de acesso a dados (o método GetSubscribers) precisa ser modificado e reconfigurado para delegar a responsabilidade de fornecer o código a uma nova classe de provedor de código sempre que for usado para acessar um novo armazenamento de dados. O método GetSubscribers ainda está vinculado ao código, embora não contenha o código.
O padrão de provedor oferece uma solução para esse problema e consiste nas seguintes etapas:
Projetar e implementar uma classe de provedor base abstrata.
Derive todas as classes de provedor de código da classe de provedor base abstrata.
Tenha o código de acesso a dados (o método GetSubscribers) para usar a classe base abstrata em vez das classes de provedor de código individuais.
A classe base abstrata delega a responsabilidade de fornecer o código para criar os objetos ADO.NET para a subclasse apropriada. A classe base abstrata é chamada DbProviderFactory. O seguinte apresenta alguns dos métodos desta classe:
public abstract class DbProviderFactory { public virtual DbConnection CreateConnection(); public virtual DbCommand CreateCommand(); public virtual DbDataAdapter CreateDataAdapter(); }
Cada subclasse fornece o código para criar os objetos ADO.NET apropriados para um armazenamento de dados específico. Por exemplo, a subclasse SqlClientFactory fornece o código para criar os objetos ADO.NET para acessar o Microsoft SQL Server, conforme mostrado na Figura 2.
Figura 2. A classe SqlClientFactory e alguns de seus 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(); } }
O padrão do provedor permite que o código de acesso a dados trate todas as subclasses da mesma forma porque todas elas são subclasses da mesma classe base. No que diz respeito ao código de acesso a dados, todas as subclasses são do tipo DbProviderFactory. O código de acesso a dados não tem como saber o tipo específico da subclasse que está sendo usada. Isso introduz um novo problema. Se o código de acesso a dados (o método GetSubscribers) não souber o tipo de subclasse, como ele pode criar uma instância da subclasse?
A solução padrão do provedor para esse problema consiste nas três partes a seguir:
- Uma cadeia de caracteres exclusiva é usada para identificar cada subclasse. ADO.NET 2.0 usa o namespace da subclasse como sua ID de cadeia de caracteres exclusiva. Por exemplo, as subclasses System.Data.SqlClient e System.Data.OracleClient da ID de cadeia de caracteres exclusiva identificam as subclasses SqlClientFactory e OracleClientFactory , respectivamente.
- Um arquivo de texto (normalmente um arquivo XML) é usado para armazenar informações sobre todas as subclasses. ADO.NET 2.0 usa os arquivos machine.config e web.config para armazenar as informações necessárias. As informações sobre uma subclasse contêm, entre outras coisas, a ID de cadeia de caracteres exclusiva e o nome do tipo da subclasse. Por exemplo, as informações sobre a subclasse SqlClientFactory incluem a ID de cadeia de caracteres exclusiva System.Data.SqlClient e o nome do tipo da subclasse, ou seja, System.Data.SqlClient.SqlClientFactory.
- Um método estático foi projetado e implementado. O método pode fazer parte da classe base abstrata ou parte de uma classe separada. ADO.NET 2.0 usa uma classe separada chamada DbProviderFactories que expõe o método estático GetFactory . O método usa a ID de cadeia de caracteres exclusiva da subclasse desejada como seu único argumento e pesquisa no arquivo machine.config para uma subclasse com a ID de cadeia de caracteres exclusiva fornecida. O método extrai o nome do tipo da subclasse desejada e usa reflexão para criar dinamicamente uma instância da subclasse.
O código de acesso a dados (o método GetSubscribers) chama o método estático GetFactory e passa a ID de cadeia de caracteres exclusiva apropriada para acessar a instância da subclasse correspondente. Depois que o método GetSubscribers acessa a instância, ele chama os métodos de criação apropriados, como CreateConnection(), CreateCommand(), etc., para instanciar os objetos ADO.NET apropriados, conforme mostrado na Figura 3.
Figura 3. A versão do método GetSubscribers que usa o novo padrão de provedor 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;
}
O código de acesso a dados (o método GetSubscribers) delega a responsabilidade de fornecer o código para criar os objetos ADO.NET para a instância de classe do provedor de código que o método GetFactory instancia e retorna. Portanto, o mesmo código de acesso a dados pode ser usado para acessar diferentes armazenamentos de dados, como Microsoft SQL Server e Oracle.
O código para criar os objetos ADO.NET corretos é específico do armazenamento de dados. O padrão de provedor no ADO.NET 2.0 remove essas partes específicas do armazenamento de dados do código de acesso a dados (o método GetSubscribers) para torná-lo mais genérico. No entanto, o padrão do provedor não remove todas as partes específicas do armazenamento de dados. Uma inspeção mais detalhada do método GetSubscribers revela as seguintes partes específicas do armazenamento de dados restantes:
- Cadeia de conexão
- ID de cadeia de caracteres exclusiva que identifica a classe de provedor de código subjacente
- Texto do comando
- Tipo de comando
A menos que algo seja feito sobre as partes acima, o código de acesso a dados ainda está vinculado a um tipo específico de armazenamento de dados. O padrão de provedor no ADO.NET 2.0 não ajuda nesse problema. No entanto, ADO.NET 2.0 nos fornece outras ferramentas e técnicas para remover as duas primeiras partes específicas do armazenamento de dados, como a cadeia de conexão e a ID de cadeia de caracteres exclusiva do método GetSubscribers.
Cadeias de Conexão
As cadeias de conexão são alguns dos recursos mais valiosos em um aplicativo Web. Eles são tão importantes que o .NET Framework 2.0 os trata como "cidadãos de primeira classe". O arquivo web.config agora dá suporte a uma nova seção chamada <connectionStrings> que contém todas as cadeias de conexão usadas em um aplicativo. Portanto, moveremos a cadeia de conexão do método GetSubscribers para esta seção:
<?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>
O <subelemento add> do <elemento connectionStrings> expõe os três atributos importantes a seguir:
- Nome – o nome amigável da cadeia de conexão
- connectionString — A cadeia de conexão real
- providerName — A ID de cadeia de caracteres exclusiva ou invariável da classe de provedor de código
O NET Framework 2.0 fornece o código de acesso a dados (o método GetSubscribers) com as ferramentas certas para extrair genericamente o valor da cadeia de conexão do arquivo web.config, conforme descrito no seguinte. O namespace System.Configuration no .NET Framework 2.0 inclui uma nova classe chamada Configuração. Essa classe representa todo o conteúdo de um arquivo web.config ou machine.config. O código de acesso a dados não pode usar o novo operador para criar diretamente uma instância dessa classe.
A própria classe expõe um método estático chamado GetWebConfiguration que usa o caminho para o arquivo web.config e retorna uma instância da classe Configuration que representa todo o conteúdo do arquivo web.config:
Configuration configuration = Configuration.GetWebConfiguration("~/");
Uma classe que herda da classe ConfigurationSection representa cada seção do arquivo web.config. O nome da classe consiste no nome da seção mais a Seção palavra-chave. Por exemplo, a classe ConnectionStringsSection representa o conteúdo da <seção connectionStrings> do arquivo web.config. O código de acesso a dados (o método GetSubscribers) não pode usar o novo operador para criar diretamente uma instância da classe ConnectionStringsSection. A classe Configuration expõe uma propriedade de coleção chamada Sections que contém todos os objetos que representam diferentes seções do arquivo web.config:
ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
Como Sections é uma coleção de objetos ConfigurationSection, o código de acesso a dados deve digitar converter a instância retornada. Depois que o método GetSubscribers acessa o objeto ConnectionStringsSection e o usa para acessar o valor da cadeia de conexão:
string connectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;
A Figura 4 mostra a nova versão do método GetSubscribers que contém o código necessário para extrair a cadeia de conexão de forma genérica.
Figura 4. A versão do método GetSubscribers que extrai a cadeia de conexão do arquivo 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;
}
O método GetSubscribers agora inclui a cadeia de caracteres MySqlConnectionString , portanto, ainda precisamos modificar o método GetSubscribers para adicionar suporte a um repositório de dados diferente, como o banco de dados Oracle. Parece que estamos de volta à estaca zero. Não exatamente. Obtemos alguns benefícios importantes movendo a cadeia de conexão do código de acesso a dados para o arquivo web.config:
A cadeia de conexão agora é o valor do atributo connectionString do <sub elemento add> do elemento connectionStrings do arquivo web.config, que é um documento XML. A grande coisa sobre um documento XML é que podemos criptografar um único elemento no documento. Não precisamos criptografar todo o documento se precisarmos proteger apenas uma pequena parte dele. O .NET Framework 2.0 vem com uma ferramenta que nos permite criptografar a <seção connectionStrings> para proteger nosso recurso mais importante, as cadeias de conexão. Imagine quanto dano um hacker pode causar ao nosso valioso banco de dados se ele colocar a mão em nossas cadeias de conexão. Lembre-se de que as cadeias de conexão são tudo o que um hacker precisa para acessar nosso banco de dados.
Pode parecer que tudo o que fizemos foi substituir a cadeia de caracteres a seguir
"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
com a nova cadeia de caracteres, MySqlConnectionString. No entanto, há uma grande diferença. A cadeia de caracteres anterior contém as informações específicas do banco de dados SQL Server que não se aplicam a outro banco de dados, como Oracle, mas a última cadeia de caracteres é apenas um nome amigável.
No entanto, o nome amigável ainda pode causar problemas porque se refere a uma cadeia de conexão específica na <seção connectionStrings> do arquivo web.config. Em nosso exemplo, ele se refere à cadeia de conexão usada para acessar o Microsoft SQL Server. Isso significa que o método GetSubscribers (o código de acesso a dados) precisa ser modificado para usar um nome amigável diferente para acessar um repositório de dados diferente, como o Oracle.
Para evitar modificar o código de acesso a dados, podemos mover o nome amigável do código de acesso a dados para a <seção appSettings> do arquivo web.config e fazer com que o código de acesso a dados o extraia dinamicamente em runtime da seguinte maneira:
string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
Também movemos o nome do provedor para a <seção appSettings> :
string providerName = ConfigurationSettings.AppSettings["ProviderName"];
Os desenvolvedores de página simplesmente alteram o atributo de valor do <subelemento add> do <elemento appSettings> para o mesmo código de acesso a dados para acessar um armazenamento de dados diferente sem fazer nenhuma alteração no próprio código de acesso a dados.
A Figura 5 apresenta a versão do código de acesso a dados (o método GetSubscribers) que contém as alterações recentes.
Figura 5. A versão do método GetSubscribers para extrair o nome do provedor e o nome amigável da cadeia de conexão do arquivo 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 fonte de dados
A versão mais recente do método GetSubscribers, conforme mostrado na Figura 5, ainda não é genérica devido aos seguintes problemas:
- O método contém o armazenamento de dados — informações específicas, ou seja, o texto do comando e o tipo de comando. Portanto, os desenvolvedores de página ainda precisam modificar o método antes de poderem usá-lo para acessar um banco de dados diferente.
- O método retorna uma instância da classe DataView para seus chamadores para que o método alimente os dados tabulares dos chamadores. Podemos pensar nisso como um contrato entre o método GetSubscribers e seus chamadores. Os chamadores esperam que o método GetSubscribers honre o contrato em todas as circunstâncias, mesmo quando o próprio armazenamento de dados subjacente não é tabular. Como o método GetSubscribers usa objetos ADO.NET para acessar o armazenamento de dados subjacente, ele não pode acessar um armazenamento de dados hierárquico, como um arquivo XML, em que instâncias das classes no System.Xml e seus sub namespaces devem ser usadas em vez de objetos ADO.NET.
O main problema com o código de acesso a dados usado no método GetSubscribers é que ele contém diretamente o código real que extrai os dados do armazenamento de dados subjacente. Esse é exatamente o tipo de problema que o padrão de provedor .NET Framework 2.0 foi projetado especificamente para resolver. De acordo com o padrão do provedor, o método GetSubscribers deve delegar a responsabilidade de fornecer o código para acessar o armazenamento de dados a uma classe diferente. Ela é chamada de classe de provedor de código. O tipo de classe de provedor de código depende do tipo do armazenamento de dados que ele acessa. Essas classes de provedor de código são coletivamente conhecidas como controles de fonte de dados. ASP.NET 2.0 vem com vários tipos diferentes de controles de fonte de dados, incluindo os controles SqlDataSource, AccessDataSource, ObjectDataSource, XmlDataSource e SiteMapDataSource .
O controle SqlDataSource foi projetado especificamente para atualizar, excluir, inserir e extrair dados de armazenamentos de dados relacionais, como Microsoft SQL Server e Oracle. O controle AccessDataSource é uma subclasse do controle SqlDataSource que sabe como trabalhar com bancos de dados do Microsoft Access. O controle ObjectDataSource, por outro lado, usa objetos de negócios na memória como seu armazenamento de dados. O controle XmlDataSource foi projetado especificamente para extrair dados de documentos XML. No entanto, o controle XmlDataSource não fornece acesso de gravação ao documento XML subjacente.
Cada controle de fonte de dados expõe uma ou mais exibições de seu armazenamento de dados subjacente. Cada exibição é uma instância de uma classe apropriada. Por exemplo, os controles SqlDataSource, AccessDataSource e ObjectDataSource expõem exibições que são instâncias das classes SqlDataSourceView, AccessDataSourceView e ObjectDataSourceView , respectivamente. As exibições ocultam o tipo real do armazenamento de dados subjacente e o fazem agir como o tipo esperado pelo código de acesso a dados. Por exemplo, o método GetSubscribers espera um tipo tabular de armazenamento de dados porque alimenta os dados tabulares de seus clientes. As exibições tabulares permitem que o método GetSubscribers extraia dados tabulares do armazenamento de dados subjacente mesmo quando o próprio armazenamento de dados é uma fonte de dados hierárquica, como um documento XML. Isso permite que o método GetSubscribers trate os armazenamentos de dados tabulares e hierárquicos como armazenamentos de dados tabulares.
Os controles de fonte de dados podem fornecer a seus clientes dois tipos de exibições: tabular e hierárquico. ASP.NET 2.0 vem com dois controles de fonte de dados que fornecem ambos os tipos de exibições, XmlDataSource e SiteMapDataSource. O restante dos controles de fonte de dados — SqlDataSource, AccessDataSource e ObjectDataSource — só apresentam exibições tabulares. No entanto, eles podem ser estendidos para fornecer exibições tabulares e hierárquicas.
Controles de fonte de dados tabulares
Um controle de fonte de dados tabular faz com que seu armazenamento de dados subjacente aja como um armazenamento de dados tabular, independentemente de o armazenamento de dados ser tabular ou não. Um armazenamento de dados tabular consiste em tabelas de linhas e colunas em que cada linha representa um item de dados. O nome de uma tabela identifica e localiza exclusivamente a tabela entre outras tabelas no armazenamento de dados tabular. Uma exibição tabular atua como uma tabela, o que significa que os modos de exibição são nomeados.
Conforme discutido antes, cada classe de controle da fonte de dados e sua classe de exibição associada (por exemplo, a classe SqlDataSource e sua classe SqlDataSourceView associada) fornecem o código real para atualizar, excluir, inserir e extrair dados do armazenamento de dados subjacente. Obviamente, o código para cada controle de fonte de dados e sua classe de exibição associada foi projetado especificamente para trabalhar com um tipo específico de armazenamento de dados. Portanto, cada classe de controle da fonte de dados e sua classe de exibição associada são específicas do armazenamento de dados. Isso representa um problema sério para o método GetSubscribers que usa controles de fonte de dados e suas exibições tabulares para acessar seu armazenamento de dados subjacente porque vincula o método a um tipo específico de armazenamento de dados, o que significa que o mesmo método não pode ser usado para acessar diferentes tipos de armazenamentos de dados.
ASP.NET 2.0 oferece uma solução que usa o padrão de provedor para:
- Introduzir a interface IDataSource e a classe abstrata DataSourceView
- Derivar todos os controles de fonte de dados tabulares da interface IDataSource
- Derivar todas as exibições tabulares da classe abstrata DataSourceView
A interface IDataSource e a classe abstrata DataSourceView delegam a responsabilidade de fornecer o código real para atualizar, excluir, inserir e extrair os dados do armazenamento de dados para suas subclasses apropriadas. O código de acesso a dados, como o método GetSubscribers, deve usar os métodos e as propriedades da interface IDataSource e da classe abstrata DataSourceView. Eles não devem usar nenhum método ou propriedade específica para uma classe de controle de fonte de dados específica, como SqlDataSource ou uma classe de exibição de fonte de dados específica, como SqlDataSourceView. O padrão do provedor permite que o código de acesso a dados trate todos os controles de fonte de dados e suas respectivas exibições de fonte de dados de forma genérica. No que diz respeito ao código de acesso a dados, todos os controles de fonte de dados são do tipo IDataSource e todas as exibições da fonte de dados são do tipo DataSourceView. O código de acesso a dados não tem como saber o tipo real do controle da fonte de dados e do objeto de exibição da fonte de dados que está sendo usado. Isso causa um novo problema. Se o código de acesso a dados (o método GetSubscribers) não souber o tipo do controle da fonte de dados, como ele pode instanciar uma instância dele?
Conforme discutido anteriormente, o padrão do provedor fornece uma solução que consiste nas seguintes etapas:
- Uma ID de cadeia de caracteres exclusiva é usada para identificar cada classe de controle da fonte de dados.
- Um arquivo de texto (normalmente um arquivo XML) é usado para armazenar informações sobre todas as classes de controle da fonte de dados.
- Um mecanismo foi projetado e implementado que pesquisa o arquivo XML para uma subclasse com uma determinada ID de cadeia de caracteres.
Agora vamos ver como ASP.NET 2.0 implementa as três tarefas acima do padrão do provedor. ASP.NET 2.0 deriva todos os controles de fonte de dados da classe Control . Por que os controles de fonte de dados derivam da classe Control se eles não renderizam texto de marcação HTML? Eles derivam da classe Control para que possam herdar os três recursos importantes a seguir:
- Eles podem ser instanciados declarativamente.
- Eles salvam e restauram seus valores de propriedade entre postbacks.
- Eles são adicionados à árvore de controle da página que contém.
O primeiro recurso permite que os desenvolvedores de página instanciem declarativamente controles de fonte de dados em seus respectivos arquivos .aspx. Portanto, o arquivo .aspx atua como o texto ou arquivo XML que a segunda etapa do padrão do provedor requer. A arquitetura de controle ASP.NET 2.0 cria dinamicamente uma instância do controle de fonte de dados declarado e atribui a instância a uma variável cujo nome é o valor da propriedade ID do controle de fonte de dados declarado. Isso cuida da primeira e terceira etapas acima exigidas pelo padrão do provedor.
A Figura 6 mostra a versão do método GetSubscribers que usa os métodos e as propriedades da interface IDataSource e da classe abstrata DataSourceView para acessar o armazenamento de dados subjacente:
Figura 6. A versão do método GetSubscribers que usa os métodos e as propriedades da interface IDataSource e da classe abstrata 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);
}
A primeira linha do método GetSubscribers mostra claramente que o método trata o controle da fonte de dados como um objeto do tipo IDataSource. O método não se importa e não deve se preocupar com o tipo real do controle da fonte de dados, como se ele é um controle SqlDataSource, AccessDataSource, ObjectDataSource ou XmlDataSource. Isso permitirá que os desenvolvedores de página mudem de um controle de fonte de dados para outro sem precisar modificar o código de acesso a dados (o método GetSubscribers). A próxima seção discutirá esse importante problema em mais detalhes.
O método GetSubscribers chama o método GetView do objeto IDataSource para acessar seu objeto de exibição tabular padrão. Observe que o método GetView retorna um objeto do tipo DataSourceView. O método GetSubscribers não se importa com o tipo real do objeto de exibição, como se ele é um objeto SqlDataSourceView, AccessDataSourceView, ObjectDataSourceView ou XmlDataSourceView.
Em seguida, o método GetSubscribers cria uma instância da classe DataSourceSelectArguments para solicitar operações extras, como inserir, paginar ou recuperar a contagem total de linhas nos dados retornados pela operação Select . O método primeiro precisa marcar o valor da propriedade CanInsert, CanPage ou CanRetrieveTotalRowCount da classe DataSourceView para garantir que o objeto de exibição dê suporte à respectiva operação antes de fazer a solicitação.
Como a operação Select é assíncrona, o método GetSubscribers registra o método SendMail como o retorno de chamada. O método Select chama automaticamente o método SendMail depois que ele consulta os dados e passa os dados como seu argumento, conforme mostrado na Figura 7.
Figura 7. O método SendMail enumera os dados e extrai as informações necessárias.
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);
}
}
O método SendMail chama o método GetEnumerator do objeto passado como seu primeiro argumento para acessar seu objeto enumerador e usa o enumerador para enumerar os dados. O método SendMail usa o método Eval da classe DataBinder para extrair o endereço de email, o nome e o sobrenome de cada assinante e envia a carta de notícias para cada um deles.
Alternando de um controle de fonte de dados para outro
Conforme discutido anteriormente, a arquitetura de controle de ASP.NET cria dinamicamente uma instância do controle de fonte de dados declarada na respectiva página .aspx e a atribui à variável cujo nome é o valor da propriedade ID do controle de fonte de dados declarado. Essa instanciação dinâmica do controle de fonte de dados declarado isola o método GetSubscribers do tipo real do controle da fonte de dados e permite que o método trate todos os controles da fonte de dados como objetos do tipo IDataSource. Isso permite que os desenvolvedores de página mudem de um tipo de controle de fonte de dados para outro sem modificar o código de acesso a dados (o método GetSubscribers). Esta seção apresenta um exemplo para esse caso.
Digamos que nosso aplicativo Web use o método GetSubscribers em conjunto com o controle ObjectDataSource para recuperar a lista de assinantes de um armazenamento de dados relacional, como o Microsoft SQL Server:
<asp:SqlDataSource ID="MySource" Runat="Server"
ConnectionString="<%$ ConnectionStrings:MySqlConnectionString %>"
SelectCommand="Select * From Subscribers" />
Suponha que nosso aplicativo Web funcione em um ambiente em que a lista de assinantes também pode vir de um documento XML. O documento XML pode ser um arquivo XML local ou um recurso remoto acessado via URL. Portanto, nosso aplicativo deve ser capaz de recuperar a lista de assinantes de documentos XML. Obviamente, o controle ObjectDataSource não foi projetado para recuperar dados tabulares de documentos XML, o que significa que precisamos usar um controle de fonte de dados que possa recuperar dados tabulares de documentos XML, como o controle XmlDataSource.
O método GetSubscribers não usa nenhuma propriedade ou método específico para as classes ObjectDataSource e ObjectDataSourceView e usa apenas os métodos e propriedades da interface IDataSource e da classe abstrata DataSourceView para lidar com controles de fonte de dados. Podemos alternar facilmente do ObjectDataSource para o controle XmlDataSource e usar o mesmo código de acesso a dados que o método GetSubscribers para recuperar a lista de assinantes:
<asp:XmlDataSource ID="MySource" Runat="Server"
DataFile="data.xml" XPath="/Subscribers/Subscriber" />
O valor do atributo XPath do controle XmlDataSource é definido como /Subscribers/Subscriber para selecionar todos os assinantes.
Operação Inserir e Excluir
Lembre-se de que nosso aplicativo Web consiste em duas partes. A segunda parte do aplicativo permite que os usuários assinem/cancelem a assinatura de uma lista de endereçamento. O método Subscribe é chamado quando o botão Assinar é clicado, conforme mostrado na Figura 8.
Figura 8. O método Subscribe é chamado quando o botão Assinar é clicado.
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);
}
A primeira linha do método Subscribe mostra que o método não se importa com o tipo real do controle da fonte de dados. Portanto, podemos alternar para um controle de fonte de dados diferente para dar suporte a um novo armazenamento de dados sem precisar alterar o código no método Subscribe.
O método usa uma instância da classe KeyedList para coletar o email, o nome e o sobrenome do assinante. Não precisamos usar a classe KeyedList. Podemos usar qualquer classe que implemente a interface IDictionary , incluindo ArrayList, KeyedList etc.
O método Subscribe verifica o valor da propriedade CanInsert do objeto de exibição da fonte de dados para garantir que o objeto de exibição dê suporte à operação Insert antes de chamar o método Insert . O método Subscribe passa a instância KeyedList como o primeiro argumento para o método Insert.
O método Unsubscribe funciona de forma semelhante ao método Subscribe. A main diferença é que o método Unsubscribe chama o método Delete do respectivo objeto de exibição para remover a assinatura do armazenamento de dados subjacente, conforme mostrado na Figura 9.
Figura 9. O método Cancelar assinatura é chamado quando o botão Cancelar assinatura é clicado.
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 hierárquicos da fonte de dados
O método GetSubscribers (o código de acesso a dados) alimenta dados tabulares para seus chamadores. No entanto, há momentos em que o código de acesso a dados deve retornar dados hierárquicos para seus chamadores. Esta seção apresenta a implementação da versão do método GetSubscribers que retorna dados hierárquicos. Podemos pensar nisso como um contrato entre o método e seus chamadores. Os chamadores esperam que o método retorne dados hierárquicos de armazenamentos de dados hierárquicos e tabulares.
ASP.NET 2.0 usa o padrão de provedor para isolar o método GetSubscribers do tipo real do armazenamento de dados subjacente e apresenta o método com as exibições hierárquicas do armazenamento de dados. Isso permite que o método trate os armazenamentos de dados hierárquicos e tabulares como armazenamentos de dados hierárquicos.
Cada controle de fonte de dados hierárquica foi projetado especificamente para trabalhar com um armazenamento de dados específico. No entanto, como todos os controles hierárquicos de fonte de dados implementam a interface IHierarchicalDataSource e todas as exibições hierárquicas da fonte de dados derivam da classe HierarchicalDataSourceView , o método GetSubscribers não precisa lidar com as especificidades de cada controle de fonte de dados e pode tratar todos eles de forma genérica.
IHierarchicalEnumerable GetSubscribers()
{
IHierarchicalDataSource ds = (IHierarchicalDataSource)MySource;
HierarchicalDataSourceView dv = ds.GetHierarchicalView("/Subscribers");
return dv.Select();
}
A primeira linha do método GetSubscribers mostra que o método trata o controle da fonte de dados como um objeto do tipo IHierarchicalDataSource e não se importa com o tipo real do controle da fonte de dados. Isso permitirá que os desenvolvedores de página mudem para um novo controle de fonte de dados hierárquica para adicionar suporte a um novo armazenamento de dados sem precisar modificar o código no método GetSubscribers.
Em seguida, o método GetSubscribers chama o método GetHierarchicalView da classe HierarchicalDataSourceView para acessar a exibição hierárquica com o caminho fornecido, como "/Subscribers". Observe que o método Select não é assíncrono. O aplicativo passa os dados retornados do método GetSubscribers para o método SendMail (consulte a Figura 15). Observe que os dados são do tipo IHierarchicalEnumerable.
O IHierarchicalEnumerable implementa IEnumerable, o que significa que ele expõe o método GetEnumerator . O método SendMail chama o método GetEnumerator para acessar o respectivo objeto IEnumerator, que é usado posteriormente para enumerar os dados. O IHierarchicalEnumerable também expõe um método chamado GetHierarchyData que usa o objeto enumerado e retorna o objeto IHierarchyData associado a ele.
A interface IHierarchyData expõe uma propriedade importante chamada Item, que não é nada além do item de dados. O método SendMail usa o método Eval da classe XPathBinder para avaliar expressões XPath em relação ao objeto Item.
Figura 10. O método SendMail enumera os dados, extrai as informações necessárias e envia o boletim informativo para cada assinante.
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);
}
}
Conclusão
Usando uma abordagem passo a passo mostrando diferentes ferramentas e técnicas do ASP.NET 2.0 e ADO.NET 2.0, este artigo demonstra como os desenvolvedores de páginas podem escrever código de acesso a dados genérico que pode ser usado para acessar diferentes tipos de armazenamentos de dados.
Shahram Khosravié engenheiro de software sênior da Schlumberger Information Solutions (SIS). O Shahram é especializado em ASP.NET, serviços Web XML, tecnologias .NET, tecnologias XML, Computação Gráfica 3D, HI/Usability, Padrões de Design e desenvolvimento de ASP.NET controles e componentes do servidor. Ele tem mais de 10 anos de experiência em programação orientada a objetos. Ele usa uma variedade de ferramentas e tecnologias da Microsoft, como SQL Server e ADO.NET. Shahram escreveu artigos sobre tecnologias .NET e ASP.NET para a revista asp.netPRO.