Criando um BuildProvider

Apesar de você não perceber, muito provavelmente já deve ter utilizado esta funcionalidade. Quando criamos arquivos de resources na nossa aplicação, automaticamente é criada uma classe chamada Resources e, dentro dela, são criadas propriedades que servem de wrapper para acessar cada um dos itens de forma mais simples e, conseqüentemente, já tendo suporte através do intellisense do Visual Studio .NET 2005. Isso é possível graças a um builder provider chamado ResourcesBuildProvider que está contido no namespace System.Web.Compilation.

Os build providers, quando adicionados no sistema de compilação do ASP.NET, nos permite customizar a compilação de alguns tipos de arquivos, como é o caso de arquivos com extensão *.resx. Sendo assim, é possível gerar uma classe dinamicamente, baseando-se em alguma regra e já podendo acessar seus membros (métodos e propriedades) durante o desenvolvimento da aplicação. Além deste exemplo, já há vários builders que a própria Microsoft criou. O trecho de código abaixo foi extraído do arquivo Web.Config global, que está no seguinte endereço: %windir%Microsoft.NETFrameworkv2.0.50727CONFIG:

<buildProviders>
    <add extension=".aspx" type="System.Web.Compilation.PageBuildProvider" />
    <add extension=".ascx" type="System.Web.Compilation.UserControlBuildProvider" />
    <add extension=".master" type="System.Web.Compilation.MasterPageBuildProvider" />
    <add extension=".asmx" type="System.Web.Compilation.WebServiceBuildProvider" />
    <add extension=".ashx" type="System.Web.Compilation.WebHandlerBuildProvider" />
    <add extension=".soap" type="System.Web.Compilation.WebServiceBuildProvider" />
    <add extension=".resx" type="System.Web.Compilation.ResXBuildProvider" />
    <add extension=".resources" type="System.Web.Compilation.ResourcesBuildProvider" />
    <add extension=".wsdl" type="System.Web.Compilation.WsdlBuildProvider" />
    <add extension=".xsd" type="System.Web.Compilation.XsdBuildProvider" />
    <add extension=".js" type="System.Web.Compilation.ForceCopyBuildProvider" />
    <add extension=".lic" type="System.Web.Compilation.IgnoreFileBuildProvider" />
    <add extension=".licx" type="System.Web.Compilation.IgnoreFileBuildProvider" />
    <add extension=".exclude" type="System.Web.Compilation.IgnoreFileBuildProvider" />
    <add extension=".refresh" type="System.Web.Compilation.IgnoreFileBuildProvider" />
    <add 
        extension=".svc" 
        type="System.ServiceModel.Activation.ServiceBuildProvider, System.ServiceModel" />
</buildProviders>

O interessante é que esta funcionalidade torna as coisas bem flexíveis e nos permite criar os nossos próprios buider providers. Para o exemplo irei utilizar um cenário bastante comum nas aplicações que desenvolvo. Para todos os valores que coloco na seção AppSettings do arquivo Web.Config da aplicação, eu costumo criar uma classe chamada Settings com propriedades de somente leitura que expõem cada um daqueles itens. Sendo assim, a idéia aqui será criar uma classe contendo propriedades que nada mais serão do que wrappers para cada um dos valores definidos na seção AppSettings.

Um detalhe importante antes de começarmos a estudar a geração do código é mencionar que tal geração deverá ser feita utilizando CodeDom (namespace System.CodeDom), que fornece tipos que representam elementos de código. Para maiores informações sobre este assunto, consulte este endereço.

O primeiro passo é criar a classe que será responsável por essa geração, que será o nosso build provider. Obrigatoriamente precisamos herdar da classe base chamada BuildProvider que está dentro do namespace System.Web.Compilation. Essa classe fornece um método chamado GenerateCode (que deverá ser sobrescrito em nosso build provider) e, como parâmetro, passa a este método uma instância do tipo AssemblyBuilder que referenciará o código gerado pelo build provider. É neste momento que precisamos recorrer ao CodeDom para gerar o código da classe proposta. Os passos são:

  • Criação de uma namespace chamado Config para organizar melhor o código a ser gerado;

  • Criar uma classe chamada Settings que armazenará as propriedades. Essa classe precisa estar dentro do namespace Config e, além disso, é necessária que seja partial para permitir que o consumidor possa “estendê-la”;

  • Finalmente, para cada item adicionado na seção AppSettings do arquivo Web.Config da aplicação, é necessário criar uma propriedade e adicionar dentro da classe Settings. Essas propriedades devem ter como nome a chave e o que ela retornará será o valor de cada item do AppSettings.

