Desenvolvimento - ASP. NET

Criando novos Providers

A arquitetura chamada pela Microsoft de Provider Model foi introduzida na versão 2.0 do ASP.NET com a finalidade de podermos configurar um determinado repositório de dados, tornando isso plug and play.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i A arquitetura chamada pela Microsoft de Provider Model foi introduzida na versão 2.0 do ASP.NET com a finalidade de podermos configurar um determinado repositório de dados, tornando isso plug and play. Há uma porção de recursos no ASP.NET 2.0 que usa essa técnica, a saber: Profile, Membership, Roles, WebParts e SiteMaps. Os Providers Models utilizam os padrões Abstract Factory e Factory Method (padrões Criacionais) para garantir a genericidade, ou seja, trabalhamos com uma classe abstrata e, em runtime, o ASP.NET se encarregará de instanciar a classe concreta.

Porém, isso não é novidade. O que poucos sabem é que podemos utilizar essa mesma arquitetura para criar nossos próprios providers, não necessariamente vinculando-os a alguma fonte de dados. A idéia deste artigo é ilustrar como devemos proceder para criar o nosso próprio modelo de providers e, tirar todo o proveito fornecido pela plataforma, ou seja, tornar a mudança entre um provider e outro de forma transparente, sem a necessidade de recompilar a aplicação.

Como cenário utilizaremos o seguinte problema: atualmente há três orgãos (Serasa, SCI e SPC) que, dado um número de CPF, retornará um valor booleano indicando se essa pessoa possui ou não restrições financeiras (ReFin). O primeiro passo é criar a classe abstrata que servirá como base para todas as classes derivadas, ou seja, teremos três tipos de consultas, pois teremos três orgãos diferentes. Essa classe base deverá obrigatoriamente herdar, também de uma outra classe abstrata, chamada ProviderBase que, por sua vez, está contida dentro do namespace System.Configuration.Provider. Essa classe fornece a implementação básica para todo e qualquer provider model.

A nossa classe chamará RefinProvider e possuirá apenas um único método abstrato para atender a nossa necessidade que é consultar um determinado número de CPF. Uma vez criada essa classe, ela deverá ser herdada por todas as classes que efetuarão a consulta. Se temos três orgãos, então teremos três classes derivadas de RefinProvider: SerasaRefinProvider, SPCRefinProvider e SCIRefinProvider. O diagrama abaixo ilustra exatamente a hierarquia dessas classes:

Figura 1 - Hierarquia das classes criadas.

Como podemos notar na imagem acima, o método Consultar da classe RefinProvider é implementado nas classes concretas onde, para cada um dos orgãos, terá uma implementação diferente, pois cada um deles exige uma maneira exclusiva de como proceder a consulta. Essas formas irão mesmo variar, e é exatamente essa a flexibilidade que o Provider Model nos proporciona, ou seja, customizar cada uma dessas classes, e apenas com uma pequena configuração no arquivo Web.Config, determinar qual será utilizada pela aplicação. O código abaixo exibe o código da classe que servirá como base para todos os providers:

using System;
using System.Configuration.Provider;

public abstract class RefinProvider : ProviderBase
{
    public abstract bool Consultar(string cpf);
}
Imports System
imports System.Configuration.Provider

Public MustInherit Class RefinProvider
    Inherits ProviderBase

    Public MustOverride Function Consultar(ByVal cpf As String) As Boolean
End Class
C# VB.NET

O próximo passo é a criação da seção de configuração que devemos ter no arquivo Web.Config. Essa seção de configuração tem os mesmos princípios do membership, roles, etc., que é a possibilidade de listar todos os possíveis providers e depois decidir qual deles usar. Para a criação desta seção de configuração, precisamos criar uma classe que herde da classe abstrata ConfigurationSection, contida dentro do namespace System.Configuration, que representa uma seção no arquivo de configuração. A nossa seção terá duas propriedades: DefaultProvider e Providers; a primeira delas, do tipo string, receberá o nome do provider escolhido para ser utilizado pela aplicação; já a segunda propriedade é do tipo ProviderSettingsCollection, que representa uma coleção de objetos de configuração. O código abaixo exibe a criação desta classe e, mais tarde ainda neste artigo, veremos a sua utilização no arquivo Web.Config.

using System;
using System.Configuration;
using System.Configuration.Provider;

public class RefinSection : ConfigurationSection
{
    [ConfigurationProperty("defaultProvider")]
    public string DefaultProvider
    {
        get
        {
            return (string)base["defaultProvider"];
        }
        set
        {
            base["defaultProvider"] = value;
        }
    }

