Capacidade do StringBuilder

by Israel Aece 27. May 2009 08:57

A classe StringBuilder é largamente utilizada por aplicações .NET, e está presente desde a primeira versão do .NET Framework, debaixo do namespace System.Text. Como todos sabemos, essa classe representa uma string mutável, ou seja, ela pode sofrer inserções de novas strings, removê-las ou até mesmo a substituição de caracteres, de forma mais mais simples e performática quando comparada à classe String.

Imagine um cenário onde você tem um laço de repetição e que vai adicionando “linhas” de N caracteres no objeto StringBuilder. Algo mais ou menos como vemos abaixo:

StringBuilder sb = new StringBuilder();
string linha = new string('x', 400);

for (int i = 1; i < 21; i++)
    sb.AppendLine(linha);

Em princípio, tudo funciona muito bem, mas ainda há muito o que melhorarmos. Ao acrescentar caracteres dentro dele, o StringBuilder verifica se o array interno que armazena os caracteres está tentando aumentar além de sua capacidade. Se sim, automaticamente dobrará o tamanho dessa capacidade, alocando um novo array e copiando os caracteres anteriores para este novo e, consequentemente, o array original será descartado, e é justamente esse aumento dinâmico que prejudica a performance.

A classe StringBuilder define uma propriedade chamada Capactiy, que podemos especificar/sugerir a quantidade inicial de memória alocada pela instância corrente. A capacidade pode ser diminuida, mas não pode ser menor que o valor especificado pela propriedade Length, e também não pode ser maior que o valor especificado pela propriedade MaxCapacity. Se executarmos o código que vimos acima e analisarmos a propriedade Capacity, ela retornará 12800, enquanto estamos realmente utilizando 402 caracteres * 20 linhas, que totaliza 8040.

Se você conseguir antecipar a quantidade de caracteres que o StringBuilder deverá ter, irá tirar um melhor proveito dele. Para isso, você pode fazer o uso de um overload do construtor que já nos permite especificar/sugerir a capacidade inicial. Com isso, a única mudança que temos no código acima é a primeira linha:

StringBuilder sb = new StringBuilder(8040);

É importante dizer que especificar a capacidade não quer dizer que quando você atingir este número, você terá uma exceção. Neste caso, internamente ele fará cálculos para poder redimensionar a classe, e acomodar esses novos caracteres. Se desejar impor uma quantidade máxima, então você pode recorrer à propriedade MaxCapacity, que impedirá adicionar mais caracteres do que o valor nela especificado.

Tags:

.NET Framework

Enumerando arquivos e diretórios

by Israel Aece 24. May 2009 19:26

Algumas classes que estão contidas no namespace System.IO trabalham com arrays de strings. Para citar alguns exemplos, temos as classes Directory e DirectoryInfo, que fornecem dois métodos chamados GetDirectories e GetFiles. Ambos os métodos retornam um array de strings, representando os sub-diretórios ou os arquivos que constam dentro de um determinado diretório.

Além disso, temos também a classe estática File, que possui os métodos ReadAllLines e WriteAllLines, utilizados para ler e escrever linhas em um arquivo, respectivamente. O primeiro método retorna um array de strings, e o segundo recebe também um array de strings.

Para os métodos que retornam array de strings, é necessário criar, alocar a memória e, finalmente, preenchê-lo. Somente depois de todo esse processo, é que iremos conseguir acessar os elementos. Em cenários onde você tem poucos arquivos/sub-diretórios, você não deverá ter maiores problemas com performance. E enquanto estiver manipulando arquivos pequenos, você também não notará um baixo desempenho, causado pelo overhead dos arrays.

A performance começa a ser degradada a partir do momento que você está extraindo informações a partir de um diretório (local ou remoto), com muitos objetos. Já no caso da manipulação do conteúdo do arquivo, podemos sofrer quando há muitas informações a serem lidas e/ou escritas. Visando amenizar esses problemas, a Microsoft inseriu novos métodos na BCL do .NET 4.0.

Agora as classes Directory e DirectoryInfo passam a ter dois novos métodos EnumerateDirectories e EnumerateFiles, que ao invés de trabalhar com arrays de strings como os métodos citados acima, passam a retornar classes que implementam a interface IEnumerable<string>. Já no caso dos métodos ReadAllLines e WriteAllLines, apenas um novo overload foi criado para suportar também o IEnumerable<string>. Todos esses métodos removem a necessidade de criar e alocar um array potencialmente grande, trabalhando sob demanda, e permitindo o acesso imediato, sem a necessidade de aguardar que o array seja criado, carregado e retornado.

Tags:

.NET Framework

WCF - MessageContracts

by Israel Aece 24. May 2009 16:32

Quando desenvolvemos serviços WCF geralmente iniciamos com a criação do contrato, suas respectivas operações e possíveis parâmetros que elas aceitam e/ou retornam. Os tipos que são enviados e recebidos por um serviço podem ser desde um tipo simples, como um DateTime, Integer ou String até mesmo tipos mais complexos, como classes que representam alguma estrutura específica da nossa aplicação.

Esses dados (simples ou complexos) que definimos no contrato serão sempre serializados e enviados como parte do body da mensagem SOAP, ficando sob responsabilidade do WCF, montar a mensagem (headers e body) de acordo com as informações que serão enviadas ao destino (body) e como o binding será configurado (headers). Mas poderemos ter situações em que esse comportamento padrão não nos atenderá, e é neste momento que entra em cena os contratos de mensagem, ou Message Contracts.

Através dos contratos de mensagem, podemos ter o controle total da estrutura da mensagem SOAP, customizando como e onde os dados que fazem parte do contrato serão acomodados dentro da mesma, já que podem fazer parte do header ou do body. Em algumas situações você precisará dessa customização, como por exemplo, quando você necessitar de uma melhor interoperabilidade com um determinado cliente, que possivelmente poderá exigir a mensagem em um formato diferente do qual o WCF cria. Além disso, você poderá ter um objeto não fará somente parte do body, mas também há propriedades dentro dele que deverão ser inseridos como elementos na coleção de headers.

Quando formos trabalhar com contratos de mensagem, além dos contratos que somos obrigados a criar em qualquer tipo de serviço (interface que representa o contrato do serviço e as classes que serão utilizadas como contratos de dados), temos também que criar as classes que representarão o contrato (formato) da mensagem que será enviada/recebida pela operação. Nesse contrato, basicamente temos propriedades que expõem tipos (complexos ou não) e que serão definidas como parte dos headers ou do body da mensagem.

Em primeiro lugar, precisamos definir que a classe será utilizada como contrato de mensagem, e para isso, decoramos ela com o atributo chamado MessageContractAttribute. É importante dizer que as classes que servem como contrato de mensagem devem, obrigatoriamente, ter um construtor sem parâmetros. Em seguida, para determinar se um campo desta classe vai fazer parte dos headers ou body, você deverá utilizar os atributos MessageHeaderAttribute ou MessageBodyMemberAttribute, respectivamente.

Para exemplificar o uso de contratos de mensagem, vamos imaginar a seguinte situação: uma aplicação poderá enviar para o serviço a relação de contas de um determinado cliente que ela quer recuperar os respectivos saldos. O serviço, por sua vez, extrairá de algum repositório o saldo atual de cada uma das contas, e retornará para o solicitante. Neste caso, teremos duas classes: CriterioDeBusca e ResultadoDaBusca.

A primeira delas representará a mensagem que o cliente irá enviar para o serviço. Essa classe disponibiliza três campos: Cliente, Codigo e Contas, utilizadas para a aplicação informar o cliente e as contas que ele deseja extrair o saldo. Já a classe ResultadoDaBusca irá representar o resultado da pesquisa, com os campos Cliente, Codigo e Saldos. A última propriedade desta classe retorna um array, onde cada elemento é representa por um objeto do tipo Saldo, que é composto pela identificação da conta e seu respectivo saldo. Abaixo temos a estrutura de cada uma delas:

[MessageContract]
public class CriterioDeBusca
{
    [MessageHeader] public string Cliente;
    [MessageHeader] public int Codigo;
    [MessageBodyMember] public string[] Contas;
}