Tendo esses objetivos, vamos escrever o código que efetua essa criação. O código abaixo exibe, na íntegra, o build provider já totalmente criado.

using System;
using System.CodeDom;
using System.Configuration;
using Web = System.Web.Compilation;

public class SettingsBuildProvider : Web.BuildProvider
{
    public override void GenerateCode(Web.AssemblyBuilder assemblyBuilder)
    {
        CodeNamespace ns = new CodeNamespace("Config");
        CodeTypeDeclaration settingsClass = new CodeTypeDeclaration("Settings");
        settingsClass.IsPartial = true;
        this.GenerateProperties(settingsClass);
        ns.Types.Add(settingsClass);
        ns.Imports.Add(new CodeNamespaceImport("System.Configuration"));

        CodeCompileUnit unit = new CodeCompileUnit();
        unit.Namespaces.Add(ns);
        assemblyBuilder.AddCodeCompileUnit(this, unit);
    }

    private void GenerateProperties(CodeTypeDeclaration type)
    {
        foreach (string key in ConfigurationManager.AppSettings.AllKeys)
        {
            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = key;
            prop.Type = new CodeTypeReference(typeof(string));
            prop.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            prop.HasSet = false;

            prop.GetStatements.Add(
                new CodeMethodReturnStatement(
                    new CodeSnippetExpression(
                        string.Format(
                            "ConfigurationManager.AppSettings["{0}"]", 
                            key))));

            type.Members.Add(prop);
        }
    }
}

Analisando o código acima, dentro do método GenerateCode inicialmente criamos o namespace padrão onde a classe de configuração irá residir. A criação do namespace é realizada através do objeto CodeNamespace que, em seu construtor, podemos informar o nome do mesmo; em seguida, utilizando uma instância da classe CodeTypeDeclaration, criaremos a classe que irá expor as propriedades do arquivo Web.Config. A classe CodeTypeDeclaration fornece uma propriedade chamada IsPartial que, opcionalmente, vamos definir para true.

Com a classe devidamente criada, o próximo passo é percorrer a coleção de itens da seção AppSettings e, para cada um deles, criar uma propriedade. Esse passo é realizado pelo método privado chamado GenerateProperties. Como essas propriedades expõem valores estáticos, as mesmas também precisam ser criadas como propriedades estáticas. Para criar a propriedade, utilizamos uma instância da classe CodeMemberProperty, definimos o nome, tipo e através da propriedade GetStatements, especificamos ali o que ela retornará. Finalmente, adicionamos essa propriedade na coleção de membros do tipo (classe) que é passado como parâmetro para este método.

Para finalizar a explicação da geração do código, adicionamos a classe já completamente configurada na coleção de tipos do namespace Config que criamos anteriormente. Como essa classe dinâmica referencia a classe ConfigurationManager, é necessário importarmos o namespace System.Configuration. para dentro do namespace Config que criamos. Depois disso, criamos um objeto do tipo CodeCompileUnit, que representa um container para o CodeDom e, através da propriedade Namespaces, adicionamos o namespace que criamos. Por fim, invocamos o método AddCodeCompileUnit, passando o build provider corrente e o container de código gerado.

Uma vez que o build provider está criado, é necessário registrar o mesmo dentro da aplicação Web que o utilizará. Para isso, o ASP.NET fornece um elemento chamando buildProviders (contido dentro do elemento compilation), onde informamos o tipo (type) referente à classe geradora do código e uma extensão (extension), onde a mesma irá depositar o código gerado. Para que isso funcione bem, é necessário que dentro do diretório App_Code exista um arquivo “dummy” com a mesma extensão definida nesta seção. Como as informações a serem utilizadas para a geração do código serão extraídas do arquivo Web.Config, este arquivo “dummy” não necessita ter nenhum dado dentro. O trecho de código abaixo exemplifica o registro do build provider:

<?xml version="1.0"?>
<configuration>
    <appSettings>
        <add key="EmailParaNotificacao" value="israel@projetando.net"/>
        <add key="SiteUrl" value="http://www.projetando.net/"/>
        <add key="BlogUrl" value="http://weblogs.pontonetpt.com/israelaece/"/>
    </appSettings>
    <system.web>
        <compilation debug="true">
            <buildProviders>
                <add
                    extension=".sett"
                    type="BuildProvider.Library.SettingsBuildProvider, BuildProvider.Library"/>
            </buildProviders>
        </compilation>
    </system.web>