    [ConfigurationProperty("providers")]
    public ProviderSettingsCollection Providers
    {
        get
        {
            return (ProviderSettingsCollection)base["providers"];
        }
    }
}
Imports System
Imports System.Configuration
Imports System.Configuration.Provider

Public Class RefinSection
    Inherits ConfigurationSection

    <ConfigurationProperty("defaultProvider")> _
    Public Property DefaultProvider() As String
        Get
            Return MyBase.Item("defaultProvider").ToString()
        End Get
        Set(ByVal value As String)
            MyBase.Item("defaultProvider") = value
        End Set
    End Property

    <ConfigurationProperty("providers")> _
    Public ReadOnly Property Providers() As ProviderSettingsCollection
        Get
            Return DirectCast(MyBase.Item("providers"), ProviderSettingsCollection)
        End Get
    End Property
End Class
C# VB.NET

Nota: Se alguém quiser saber um pouco mais sobre como proceder para criar uma seção de configuração customizada, consulte este artigo.

Depois da criação das classes, que são os providers, e também da classe que representará a seção de configuração, chega o momento da criação de uma das classes mais importantes, que é uma classe estática que centralizará todo o trabalho, ou seja, extrairá as informações do arquivo de configuração, criará os providers especificados e deixará a disposição da aplicação o provider padrão, ou melhor, aquele que será escolhido para que a aplicação possa usá-lo.

Essa classe receberá o nome de Refin e terá duas propriedades públicas e estáticas chamadas Provider e Providers. A primeira delas, Provider, retornará a instância do provider (RefinProvider) que está atualmente selecionado; já a segunda, Providers, retorná uma coleção do tipo ProviderCollection, contendo todos os providers que foram especificados no arquivo de configuração. Além disso, essa classe ainda possuirá um construtor estático onde, dentro dele, através do método GetSection da classe ConfigurationManager, recuperaremos a instância da seção (RefinSection) que criamos anteriormente.

Ainda dentro do construtor estático, invocamos o método estático InstantiateProviders da classe ProvidersHelper passando para o mesmo as configurações dos providers que deverão ser inicializadas, a instância de uma coleção do tipo ProviderCollection e o tipo dos providers que deverão ser inicializados. Depois que passar pelo método, o segundo parâmetro, que é a coleção, estará devidamente inicializado, ou seja, com todas as instâncias dos providers especificados no arquivo de configuração. Com a coleção definida, então utilizaremos a propriedade DefaultProvider que criamos na classe RefinSection para capturar o provider que a aplicação deve utilizar.

Finalmente, a classe Refin ainda possui, por conveniência, um método estático chamado Consultar que, em seu interior, faz a chamada para o método Consultar da classe concreta, que nada mais é do que o provider que está correntemente selecionado. O código abaixo ilustra todo esse processo que acabamos de ver:

using System;
using System.Configuration;
using System.Configuration.Provider;
using System.Web.Configuration;

public static class Refin
{
    private static readonly RefinProvider _provider;
    private static readonly ProviderCollection _providers;

    public static RefinProvider Provider
    {
        get
        {
            return _provider;
        }
    }

    public static ProviderCollection Providers
    {
        get
        {
            return _providers;
        }
    }

    static Refin()
    {
        RefinSection section =
            (RefinSection)ConfigurationManager.GetSection("refin");

        _providers = new ProviderCollection();
        ProvidersHelper.InstantiateProviders(
            section.Providers, 
            _providers, 
            typeof(RefinProvider));

        string defaultProvider = section.DefaultProvider.Trim();

        if (!string.IsNullOrEmpty(defaultProvider))
            _provider = (RefinProvider)_providers[defaultProvider];
        else
            throw new ConfigurationErrorsException("Provider padrão não definido.");
    }

    public static bool Consultar(string cpf)
    {
        return Refin.Provider.Consultar(cpf);
    }
}
Imports System.Configuration
Imports System.Configuration.Provider
Imports System.Web.Configuration