[MessageContract(IsWrapped = true, WrapperName = "contas")]
public class ResultadoDaBusca
{
    [MessageHeader] public string Cliente;
    [MessageHeader] public int Codigo;
    [MessageBodyMember] public Saldo[] Saldos;
}

public class Saldo
{
    public string Conta { get; set; }
    public decimal Valor { get; set; }
}

Como podemos notar, as classes CriterioDeBusca e ResultadoDaBusca estão decoradas com o atributo MessageContractAttribute, que diz ao runtime do WCF que elas devem ser consideradas como a mensagem SOAP em si, e não como simples contratos de dados. Os campos destas classes também possuem os atributos para determinar o que irá compor o header e o body.

Já comentamos a finalidade de cada atributo que foi decorado nos membros acima. Apesar de eles estarem com a configuração padrão (com exceção da classe ResultadoDaBusca), ainda há uma série de propriedades que cada um deles disponibiliza, para que possamos definir algumas outras configurações que serão utilizadas pelo WCF durante o processamento/serialização da mensagem. Antes de prosseguirmos, é necessário entender cada uma dessas propriedades que podemos, opcionalmente, utilizar. As tabelas abaixo sumarizam cada uma delas.

MessageContractAttribute
Propriedade Descrição
HasProtectionLevel Recebe um valor boleano indicando se a mensagem deverá ter um nível de proteção.
IsWrapped Recebe um valor boleano indicando se o corpo da mensagem terá um elemento que servirá como wrapper. Caso True, o WCF utilizará as informações definidas nas propriedades WrapperName e WrapperNamespace para criá-lo.
ProtectionLevel Propriedade que recebe um dos valores definidos pelo enumerador ProtectionLevel, indicando se a mensagem deverá ser encriptada, assinada, ambos ou não precisará de nenhuma espécie de proteção.
WrapperName Define o elemento que servirá como wrapper para o body da mensagem. Quando omitido ou quando a propriedade IsWrapped estiver definida como False, o corpo será colocado imediatamente após o elemento <soap:Body>.
WrapperNamespace Define o namespace para o elemento que servirá como wrapper da mensagem.

MessageHeaderAttribute
Propriedade Descrição
HasProtectionLevel Recebe um valor boleano indicando se a mensagem deverá ter um nível de proteção.
Name Define o nome do elemento que será serializado. Quando omitido utilizará o nome do próprio campo.
Namespace Fornece um namespace para o header em questão e seus possíveis filhos, a menos que eles sobrescrevam isso.
ProtectionLevel Propriedade que recebe um dos valores definidos pelo enumerador ProtectionLevel, indicando se a mensagem deverá ser encriptada, assinada, ambos ou não precisará de nenhuma espécie de proteção.
Actor Define uma URI que determina a quem se destina aquele atributo.
MustUnderstand Valor boleano que indica se o ator a quem se destina aquela informação deverá ou não entendê-la. Caso esteja definido como True, e o ator não entende aquele header, uma fault deverá ser lançada.
Relay Também define um valor boleano, que indica se o header deverá ser encaminhado para o próximo nó, caso ele não seja interpretado pelo ator atual.

MessageBodyMemberAttribute
Propriedade Descrição
HasProtectionLevel Recebe um valor boleano indicando se a mensagem deverá ter um nível de proteção.
Name Define o nome do elemento que será serializado. Quando omitido utilizará o nome do próprio campo.
Namespace Fornece um namespace para o header em questões e seus possíveis filhos, a menos que eles sobrescrevam isso.
Order  Como o próprio nome diz, é uma propriedade que recebe um valor inteiro indicando a ordem de serialização de cada elemento dentro do corpo da mensagem. Quando omitido, os elementos são serializados de forma alfabética, seguido dos campos que tem essa propriedade definida explicitamente.
ProtectionLevel  Propriedade que recebe um dos valores definidos pelo enumerador ProtectionLevel, indicando se a mensagem deverá ser encriptada, assinada, ambos ou não precisará de nenhuma espécie de proteção.

Ainda temos uma especialização da classe MessageHeaderAttribute, que é a MessageHeaderArrayAttribute. Este atributo pode ser somente aplicado a membros do tipo array e que serão acomodados no header da mensagem. Decorando uma propriedade ou campo com este atributo, os elementos do array serão serializados de forma independente, ao invés de estarem envolvidos por um wrapper. Por exemplo, se tivermos um array de saldos, por padrão, ele será serializado da seguinte forma:

<header value="1" />
<saldos>
  <conta numero="123" valor="100.00" />
  <conta numero="456" valor="200.00" />
</saldos>
<header value="2" />

Já quando aplicamos o atributo MessageHeaderArrayAttribute, ele ficará da seguinte forma:

<header value="1" />
<conta numero="123" valor="100.00" />
<conta numero="456" valor="200.00" />
<header value="2" />

Depois disso, o próximo passo é a criação do contrato. Agora, ao invés de trabalharmos diretamente com os tipos mais simples e/ou complexos nos parâmetros e resultado das operações, elas devem começar a trabalhar diretamente com as classes previamente criadas. As classes que criamos para o exemplo são autoexplicativas, onde uma representa a entrada de dados e a outra o resultado, e como ambas estão decoradas como o atributo MessageContractAttribute, elas definirão a estrutura da mensagem SOAP. Abaixo está o contrato já configurado com elas:

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    ResultadoDaBusca RecuperarSaldo(CriterioDeBusca criterio);
}

Observação: Você ainda tem uma segunda alternativa para a definação de contratos de mensagem, que também é conhecida como “inline”. Neste modo, você pode decorar os parâmetros do teu contrato com os atributos MessageHeaderAttribute ou MessageBodyAttribute.

RPC vs. Messaging

Uma das idéias das aplicações orientadas a serviços é que elas devem trocar mensagens, ou seja, ao invocar uma operação, teoricamente, você deveria criar uma mensagem explicitamente e enviá-la para o seu destino, enquanto do outro lado, você deve capturá-la, analisar o seu conteúdo, e executar a tarefa que ela solicita. O contrato que vimos acima segue essa linha, já que devemos lidar com objetos que representam a mensagem. Esse tipo de formato é conhecido como Messaging.

Existe também uma outra possibilidade, conhecida como RPC (Remote Procedure Call). Neste estilo, nós trabalhamos com os serviços como se fossem componentes/classes tradicionais, ou seja, não precisamos lidar com as mensagens diretamente, pois o método irá receber e/ou retornar os tipos corretos e a tecnologia, que neste caso é o WCF, se encarregará de montar e serializar a mensagem que será enviada de um lado a outro.

O WCF não está limitado a apenas um desses estilos de mensagens, mas na maioria das vezes utilizamos o estilo RPC. Mas o tema deste artigo é justamente mostrar como podemos desenvolver serviços que utilizem o padrão Messaging, e como já vimos acima, vamos trabalhar diretamente com as classes que representarão as mensagens.

Implementação

Ambos os lados da comunicação precisarão trabalhar de forma diferente, já que não vamos mais passar ou receber os parâmetros diretamente; agora vamos lidar com uma classe que representará a resposta e uma classe que representará a requisição. Ambas já foram criadas acima, e o contrato foi desenvolvido utilizando essas duas classes.

Apesar de mudar a forma que desenvolvemos, não há muita complexidade, já que basta instanciarmos uma das classes (dependendo do contexto), abastecer as respectivas propriedades e enviá-la para o WCF, que seguirá os atributos definidos para configurar o formato da mensagem que está sendo enviada. Se analisarmos a classe que representará o serviço, veremos o contrato IContrato implementado nela, e com isso podemos analisar e compreender as classes que representam as mensagens:

public class Servico : IContrato
{
    public ResultadoDaBusca RecuperarSaldo(CriterioDeBusca criterio)
    {
        List<Saldo> saldos = new List<Saldo>(criterio.Contas.Length);
        ResultadoDaBusca resultado =
            new ResultadoDaBusca() { Cliente = criterio.Cliente, Codigo = criterio.Codigo };

        foreach (var item in criterio.Contas)
            saldos.Add(new Saldo() { Conta = item, Valor = 1.0M });

        resultado.Saldos = saldos.ToArray();
        return resultado;
    }
}