</configuration>

Como o cenário é gerar código (propriedades), para cada um dos itens que estão na seção AppSettings do Web.Config, ali os mesmos já estão definidos. Com esse registro feito, agora já temos suporte em tempo de desenvolvimento, permitindo que o intellisense do Visual Studio .NET 2005 já mostre as propriedades criadas. A imagem abaixo ilustra esse funcionamento:

Figura 1Intellisense já suportando as propriedades criadas dinamicamente.

O interessante é que, se você colocar um Breakpoint na linha em que faz a chamada para algumas destas propriedades, ao entrar dentro da mesma, você enxergará a classe que foi criada, com a seguinte mensagem:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.832
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Config {
    using System.Configuration;
    
    
    public partial class Settings {
        
        public static string EmailParaNotificacao {
            get {
                return ConfigurationManager.AppSettings["EmailParaNotificacao"];
            }
        }
        
        // outras propriedades...
    }
}

Conclusão: Como pudemos ver no decorrer deste artigo, os build providers fornecem uma grande funcionalidade que, dependendo do cenário, pode reduzir consideravelmente a quantidade de código manual a ser escrita. O interessante é que você não precisa se limitar o acesso a informações do próprio ASP.NET (como foi o caso do AppSettings que utilizamos aqui). Você pode criar um arquivo qualquer, padrão XML, CSV, etc. e o teu build provider fazer o parser e, conseqüentemente, gerar o código referente para que o desenvolvedor possa acessar as informações ainda em tempo de desenvolvimento.

Clock Skew

Quando construímos um serviço WCF, basicamente criamos o serviço e os clientes que o acessam. Fazemos a distribuição dos clientes, sem nos preocuparmos com uma configuração que, as vezes, podem resultar em possíveis erros durante a execução.

Ao expor um serviço através de um binding que já traz segurança integrada (como é o caso do WSHttpBinding), há um comportamento especial em que ao enviar a mensagem do cliente para o serviço, a data/hora atual são incluídos dentro da requisição. Quando a mensagem chegar para o serviço, ele analisará essa data/hora antes de efetivamente executar o pedido. Para evitar possíveis ataques de replay, o WCF verifica se a data/hora de criação é maior ou menor que o permitido (o padrão é 5 minutos de tolerancia para mais ou para menos) e, caso seja, a requisição será rejeitada.

Para customizar esse valor, podemos recorrer a propriedade MaxClockSkew que aceita um TimeSpan que especifica a tolerancia máxima permitida. Para os bindings pré-definidos essa configuração não está acessível, tendo que fazer o uso de um customBinding caso necessitamos mudar a configuração padrão que, dificilmente será necessário, a menos que a sincronização dos relógios das duas ou mais máquinas envolvidas não seja possível.

O futuro do System.Messaging.dll

Juntamente com o Windows Vista e Windows Server 2008 foi colocado a disposição a nova versão do Message Queue, 4.0. Essa nova versão traz algumas funcionalidades muito interessantes e, a primeira delas, são as subqueues. Como o próprio nome diz, as subqueues são filas que estão abaixo da fila principal e herdam as suas características. Não podemos mandar mensagens explicitamentes para essas filas, mas podemos remove-las de lá. Além disso, não temos o controle da criação ou exclusão dessas subqueues, ficando sob responsabilidade do próprio Message Queue que a criará quando preciso e a excluirá quando estiver vazia.

Outra funcionalidade muito interessante são as Poison Message Queues. Mensagens que estão dentro de uma fila estão propícias a falharem e, se essa falha ocorrer repetidamente, podemos ter um loop infinito, ou seja, a mensagem atual nunca será processada e, consequentemente, as mensagens subsequentes também não. Para isso, o Message Queue 4.0 possibilita especificarmos a quantidade de tentativas de processamento e, caso todas elas esgotem, a mesma será movida para uma Poison Message Queue que, nada mais é que uma subqueue, dando sequencia no processamento das mensagens seguintes.

Apesar de muito interessantes, essas novas funcionalidades somente podem ser utilizadas a partir do WCF (mais detalhes aqui). Pelo fato da Microsoft estar centralizando todas as formas de comunicação em cima do WCF, acredito que a API System.Messaging não evoluirá e, consequentemente, não suportará essas novas funcionalidades.