Public NotInheritable Class Refin

    Private Shared _provider As RefinProvider
    Private Shared _providers As ProviderCollection

    Public Shared ReadOnly Property Provider() As RefinProvider
        Get
            Return _provider
        End Get
    End Property

    Public Shared ReadOnly Property Providers() As ProviderCollection
        Get
            Return _providers
        End Get
    End Property

    Shared Sub New()
        Dim section As RefinSection = _
            DirectCast(ConfigurationManager.GetSection("refin"), RefinSection)

        _providers = New ProviderCollection()
        ProvidersHelper.InstantiateProviders( _
            section.Providers, _
            _providers, _
            GetType(RefinProvider))

        Dim defaultProvider As String = section.DefaultProvider
        If Not String.IsNullOrEmpty(defaultProvider) Then
            _provider = DirectCast(_providers(defaultProvider), RefinProvider)
        Else
            Throw New ConfigurationErrorsException("Provider padrão não definido.")
        End If
    End Sub

    Public Shared Function Consultar(ByVal cpf As String) As Boolean
        Return Refin.Provider.Consultar(cpf)
    End Function

End Class
C# VB.NET

Agora, depois de toda a estrutura de classes definidas, chega o momento de utilizarmos isso na aplicação Web. Como já fizemos a maior parte do código, na aplicação Web resta-nos apenas registrar a seção que criamos anteriormente e fazer o uso dela, adicionando os providers que podemos utilizar e, que neste cenário, estão sendo distribuídos juntamente com o componente que acabamos de desenvolver. O código do arquivo Web.Config fica da seguinte forma:

<?xml version="1.0"?>
<configuration>
    <configSections>
        <section name="refin" type="CSLibrary.RefinSection, CSLibrary" />
    </configSections>
    <refin defaultProvider="SCI">
        <providers>
            <add name="Serasa" type="CSLibrary.SerasaRefinProvider, CSLibrary"/>
            <add name="SPC" type="CSLibrary.SPCRefinProvider, CSLibrary"/>
            <add name="SCI" type="CSLibrary.SCIRefinProvider, CSLibrary"/>
        </providers>
    </refin>
    <system.web>
        <!-- Outras Configurações -->
    </system.web>
</configuration>
Web.Config

Com o código acima, conseguimos assimilar as classes que já criamos e onde elas estão sendo aplicadas neste momento. Registramos a seção RefinSection dentro do elemento configSections para mais adiante fazermos o uso dele. Recapitulando, a classe RefinSection possui duas propriedades: DefaultProvider e Providers. A propriedade DefaultProvider recebe um dos valores definidos no atributo name do elemento add dos providers. O provider que estiver ali selecionado é o que será executado, e ainda podendo ser trocado sem nenhum grande trabalho, pois basta alterar a propriedade DefaultProvider e a recompilação da aplicação não é necessária.

Para finalizar, resta-nos o código na aplicação cliente. Neste momento, utilizaremos o método estático Consultar da classe Refin. Internamente ele invoca o método da classe concreta atualmente selecionada que é exposta através da propriedade Provider. O interessante é que a aplicação nada sabe sobre o provider concreto.

protected void Button1_Click(object sender, EventArgs e)
{
    if (CSLibrary.Refin.Consultar(this.txtCPF.Text))
        Response.Write("Há restrições financeiras para o CPF consultado.");
    else
        Response.Write("Não há restrições financeiras para o CPF consultado.");
}
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Button1.Click

    If VBLibrary.Refin.Consultar(Me.txtCPF.Text) Then
        Response.Write("Há restrições financeiras para o CPF consultado.")
    Else
        Response.Write("Não há restrições financeiras para o CPF consultado.")
    End If
End Sub
C# VB.NET

Como muitos estão familiarizados com as classes de providers fornecidas dentro do .NET Framework, vamos fazer uma comparação entre as classes existentes para Membership e Roles com as classes que criamos no decorrer deste artigo, para assim melhorar a compreensão e também a necessidade de cada uma das classes. Essa comparação é feita através da tabela abaixo:

Refin Membership Roles
RefinSection MembershipSection RoleManagerSection
RefinProvider MembershipProvider RoleProvider
Refin Membership Roles
SerasaRefinProvider, SPCRefinProvider e SCIRefinProvider SqlMembershipProvider e AccessMembershipProvider SqlRoleProvider e AccessRoleProvider

Conclusão: Vimos no decorrer deste artigo que a arquitetura dos Provider Models vai muito além do que a plataforma disponibiliza, que é o caso do Membership, Roles, etc.. Podemos, através dele, tornar as nossas aplicações muito mais flexíveis pois nos permite trocar o provider a qualquer momento, e ainda estender o mesmo, permitindo assim customizar para um novo orgão de consulta (neste cenário) ou para qualquer outra finalidade.

Baixe o código.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.