Em termos de hosting e binding, nada muda. Já do lado do cliente, ao fazer a referência devemos nos atentar a um pequeno detalhe, que é justamente a criação dos contratos de mensagem. Por padrão, quando fazer a referência através da IDE do Visual Studio ou através do utilitário svcutil.exe, ele não irá criar o proxy adequadamente, ou seja, ele sempre irá trabalhar no estilo RPC, o que nos obrigará a passar os parâmetros individualmente para cada operação.

Quando utilizamos a IDE e queremos que ele mantenha o estilo Messaging, então devemos ir até as configurações da referência do serviço, e marcar a opção “Always generate message contracts”, ou se estiver utilizando o svcutil.exe, utilize a opção /mc. Isso irá nos permitir a trabalhar de uma forma semelhante ao que vemos no exemplo abaixo:

using (ContratoClient proxy = new ContratoClient())
{
    CriterioDeBusca cb =
        new CriterioDeBusca("Israel Aece", 5, new string[] { "12345-6", "09876-2" });

    ResultadoDaBusca rb = proxy.RecuperarSaldo(cb);

    foreach (Saldo s in rb.Saldos)
        Console.WriteLine("{0}: {1:C2}", s.Conta, s.Valor);
}

Apesar de você mudar a forma que você desenvolve ou consome os serviços no estilo Messaging, não há muitas dificuldades. A grande mudança fica por parte do formato da mensagem que é criada pelo WCF, já que respeitará todos os atributos que decoramos nas classes que representarão as mensagens. Se analisarmos o tracing da mensagem que está sendo trafegada entre o serviço e o cliente, teremos um resultado como este (alguns trechos foram omitidos por questões de espaço):

<s:Envelope>
  <s:Header>
    <Action s:mustUnderstand="1">.../IContrato/RecuperarSaldoResponse</Action>
    <h:Cliente>Israel Aece</h:Cliente>
    <h:Codigo>5</h:Codigo>

    <ActivityId>dc2132ac-46c5-495d-a313-e9bb7152fe0a</ActivityId>
  </s:Header>
  <s:Body>
    <contas xmlns="http://tempuri.org/">
      <Saldos>
        <d4p1:Saldo>
          <d4p1:Conta>12345-6</d4p1:Conta>
          <d4p1:Valor>1.0</d4p1:Valor>
        </d4p1:Saldo>
        <d4p1:Saldo>
          <d4p1:Conta>09876-2</d4p1:Conta>
          <d4p1:Valor>1.0</d4p1:Valor>
        </d4p1:Saldo>
      </Saldos>
    </contas>
  </s:Body>
</s:Envelope>

Se analisarmos detalhadamente a mensagem gerada, veremos que os campos Cliente e Codigo fazem parte do header da mensagem, enquanto o array Saldos é parte do corpo da mensagem, que está envolvido (wrapped) pelo elemento <contas />.

Conclusão: Vimos no decorrer deste artigo uma nova forma de trabalhar com o WCF, utilizando o contrato de mensagens. É importante dizer que na maioria das vezes, você utilizará o estilo “tradicional”, onde você deverá recorrer ao estilo RPC, que já é o padrão. Em casos mais específicos, como aqueles que comentamos acima, então esse estilo de comunicação poderá dar uma maior flexibilidade, já que permitirá ao cliente ou ao serviço, ler ou gerar uma mensagem em um formato diferente daquele que o WCF cria automaticamente.

MessageContracts.zip (66.48 kb)

Tags: ,

Interoperabilidade | WCF

Queries compiladas

by Israel Aece 21. May 2009 16:59

Em várias aplicações é muito comum você ter que executar uma determinada query diversas vezes. Essas queries são utilizadas por diversos pontos do sistema e por diversos usuários. Quando estamos utilizando o LINQ To SQL ou o Entity Framework, a cada query que fazemos em C#/VB.NET, há uma série de procedimentos internos que são realizados para transformar/traduzir a query em T-SQL (ASTs).

Para evitar que esses procedimentos aconteçam a todo o momento, podemos recorrer a uma classe chamada CompiledQuery. Basicamente, a finalidade desta classe é otimizar a execução, que apenas efetua todos os procedimentos uma única vez, e faz o caching do “plano de execução” (em termos de entidades). E para fazer tudo isso funcionar, basta utilizar o método Compile, que esta classe disponibiliza. Esse método retorna um delegate do tipo Func<,,>, representando a query compilada, que por sua vez, especificará os tipos dos parâmetros necessários para ela, tais como, a fonte de dados (DataContext ou ObjectContext), os parâmetros de entrada e o resultado.

Vale lembrar que essa classe não é comum para ambas as tecnologias, ou seja, há uma versão dela para o Linq To SQL e outra para o Entity Framework, contidas nos namespaces System.Data.Linq e System.Data.Objects, respectivamente. O exemplo abaixo ilustra uma classe chamada DBQueries, que possui um membro estático chamado AreasDeUmaEmpresa, que representa uma query frequentemente utilizada em um determinada aplicação, e que faz uso do Linq To SQL como acesso a dados:

using System.Linq;
using System.Data.Linq;

internal static class DBQueries
{
    public static Func<DBContext, int, IEnumerable<Area>> AreasDeUmaEmpresa;

    static DBQueries()
    {
        AreasDeUmaEmpresa =
            CompiledQuery.Compile<DBContext, int, IEnumerable<Area>>(
                (db, empresaId) => from a in db.Areas where a.EmpresaID == empresaId select a);
    }
}

Abaixo está a mesma query, mas com uma ligeira modificação (não muito vísivel) para que funcione com o Entity Framework:

using System.Linq;
using System.Data.Objects;

internal static class DBQueries
{
    public static Func<DBEntities, int, IEnumerable<Area>> AreasDeUmaEmpresa;

    static DBQueries()
    {
        AreasDeUmaEmpresa =
            CompiledQuery.Compile<DBEntities, int, IEnumerable<Area>>(
                (db, empresaId) => from a in db.Areas where a.EmpresaID == empresaId select a);
    }
}

Como já sabemos, apesar de terem o mesmo nome, as classes CompiledQuery são tipos diferentes. O método Compile de cada uma delas possui uma constraint para o primeiro tipo genérico (TArg0), que identifica a fonte de dados, ou melhor, o contexto. No caso do LINQ To SQL, ele nos obrigará a definir esse primeiro parâmetro com um tipo que herde direta ou indiretamente da classe DataContext, enquanto a classe CompiledQuery utilizada pelo Entity Framework, determina que este mesmo parâmetro deve ser uma classe do tipo ObjectContext.

Em ambos os casos, armazenamos o resultado gerado pelo método Compile das respectivas classes CompiledQuery, em campos estáticos. Esse campo foi inicializado no construtor (também estático), mas talvez você possa fazer isso sob demanda. A idéia é criar um membro estático para armazenar cada query compilada, mantendo o resultado durante a execução da aplicação, reutilizando-a quando necessário.

Abaixo consta um exemplo de utilização da query compilada com LINQ To SQL. O Entity Framework seguirá a mesma idéia, apenas criando o contexto correspondente.

using (DBContext db = new DBContext())
    foreach (var item in DBQueries.AreasDeUmaEmpresa(db, 12)) { }

Queries compiladas tem uma performance consideravelmente melhor em relação as queries não compiladas, e usá-las poderá trazer bons ganhos de performance, podendo inclusive, utilizá-la em conjunto com o ConnectedDataContext. Mas também há um ponto negativo: você não conseguirá, de forma simples, projetar possíveis tipos anônimos que possam ser gerados através do select. Como tipos anônimos não podem ser propagados entre chamadas de métodos, então a única opção que nos resta é a criação de uma classe para armazenar e servir como wrapper para esse resultado.

Tags: , ,

Data

Interfaces Explícitas e o WCF

by Israel Aece 19. May 2009 12:11

O C# e o VB.NET possibilitam a implementação explícita de interfaces. Esse tipo de implementação quer dizer que ao implementar a interface em uma classe, a assinatura do membro (propriedade, evento, método, etc.) levará o “nome completo”. Para exemplificar, note o exemplo abaixo, que exibe as duas implementações (implítica e explicíta):

class Data : ILog
{
    public void WriteMessage(ILogger logger, string msg) { }
}

class IO : ILog
{
    void ILog.WriteMessage(ILogger logger, string msg) { }
}

Note que na segunda implementação, o método WriteMessage está prefixado com o nome da interface onde ele foi definido, que neste caso é ILog. A finalidade deste tipo “diferenciado” de implementação é reduzir possíveis conflitos de nomenclatura que possa haver e, principalmente, esconder a implementação da “visão” pública do tipo onde ela foi implementada. É importante dizer que este tipo de implementação não proibe o cliente de acessar os métodos; basta apenas fazer um cast da instância da classe para a interface, que o método WriteMessage estará acessível.

Esse tipo de implementação também é útil ao trabalharmos com o WCF. Quando estamos utilizando algum recurso de extensibilidade, é muito comum implementarmos as interfaces predefinidas por ele, como por exemplo IServiceBehavior, IOperationBehavior ou IExtesion<T>, para acoplar um código customizado durante a execução do serviço. Além disso, quando desejamos customizar a serialização de um tipo, também devemos implementar algumas interfaces (ISerializable ou IXmlSerializable) fornecidas pelo .NET Framework. Quando implementadas, essas interfaces são utilizadas exclusivamente pelo runtime do WCF/.NET, e em um primeiro momento, os métodos que elas expõem não serão acessados diretamente pelo desenvolvedor.

Um exemplo mais concreto disso é a classe ServiceMetadataBehavior, que implementa de forma explítica a interface IServiceBehavior. Se estivermos trabalhando com a configuração imperativa,  ao instanciar essa classe, apenas iremos visualizar as propriedades que ela disponibiliza para configuração dos metadados do serviço, e não os métodos que a interface expõe, tornando o tipo bem menos poluído e de fácil entendimento, não misturando o que deve ser acessado pelo desenvolvedor e o que deve ser acessado pelo runtime.

Tags: ,

WCF | .NET Framework

Por dentro da classe Message

by Israel Aece 17. May 2009 22:56

Em qualquer tecnologia de aplicação distribuída, há vários elementos que trabalham em conjunto para fazer tudo funcionar. Na maioria das vezes que utilizamos alguma dessas tecnologias, não nos preocupamos como o processo acontece nos bastidores. Na verdade, a idéia é essa mesma, ou seja, no primeiro momento, não há necessidade de conhecer níveis mais baixos, justamente porque a tecnologia os abstrai.

Já quando as coisas começam a ficar mais complexas, talvez seja o momento de entender o funcionamento interno de forma mais aprofundada, a fim de analisar o porque daquele determinado comportamento, ou ainda, caso você queira customizar ou interceptar algum ponto da execução. A finalidade deste artigo é demonstrar a classe Message (System.ServiceModel.Channels), responsável por representar o envelope SOAP dentro da infraestrutura do WCF.

Em vários artigos anteriores, eu mencionei a classe Message, principalmente quando falado sobre a codificação e extensibilidade da mensagem. Do lado do remetente da mensagem, haverá um encoder, responsável por recuperar a instância da classe Message, transformá-la em um stream de bytes e enviá-la para o destino através da rede. Já do lado do destinatário, o encoder captura a sequência de bytes da rede e a transforma em uma instância da classe Message novamente, para que a mesma possa ser processada.

A instância da classe Message, que no primeiro momento é criada pelo runtime,  é a forma tipada de acesso ao envelope SOAP, que traz diversas informações importantes. Entre elas temos a operação requisitada pelo cliente, seus respectivos parâmetros e dados contextuais, refletindo o nível de segurança, transações, mensagens confiáveis, etc. O protocolo SOAP é baseado em XML, e sua estrutura consiste basicamente em duas seções: headers e body. Os headers são as informações contextuais da requisição (não confunda headers do SOAP com headers de um protocolo como o HTTP); já o body é a seção dentro do SOAP que armazena as informações (parâmetros) exigidos pela operação que será executada. Abaixo temos a representação XML da classe Message:

<s:Envelope
  xmlns:a="http://www.w3.org/2005/08/addressing"
  xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://www.israelaece.com/srv/cadastrar</a:Action>
  </s:Header>
  <s:Body>
    <Program.Usuario
      xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
      <Nome>Israel Aece</Nome>
    </Program.Usuario>
  </s:Body>
</s:Envelope>

É importante dizer que nem sempre o conteúdo da classe Message vai ser representado por XML baseando-se no SOAP. Em alguns cenários, a serialização da mensagem deverá seguir um outro formato, como por exemplo, quando você expõe um serviço através de POX (Plain Old XML), eliminando a estrutura SOAP. Neste caso, a classe Message conseguirá se adaptar perfeitamente a esse formato.

Antes de analisarmos a estrutura da classe Message, devemos entender sobre alguns novos tipos que foram adicionados junto com o WCF para melhorar a serialização/deserialização da classe Message. A Microsoft adicionou três novos tipos debaixo do namespace System.Xml (Assembly System.Runtime.Serialization.dll): XmlDictionary, XmlDictionaryWriter e XmlDictionaryReader. Como o padrão XML utilizado pelo WCF possue várias características específicas, essas classes foram desenhadas exclusivamente para transformar a classe Message em um encoding específico (Text, Binary e MTOM).

Como o próprio nome diz, a classe XmlDictionary é um dicionário (chave/valor), utilizado pelo WCF para reduzir o tamanho das tags XML (não do conteúdo) que, consequentemente, irá gerar uma mensagem menor. Isso obrigará ambos os lados compartilharem o mesmo dicionário, para conseguir interpretar a mensagem.

A classe XmlDictionaryWriter trabalha diretamente com a serialização e codificação da classe Message. Ela herda da classe XmlWriter, mas ainda continua sendo uma classe abstrata. Essa classe define três métodos estáticos que recebem um stream como parâmetro (que servirá como output) e retornam as implementações concretas desta classe, sendo: CreateTextWriter, CreateBinaryWriter e CreateMtomWriter. Cada um desses métodos retornará uma classe que herda direta ou indiretamente da classe XmlDictionaryWriter, correspondendo as codificações suportadas pelo WCF, que por padrão são: Text, Binary ou MTOM, respectivamente. Ainda há um método estático chamado CreateDictionaryWriter, que serve como um facilitador quando temos um objeto do tipo XmlWriter e queremos “transformá-lo” em XmlDictionaryWriter.

De forma semelhante o XmlDictionaryWriter trabalha o XmlDictionaryReader. O XmlDictionaryReader herda diretamente da classe XmlReader, e serve como base para as classes que são responsáveis por ler o conteúdo serializado e codificado em XML. Esta classe também fornece métodos estáticos que retornam as implementações concretas de cada leitor, sendo: CreateTextReader, CreateBinaryReader, CreateMtomReader e CreateDictionaryReader. Esses métodos recebem como parâmetro um stream ou array de bytes representando o conteúdo codificado em um determinado tipo.

Além destas classes operarem com o formato de XML Documents, elas também podem manipular XML Infosets. O padrão Infosets permite expressar vários formatos de documentos XML (que possuem diferentes estruturas e regras de análise) em um formato único, definindo os elementos e atributos que o documento contém, de uma forma completamente diferente da sua representação. Isso torna tudo muito flexível, já que possibilita novos tipos de codificação. O WCF separa a serialização da codificação, o que faz com ele primeiramente serialize a classe Message em Infosets, e depois disso, codifica esses Infosets em um formato específico, utilizando os que já são fornecidos pela plataforma (Text, Binary ou MTOM), ou criando um customizado. Esse padrão também influencia no envelope SOAP gerado pelo WCF, mas vamos discutir isso mais adiante.

O exemplo a seguir mostra como podemos proceder para testar o funcionamento e analisar a geração de cada um dos tipos que vimos acima. Note que estou utilizando o método CreateMtomWriter, mas poderia ser qualquer um dos outros métodos, obviamente, alterando alguns parâmetros na chamada, de acordo com a exigência de cada um deles. Neste exemplo, ele irá serializar tudo o que estamos digitando em XML Infosets, e em seguida, codificar no padrão MTOM.

using (FileStream fs = new FileStream("Dados.mtom", FileMode.Create))
{
    using (XmlDictionaryWriter writer =
            XmlDictionaryWriter.CreateMtomWriter(fs, Encoding.UTF8, 1024, "application/xop+xml"))
    {
        writer.WriteStartElement("teste");
        writer.WriteString("algum valor");
        writer.WriteEndElement();
    }
}
 
De volta a classe Message, ela está fortemente vinculada a uma das versões do protocolo SOAP existente no mercado. Como já foi falado acima, assim como o envelope SOAP, a classe Message também possui as seções que determinam os headers e o body. As regras para ler e escrever os dados no body e nos headers são diferentes, por exemplo, os headers sempre serão buferizados na memória e podem ser acessados em qualquer ordem e quantas vezes desejar, enquanto o body pode somente ser lido uma única vez e pode ser streamed. Para maiores informações sobre a comparação entre buffered e streamed, consulte este artigo.

Na maioria das vezes, a criação da classe Message é feita pelo próprio WCF. Em alguns cenários, você pode utilizar alguns pontos de extensibilidade para interceptar a mensagem e, consequentemente, extrair e/ou gravar informações nesta classe. Também temos a possibilidade de confeccionar um contrato que aceite ou devolva instâncias das classe Message, e quando isso acontece, há algumas restrições, como por exemplo:

  • A operação não pode ter qualquer parâmetro de saída (out) ou de referência (ref).
  • Não pode haver mais do que um parâmetro. Se houver um parâmetro, ele deve ser do tipo Message ou ter o ser um contrato de mensagem.
  • O retorno de ser void, Message ou um contrato de mensagem.

Para iniciar, vamos analisar as – poucas – propriedades que essa classe fornece. Para começar, vamos analisar as propriedades Headers e Properties. A prmieira delas representa a coleção de headers existentes em uma mensagem e influenciarão no processamento dela, já que podem armazenar informações de correlação, transações, segurança, mensagens confiáveis, etc., tudo de acordo com as especificações WS-*, ou seja, esses headers são utilizados pela própria infraestrutura do WCF e ultrapassam possíveis intermediários, chegando até o seu destino final. Já a propriedade Properties expõe um dicionário de dados, e essas informações são utilizadas "localmente", não ultrapassando possíveis intermediários. O próprio WCF já utiliza isso em alguns casos como, por exemplo, nos protocolos existentes e suportados por ele. Caso o transporte seja realizado através do protocolo HTTP, os detalhes específicos da requisição/protocolo (HTTP Headers) são armazenados neste dicionário, "fora" da mensagem.

Há duas propriedades públicas boleanas, chamada de IsEmpty e IsFault. A primeira delas, indica se a mensagem está ou não vazia, enquanto a segunda, define se a mensagem é ou não uma mensagem que descreve uma falha (Fault).

Como o body da mensagem é um stream, ele pode ser lido ou escrito apenas uma única vez. Para assegurar esse comportamento, o WCF disponibiliza uma propriedade de somente-leitura chamada State. Essa propriedade retorna um dos valores definidos no enumerador MessageState: Created, Read, Written, Copied e Closed. O valor dessa propriedade vai alterando de acordo com o métodos de escrita ou leitura que você invoca a partir da instância.

Por último e não menos importante, temos a propriedade Version. O valor dessa propriedade é definida no momento da criação da mensagem e não pode ser mais alterada. Essa propriedade contém informações à respeito de qual versão do envelope SOAP e o protocolo de endereçamento (WS-Addressing) que será usado pela mensagem.

Atualmente temos duas versões de envelope SOAP: 1.1 e 1.2. Como falamos acima, a escolha do tipo de envelope traz consequências durante a criação da mensagem, ou seja, a versão 1.1 (que é a mais utilizada) é baseada na sintaxe XML, enquanto a versão 1.2 faz uso de XML Infosets.

Já o WS-Addressing é uma especificação que define um mecanismo para permitir o endereçamento e roteamento de mensagens, independentemente de qual protocolo esteja utilizando. Além de definir a estrutura de um endpoint, o WS-Addressing também adiciona vários headers no envelope SOAP, definindo para onde a mensagem está indo, como reagir a esta mensagem e como as mensagens de requisição/resposta estão vinculadas.

Para representar qual versão do SOAP e do WS-Addressing a mensagem vai utilizar, o WCF disponibiliza uma classe chamada MessageVersion. Em seu construtor, você deverá especificar a versão do SOAP e do WS-Addressing que ela irá representar. Para expor qual versão do SOAP e WS-Addressing que a instância da classe MessageVersion está armazenando, ela define duas propriedades de instância e de somente-leitura chamada Envelope (do tipo EnvelopeVersion) e Addressing (do tipo AddressingVersion).

Tanto a classe EnvelopeVersion quanto a AddressingVersion possui propriedades estáticas que retornam a instância dela mesma, pré-configurada com a versões suportadas do SOAP e WS-Addressing. Ainda possuem uma propriedade chamada None, para caso onde não se aplica o SOAP (como é o caso do AJAX/JSON) ou o WS-Addressing. Para ter uma melhor reusabilidade, a classe MessageVersion também possui várias propriedades estáticas (Default, None, Soap11, Soap11WSAddressing10, Soap11WSAddressingAugust2004, Soap12, Soap12WSAddressing10, Soap12WSAddressingAugust2004) que retornam instâncias dela mesma, pré-configurada com as combinações suportadas/mais utilizadas.

A classe OperationContext fornece uma propriedade chamada IncomingMessageVersion, que retorna uma instância da classe MessageVersion, para obter a versão da mensagem que está chegando até o serviço/cliente. Você pode utilizar essa propriedade para criar uma nova mensagem baseando-se na mesma versão da outra parte.

Criando a classe Message

Você não pode criar diretamente a instância da classe Message, pois ela não tem um construtor público. Ao invés disso, ela fornece um método estático chamado CreateMessage que, por sua vez, fornece vários overloads. Obrigatoriamente os overloads deste método recebem, entre os vários parâmetros, uma instância da classe MessageVersion, especificando qual a versão de SOAP e do WS-Addressing que aquela mensagem utilizará. Grande parte destes overloads também recebem uma string, representando a Action da mensagem, informação que o WCF confia e a utiliza para determinar para qual método ele deverá entregar a mensagem.

O overload mais simples recebe apenas a versão e a action, criando uma mensagem com o corpo vazio. Já outro overload recebe também um object, que cria a mensagem serializando aquele objeto como corpo da mensagem. Nestes casos, mensagem utilizará o DataContractSerializer com as configurações padrão para efetuar a serialização do objeto. Caso desejar mudar isso, você pode utilizar um overload específico, que recebe como parâmetro um objeto do tipo XmlObjectSerializer.

O código abaixo ilustra um exemplo simples de criação da classe Message. A finalidade do código é criar uma mensagem que armazena a instância da classe Usuario que possui apenas uma propriedade pública chamada Nome. Inicialmente criamos a instância da classe Message através do overload do método CreateMessage, que recebe a versão (SOAP e WS-Addressing), uma Action e o body. A instância da classe XmlDictionaryWriter recebe em seu construtor um objeto do tipo FileStream, para que o resultado seja armazenado em um arquivo físico. Na sequência, invocamos o método WriteMessage a partir da classe Message, passando a instância do XmlDictionaryWriter, que serializará a mensagem neste objeto.

using (Message msg = Message.CreateMessage(
    MessageVersion.Default, "Cadastrar", new Usuario() { Nome = "Israel" }))
{
    Console.WriteLine(msg.State); //Created

    using (FileStream fs = new FileStream("Message.xml", FileMode.Create))
        using (XmlDictionaryWriter xml = XmlDictionaryWriter.CreateTextWriter(fs))
            msg.WriteMessage(xml);

    Console.WriteLine(msg.State); //Written
}

O método WriteMessage é um dos métodos de escrita que a classe Message disponibiliza. Há outras versões que nos dará um maior controle em como as “partes” da mensagem são escritas. Esses métodos autoexplicativos são: WriteBody, WriteBodyContents, WriteStartBody, WriteStartEnvelope e WriteStartHeaders e todos eles recebem como parâmetro uma instância da classe XmlDictionaryWriter.

Lendo a classe Message

Um dos overloads do método CreateMessage recebe uma instância da classe XmlDictionaryReader. Esse overload é utilizado quando queremos fazer o processo inverso, ou seja, temos a mensagem serializada em algum local (como um arquivo no disco), e desejamos transformá-la novamente em uma classe Message. Além do XmlDictionaryReader, ainda precisamos informar ao método CreateMessage um número inteiro e uma instância da classe MessageVersion.

O número inteiro permite controlar o tamanho máximo do header da mensagem, já que ele é buferizado. A versão da mensagem deve sempre refletir a mesma versão usada quando ela foi serializada. O código abaixo ilustra como podemos recuperar o conteúdo que foi salvo no arquivo “Message.xml” (através do exemplo anterior), e transformá-lo novamente em uma instância da classe Message. Note que utilizamos o método CreateTextReader para criar o XmlDictionaryReader, seguindo o mesmo exemplo acima:

using (FileStream fs = new FileStream("Message.xml", FileMode.Open))
{
    using (XmlDictionaryReader xml =
        XmlDictionaryReader.CreateTextReader(fs, XmlDictionaryReaderQuotas.Max))
    {
        using (Message msg = Message.CreateMessage(xml, 1024, MessageVersion.Default))
        {
            Usuario u = msg.GetBody<Usuario>();
        }
    }
}

Uma vez criado a instância da classe Message, precisamos saber como devemos proceder para extrair o seu conteúdo, ou melhor, extrair o que está contido no body da mensagem. Basicamente temos dois métodos: GetBody<T> e GetReaderAtBodyContents. O primeiro deles, recebe um parâmetro genérico, utilizado pelo mesmo para deserializar o body da mensagem neste tipo especificado, utilizando o DataContractSerializer. Há um overload deste método que aceita uma instância da classe XmlObjectSerializer, que te permitirá customizar o mecanismo de deserialização. Já o método GetReaderAtBodyContents retorna um novo XmlDictionaryReader, posicionado no elemento body do envelope SOAP.

Independentemente de qual dos métodos utilize para ler o body da mensagem, você poderá chamar apenas uma única vez. Chamando duas vezes um dos métodos de leitura, uma exceção do tipo InvalidOperationException será disparada. Em situações onde você precisa processar o body múltiplas vezes, então você precisará criar uma cópia buferizada da mensagem. Para isso, a classe Message fornece um método chamado CreateBufferedCopy que retorna uma instância da classe MessageBuffer. A instância dessa classe representa a mensagem em memória, disponibilizando um método chamado CreateMessage, que retorna uma cópia idêntica da mensagem original. O exemplo abaixo ilustra o seu uso:

using (Message msg = Message.CreateMessage(xml, 1024, MessageVersion.Default))
{
    MessageBuffer mb = msg.CreateBufferedCopy(int.MaxValue);
    Console.WriteLine(mb.CreateMessage().GetBody<Usuario>().Nome);
    Console.WriteLine(mb.CreateMessage().GetBody<Usuario>().Nome);
}

Criando Fault Messages

O método CreateMessage ainda possui dois overloads que permitem a criação de uma instância da classe Message que representa uma mensagem de falha. Um desses overloads aceita como parâmetro uma instância da classe FaultCode e o outro overload recebe uma instância da classe MessageFault. O código abaixo ilustra a criação de uma MessageFault, e em seguida utilizamos o método CreateMessage passando esta fault criada:

MessageFault mf =
    MessageFault.CreateFault(new FaultCode("Receiver"), new FaultReason("Dados inválidos"));

using (Message msg = Message.CreateMessage(MessageVersion.Default, mf, "Cadastrar"))
{
    //…
}

Ao chamar a propriedade IsFault em cima da instância da classe Message criada acima, o valor retornado será True. Ao visualizar o resultado gerado pelo código acima, teremos:

<s:Envelope
  xmlns:s="http://www.w3.org/2003/05/soap-envelope"
  xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">Cadastrar</a:Action>
  </s:Header>
  <s:Body>
    <s:Fault>
      <s:Code>
        <s:Value>s:Receiver</s:Value>
      </s:Code>
      <s:Reason>
        <s:Text xml:lang="en-US">Dados inválidos</s:Text>
      </s:Reason>
    </s:Fault>
  </s:Body>
</s:Envelope>

Headers e Properties

A propriedade Headers é uma coleção, enquanto a propriedade Properties é um dicionário e podem ser manipuladas através de métodos como Add, Insert, Remove, etc. Para maiores detalhes sobre a finalidade de cada uma dessas propriedades, consulte este artigo.

Conclusão: Neste artigo vimos como podemos proceder para a criação, leitura e manipulação de uma mensagem, que é utilizada por serviços WCF. É importante conhecer esses detalhes, principalmente quando você desejar interceptar algum ponto durante a execução do serviço. Além disso, é provável que em algum momento você precisará customizar a criação e/ou leitura da mensagem, e para isso, você deverá manipular instâncias da classe Message, ao invés de deixar o WCF criá-las.

Tags: ,

WCF

ClientAccessPolicy.xml sem IIS

by Israel Aece 15. May 2009 11:50

Um problema que sempre ocorre quando estamos tentando consumir um serviço WCF no Silverlight, é a ausência do arquivo ClientAccessPolicy.xml dentro do diretório onde está hospedado o serviço. Para que ele seja consumido por uma aplicação Silverlight, você deve criar esse arquivo na raiz do serviço, com a mesma estrutura definida neste artigo. Esse arquivo permite que o Silverlight invoque serviços a partir de um dominío diferente de onde ele reside.

Mas, por algum motivo, você está criando uma aplicação Console ou até mesmo um Windows Service, que servirá como o host para o serviço. Lá você configura a classe ServiceHost expondo o serviço através do BasicHttpBinding. Com isso você já consegue referenciá-lo no Silverlight, mas quando executar a aplicação, você receberá um erro de comunicação. Sabemos que é a ausência do arquivo acima mencionado, mas como estamos utilizando um host que não é o IIS, como devemos proceder para disponibilizar esse arquivo?

O Carlos Figueira criou uma solução interessante para isso, onde definiu uma interface que, quando implementada na classe que representa o serviço, gera a estrutura do arquivo ClientAccessPolicy.xml (ali está também suportando o padrão de arquivo necessário para que o Flash se comunique com o serviço WCF). Com o uso do atributo WebGetAttribute, ele configura a propriedade UriTemplate, redirecionando as requisições de cada um desses arquivos para o método correspondente.

Tags: , , ,

WCF

Migrando de ASMX para WCF

by Israel Aece 12. May 2009 20:25

Junto com a primeira versão do Visual Studio .NET e do .NET Framework, temos a possibilidade de criarmos serviços Web, baseados em XML e utilizando a tecnologia ASP.NET Web Services (ASMX). Isso ainda continua disponível nas templates de projeto da versão mais atual do Visual Studio .NET, mas, para a criação de novos projetos, ou melhor, de novos serviços, o ideal é recorrer ao WCF – Windows Communication Foundation.

De qualquer forma, os ASP.NET Web Services já existem há algum tempo e há muitas aplicações que ainda o utilizam, e este artigo ajudará a entender melhor as diferenças entre ASMX e o WCF, desde a sua estrutura de projeto até detalhes relacionados à execução do mesmo. Cada uma das seções a seguir irá analisar e discutir essas mudanças, falando também sobre alguns detalhes importantes que, se não se atentar, poderá ter um comportamento “estranho” durante a execução.

Templates e Estrutura de Projeto

Quando você opta por criar um projeto ASMX, então você deve recorrer à template ASP.NET Web Service Application. Ao criar esse tipo de projeto, você poderá adicionar dentro dele arquivos com extensão *.asmx. Esse tipo de arquivo representará um serviço. Dentro desta classe, teremos os métodos que queremos publicar. Vale lembrar que o modificador de acesso do método (public, private, etc.) não tem validade. O que determinará a visibilidade do método é se ele estiver ou não decorado com o atributo WebMethodAttribute.

Além disso, a classe que representa o serviço pode, opcionalmente, herdar da classe WebService. Essa classe fornece acesso direto aos objetos ASP.NET, como Application e Session. Assim como nas aplicações ASP.NET tradicionais (UI), o arquivo ASMX apenas possui uma diretiva chamada @WebService, que define alguns atributos utilizados pelo compilador do ASP.NET. As classes necessárias para trabalhar com o ASMX estão contidas no Assembly System.Web.Services.dll e debaixo do namespace System.Web.Service.

Já com o WCF, trabalhamos de forma bem parecida. Para trabalhar com ele, é necessário que você, ao criar um novo projeto, escolha a template WCF Service Application. Neste projeto, adicionaremos arquivos com extensão *.svc, que representará o serviço. Esse tipo de arquivo também possui uma diretiva própria, chamada de @ServiceHost, que também leva informações ao compilador do ASP.NET. As classes necessárias para trabalhar com o WCF estão contidas no Assembly System.ServiceModel.dll e debaixo do namespace System.ServiceModel.

Apesar de existirem templates distintas para cada uma das tecnologias, isso não quer dizer que você está obrigado a criar um projeto específico para os teus serviços. Caso você já possua uma aplicação ASP.NET criada, é perfeitamente possível adicionar arquivos com extensão *.asmx ou *.svc neste projeto. Uma aplicação ASP.NET (UI) consegue coexistir com qualquer uma dessas tecnologias.

Contratos

O ASMX irá se basear nos métodos decorados com o atributo WebMethodAttribute para gerar o documento WSDL. Você deverá controlar a visibilidade dos teus métodos adicionando um removendo este atributo. Qualquer tipo complexo referenciado nos métodos, será automaticamente inserido na descrição do serviço sem nenhuma configuração extra.

Já o WCF trabalha de forma bem diferente. Ele utiliza interfaces para determinar os contratos que o serviço possui. Essas interfaces são aquelas tradicionais, que já utilizamos no nosso dia-à-dia, mas decorada com um atributo chamado ServiceContractAttribute. Dentro das interfaces teremos os métodos, e controlamos se eles serão ou não expostos através do atributo OperationContractAttribute.

Em relação a tipos complexos vinculados ao contrato do serviço, a exposição deles será determinado pela versão do WCF que está utilizando. Se estiver utilizando a versão 3.0, então você precisará decorar essas classes com o atributo DataContractAttribute, e para cada propriedade que desejar expor, decorá-la com o atributo DataMemberAttribute (opt-in). Com o Service Pack 1 para o .NET Framework 3.5, esses parâmetros são opcionais (opt-out), tornando-os POCOs. Mas há um detalhe: quando você decorar a classe com o atributo DataContractAttribute, então você deverá explicitamente determinar quais propriedades deseja disponibilizar, utilizando o atributo DataMemberAttribute.

Há um outro detalhe importante durante a execução das operações. Quando você utiliza ASMX, ele concatena o namespace com o nome da mensagem para determinar o Action. O WCF concatena o namespace, o nome do serviço e o nome da operação. Para manter compatibilidade com possível clientes ASMX, você deve manter a mesma fórmula, e para isso, pode recoorer a propriedade Action do atributo OperationContextAttribute.

Serialização/Deserialização

A serialização e deserializaão estão condicionadas ao serializador que cada tecnologia utiliza. O ASMX utiliza o XmlSerializer (System.Xml.Serialization) para transformar os objetos em XML e vice-versa. O XmlSerializer serializa todos os membros públicos (propriedades e campos), sem a necessidade de definir algum atributo. Você ainda pode controlar como essa serialização será realizada, utilizando vários atributos que existem debaixo deste mesmo namespace. Para maiores detalhes sobre o funcionamento do XmlSerializer e dos atributos, consulte este artigo.

O WCF, por outro lado, utiliza o serializador DataContractSerializer por padrão. Este serializador trabalha de forma semelhante ao XmlSerializer, com poucos diferenças. Entre essas diferenças temos o entendimento por parte do DataContractSerializer do atributo SerializableAttribute, para manter a compatibilidade com objetos que foram criados para serem utilizados pelo .NET Remoting. Além disso, uma outra diferença é a capacidade que este serializador tem de persitir também membros definidos como private e protected. Este serializador ainda gera um XML mais simplificado, melhorando a interoperabilidade entre as plataformas. Se desejar utilizar o XmlSerializer, então basta decorar o seu contrato com o atributo XmlSerializerFormatAttribute. Somente utilize o XmlSerializer para cenários onde você precisa ter controle total sob como o XML é gerado.

Ainda temos um terceiro serializador que é o NetDataContractSerializer. A diferença em relação ao DataContractSerializer é que ele armazena no XML gerado, informações a respeito do tipo (CLR), como versão, assembly e full name. Este serializador é rico nas informações referente ao tipo, ele compartilha tipos, ao contrário do DataContractSerializer, que compartilha contratos. Este ainda possui baixa interoperabilidade, e pode ser utilizado em cenários onde se tem .NET Remoting.

Protocolo/Hosting

O ASMX pode somente ser hospedado no IIS, utilizando o protocolo HTTP/HTTPS. Já o WCF tem uma arquitetura muito mais flexível e é independente do protocolo, ou seja, ele pode rodar em HTTP, HTTPS, TCP, MSMQ, etc. Isso quer dizer que ao construir um serviço através do WCF, você não deve mencionar e/ou confiar em nenhum momento que o mesmo será exposto através de um determinado protocolo, já que você não conseguirá garantir isso.

O WCF também pode utilizar o IIS como host. Mas além dele, podemos fazer uso de outras aplicações para expor um serviço, como um Windows Service, ou ainda, uma simples aplicação Windows/Console. Para mais detalhes sobre cada um dos tipos de host, consulte este artigo.

Extensibilidade

O ASMX permite você interceptar as requisições através de SOAP Extensions. Com elas, podemos acoplar um código customizado no processamento da mensagem, efetuando logs, tratando a autenticação/autorização, etc. A estrutura para a criação de um extensão é simples: basta herdar da classe SoapExtension e implementar o método ProcessMessage. Como parâmetro, este método traz uma propriedade chamada Stage, que podemos identificar o estágio do processamento da mensagem. Ela nos fornece quatro opções auto-explicativas: BeforeSerialize, AfterSerialize, BeforeDeserialize e AfterDeserialize. Para utilizá-la, basta criar um atributo que herda de SoapExtensionAttribute, e sobrescrever o método ExtensionType.

O WCF traz inúmeros pontos de extensibilidade tanto do lado do serviço quanto do cliente. Através destes pontos, podemos interceptar e inspecionar os parâmetros que estão sendo enviados, a escolha da operação a ser disparada, a criação da instância da classe que representa o serviço, a serialização e deserialização da mensagem (é o que a classe SoapExtension faz), entre outros. O WCF fornece vários tipos (interfaces e classes) para você customizar o que for necessário. Para entender detalhadamente sobre todos as possibilidades que temos, consulte este artigo.

Segurança

O ASMX pode confiar somente na segurança baseada no transporte, ou seja, ele somente será seguro se você expor o serviço através de HTTPS. Você somente conseguirá abrir mão do HTTPS se utilizar a segurança baseada na mensagem, que está disponível no ASMX através do WSE – Web Services Enhancements. Muitas vezes se utiliza um SoapHeader com usuário e senha. Isso somente terá alguma segurança se utilizar HTTPS ou segurança em nível de mensagem. Do contrário, qualquer um que intercepte a requisição, conseguirá extrair o conteúdo da mensagem e seus respectivos headers.

Como já era de se esperar, o WCF fornece ambos níveis de segurança nativamente. São configurações que você realiza (de forma imperativa ou declarativa), e que o serviço utilizará para efetuar a autenticação e autorização do cliente. Uma das grandes dificuldades que o pessoal encontra ao utilizar o WCF, é que se configurar o WCF para autenticar o cliente através de usuário e senha, ainda assim será necessário utilizar um certificado para garantir a segurança.

Configuração

O ASMX possibilita que algumas configurações sejam feitas de forma declarativa, ou seja, aquela que é realizada através do arquivo Web.config. Entre essas configurações, temos a possibilidade de definir as SoapExtesions, página de ajuda/WSDL customizada, os protocolos que podem ser utilizados para acessar o serviço (HttpSoap, HttpPost e HttpGet) e mais algumas outras.

No WCF, a seção de configurações são extremamente ricas. Grande parte de tudo que utilizamos no WCF pode ser configurado a partir do arquivo de configuração. Segurança, transações, know types, behaviors, bindings, endpoints, contratos, etc. O arquivo de configuração é tão rico que as vezes chega a ser complexo. A Microsoft melhorou isso no WCF 4.0, como já foi discutido neste artigo. Aqui não há muito o que se comparar, já que grande parte do que temos no WCF não existe nativamente no ASMX. Devido a isso, muitos desenvolvedores experientes na construção de serviço utilizando o ASMX, sofrem bastante quando passam a usar o WCF.

Compatibilidade com o ASP.NET

Dentro de métodos criados no ASMX, você pode tranquilamente acessar os objetos nativos do ASP.NET, como caching, Session, Application, Cookies, etc. Você pode utilizar esses repositórios para manter as informações de acordo com o contexto. Todas essas informações são disponibilizadas através da propriedade estática Current da classe HttpContext.

A configuração padrão do WCF não permite você acessar essas informações. Na verdade, isso não é uma boa prática. Se fizer uso dessas classes dentro do seu serviço, ele ficará dependente do protocolo HTTP. Se quiser expor o mesmo serviço através de TCP ou MSMQ, essas informações não terão nenhuma utilidade. De qualquer forma, se quiser manter a compatibilidade e continuar utilizando os mesmos recursos, então você deverá explicitamente habilitá-los. Para fazer isso, você deve decorar a classe que representa o serviço com o atributo AspNetCompatibilityRequirementsAttribute, definindo a propriedade RequirementsMode como Required.

Dependendo do tempo de vida e do escopo que deseja manter alguma informação, você pode recorrer a técnicas nativas do WCF, como o compartilhamento de estado, utilizando a interface IExtension<T>, como é abordado no final deste artigo.

Interoperabilidade

Há várias especificações que foram definidas por grandes players do mercado, que regem a estrutura do envelope SOAP para suportar alguma funcionalidade. Essas especificações são conhecidas como WS-* e estão divididas em várias categorias, sendo Messaging, Security, Reliable Messaging, Transaction, Metadata, entre outros. Cada uma das empresas utiliza essas especificações e as implementam em sua plataforma. Como todos seguem (teoricamente) as mesmas especificações, haverá interoperabilidade entre serviços construídos em plataformas diferentes.

O ASMX não possui nativamente suporte a elas. A Microsoft criou um Add-on para o Visual Studio .NET, chamado WSE – Web Services Enhancements, que atualmente está na versão 3.0. Ao instalá-lo, além de várias ferramentas que ele adiciona na IDE para auxiliar na configuração destes protocolos, adiciona o runtime necessário para fazer tudo isso funcionar. É importante dizer que o WCF consegue também interoperar com o WSE, já que ambos implementam os mesmos padrões.

Como esses padrões já foram implementados nativamente no WCF, não exige nenhum complemento. Todas as especificações são configuradas através do binding, podendo inclusive efetuar essa configuração através do arquivo Web.config. Antes de habilitar ou desabilitar essas funcionalidades, é importante que se tenha o devido conhecimento, para evitar qualquer problema ao até mesmo criar alguma vulnerabilidade. 

Ainda falando sobre interoperabilidade, a Microsoft se preocupou em manter os investimentos feitos com o ASMX. O WCF consegue facilmente conversar com serviços construídos em ASMX em ambos os lados, ou seja, você pode construir um serviço ASMX e consumí-lo com a infraestrutura do WCF, bem como pode construir um serviço escrito em WCF e consumí-lo com a infraestrutura do ASMX. Na verdade, quando você for criar um novo serviço, opte sempre pelo WCF. Você somente teria essa interoperabilidade entre essas tecnologias, quando desejar substituir o ASMX pelo WCF em serviços que já estão rodando, e com vários clientes dependendo do mesmo.

Internals

As requisições para arquivos *.asmx são direcionadas pelo IIS para o ISAPI do ASP.NET (aspnet_isapi.dll). Em um determinado momento, o ISAPI direciona a requisição do pedido para o código gerenciado. As requisições para estes arquivos tem como alvo um dos handlers WebServiceHandlerFactory ou ScriptHandlerFactory. A primeira delas, se baseando no arquivo requisitado, construirá dinamicamente a classe que representa o serviço. Já a classe ScriptHandlerFactory construirá o handler baseando-se em uma requisição REST/AJAX.

O WCF também utiliza o pipeline do ASP.NET, e quando a requisição é entregue pelo IIS para o ASP.NET, ele verifica se trata-se de uma requisição para um arquivo *.svc. Caso positivo, o handler responsável pelo processamento do pedido é o HttpHandler (System.ServiceModel.Activation). Internamente o WCF faz o processamento síncrono da operação, não liberando a thread do ASP.NET, que ficará bloqueada até a finalização da operação. Para entender melhor esse problema e também como melhorá-lo, você pode recorrer a este artigo.

Deployment

Assim como qualquer aplicativo .NET, basta mover os serviços para o IIS remoto e tudo já funciona. Obviamente que você deverá se certificar que você tenha a mesma versão do .NET Framework (isso inclui o Service Packs) instalada no servidor. É importante dizer que ambas tecnologias necessitam de um diretório virtual devidamente criado no IIS, com as permissões também configuradas. Apenas atente-se ao WCF, que tem um pequeno bug quando você opta pela pré-compilação do projeto.

Cliente

Dentro do Visual Studio .NET você tem duas opções para referenciar um serviço: “Add Web Reference” e “Add Service Reference”. Podemos dizer que com a primeira opção, você deve utilizar quando for referenciar um serviço ASMX em uma aplicação; já com a segunda opção, você deve utilizar quando você for referenciar um serviço WCF em uma aplicação. Quando fizer isso, a IDE criará o proxy utilizando a API da tecnologia correspondente.

Isso não quer dizer que você precisa seguir sempre esta regra. Você pode referenciar um serviço ASMX na opção “Add Service Reference”. Ao fazer isso, toda a estrutura do lado do cliente, será criada utilizando a API do WCF, ou seja, o proxy será baseado na classe ClientBase<TChannel> ao invés da SoapHttpClientProtocol, e toda a configuração do endpoint do serviço será colocada no arquivo Web.config. O WCF criou um binding chamado BasicHttpBinding para a interoperabilidade com o ASMX, que você pode utilizar para interagir com o serviço criado através do ASMX, sem maiores problemas.

Conclusão: Como vimos neste artigo, há vários detalhes que precisamos nos atentar na construção ou migração de um serviço escrito em ASMX para WCF. Para efeito de compatibilidade, algumas funcionalidades continuam trabalhando da mesma forma, enquanto para outras há um jeito diferente de fazer, e que na maioria das vezes, acaba sendo mais flexível. O artigo também não aborda detalhadamente cada uma das funcionalidades do WCF. Se desejar construir um serviço utilizando WCF, ou se quiser entender mais detalhadamente cada uma das funcionalidades abordadas aqui, pode consultar este artigo, que no final dele, há uma relação com os principais artigos que falam sobre essas funcionalidades.

Tags: , , , ,

WCF | CSD | Interoperabilidade | Security

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host