WCF - Transferência e Codificação de Dados

by Israel Aece 29. August 2008 10:06

Um dos maiores benefícios que serviços da Web tem em relação à tecnologias de comunicação distribuídas é o uso de XML como base da codificação, permitindo assim, a interoperabilidade entre as mais diversas plataformas. Entretanto, a escolha do padrão de codificação e a forma de transferência destas informações a serem adotados pelo serviço influenciará diretamente na performance e interoperabilidade do mesmo e também daqueles que o consomem.

O WCF - Windows Communication Foundation - fornece algumas técnicas e alternativas que podemos adotar durante a configuração ou criação de um determinado serviço. A finalidade deste artigo é mostrar como implementar tal configuração/criação e analisar os impactos (inclusive a nível de contrato), benefícios e restrições de cada uma destas técnicas.

Quando desenhamos um contrato para um serviço, definimos alguns métodos para satisfazer as regras de negócio que ele se propõe a fazer. Por padrão, para cada método criado, o WCF cria duas versões do método (mais popularmente conhecido como mensagens), uma para requisição (Request) e outro para resposta (Response). Cada mensagem é representada por um envelope SOAP que, por sua vez, contém o cabeçalho (header) e corpo (body). Enquanto o cabeçalho traz informações a respeito da segurança, correlação, contexto, etc., o corpo da mensagem é responsável por conter os dados que estão sendo enviados para um serviço ou quando a resposta de um serviço está sendo retornada para o cliente.

As informações contidas no cabeçalho podem ser utilizadas pelo destinatário final bem como por algum intermediário, caso exista. Já o conteúdo do corpo da mensagem, por conversão, deve ser utilizado pelo último recebedor, ou melhor, o destinatário final, independente de quantos firewalls ou roteadores existam.

Quando temos um contrato que expõe métodos que recebem ou devolvem tipos de dados simples, como strings, números ou até mesmo objetos complexos, mas que disponibiliza propriedades também simples, não vemos muitos problemas em termos de performance. O problema começa a aparecer quando o serviço que estamos desenhando irá aceitar um conteúdo mais "pesado", como é o caso de arquivos, imagens, multimídia, etc. Neste caso, se não nos atentarmos a alguns detalhes, podemos sofrer grandes penalidades na performance do serviço.

O padrão Text (texto estruturado) consiste em codificar os dados em um formato legível e, sendo assim, é possível ler o seu conteúdo em qualquer editor de texto, e qualquer sistema será capaz de processar tal texto. Com o padrão Text, além de cada item estar envolvido por um elemento ou atributo, dados "não-texto" serão convertidos para texto antes de serem enviados, contribuindo para aumentar o tamanho das mensagens que trafegam pela rede. Para um serviço mais simples, o overhead é mínimo. Mas e quando precisamos transferir grandes quantidades de dados, como por exemplo: imagens, multimídia, etc. (representados por array de bytes)?

O comportamento atual do WCF faz com que ele aplique a codificação Base64 a um conteúdo binário se desejarmos continuar utilizando o formato Text, resultando assim em um texto mais compactado mas, mesmo assim, ainda será um conteúdo muito maior em relação ao seu tamanho original. Além disso, o parser do XML não trabalha muito bem com conteúdos extensos, o que poderá acarretar em problemas durante o runtime.

Em situações onde o conteúdo binário é uma exigência e não há necessidade de interoperabilidade (onde cliente e servidor fazem uso do WCF), podemos recorrer à utilização da codificação binária, que é utilizada em todos os bindings para uso em ambiente homogêneo. Falaremos mais sobre os tipos de codificação e os bindings na seção Encodings, um pouco mais abaixo.

Por último, temos ainda uma terceira alternativa que é o MTOM (Message Transmission Optimization Mechanism), que é o mesmo padrão utilizado quando enviamos a um e-mail um conteúdo binário. Este padrão aberto, regido pelo W3C, ao invés de codificar o conteúdo binário como Text, ele é enviado de forma intacta como sendo um "anexo" da mensagem, seguindo a especificação do padrão MIME (Multipurpose Internet Mail Extension). Assim como antes, qualquer texto da mensagem original será codificado como um XML Infoset, mas o conteúdo binário será representado como uma referência, apontando para o anexo MIME. Quando a mensagem chega ao seu destino, o runtime do WCF é capaz de reconstituir a mensagem original a partir do envelope SOAP e seus respectivos "anexos".

Quando fazemos o uso do MTOM, o WCF é capaz de determinar o tamanho da mensagem e, caso ela seja pequena demais, ao invés de fazer o uso do MTOM, ele opta por codificar a mensagem utilizando a codificação Base64. Já quando o conteúdo é grande e o WCF vê que terá benefícios com o uso do WCF, ele criará uma nova seção MIME e colocará o conteúdo binário lá e, no envelope SOAP, teremos uma referência para tal conteúdo. A imagem abaixo ilustra o uso do MTOM:

Figura 1 - O MTOM define externamente o conteúdo binário.

Encodings

Um encoding dentro do WCF define as regras de como será realizada a codificação dos dados, ou melhor, da mensagem. As mensagens (Request e Response) são representadas dentro da infraestrutura do WCF através de uma classe chamada Message. Temos um outro elemento importante dentro do runtime do WCF, chamado encoder, que nada mais é que uma implementação de um determinado encoding e que fará tarefas distintas dependendo do momento, a saber: do lado do remetente, o 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 seqüência de bytes da rede e a transforma em uma instância da classe Message novamente, para que a mesma possa ser processada.

Todos os padrões de codificação (encoding) que falamos acima estão fortemente relacionados ao binding e, sendo assim, isso se trata de uma configuração que pode ser realizada sem interferir na criação ou implementação do contrato. Cada um dos bindings pré-definidos dentro do .NET Framework já possui um encoding específico. A tabela abaixo ilustra as quatro opções (classes) que temos e que falamos acima:

Encoding Descrição
TextMessageEncodingBindingElement Este encoding é o padrão para todos os bindings baseados em HTTP e também é o apropriado para todos os customizados que você venha a criar e que exigem interoperabilidade. Lembrando que este encoding lê e escreve o conteúdo baseando-se no formato SOAP 1.1/1.2, em formato Text, sem nenhum tratamento específico para conteúdo binário.
MtomMessageEncodingBindingElement Este encoding fornece uma customização importante para o conteúdo binário e não é utilizado por padrão em nenhum binding.
BinaryMessageEncodingBindingElement Este encoding é o padrão para todos os bindings Net* e é a escolha ideal para quando a comunicação estiver envolvendo o WCF em ambas as partes (cliente e servidor). Este encoding utiliza o formato .NET Binary XML, que é uma representação binária da Microsoft para XML Infosets.
WebMessageEncodingBindingElement * Este encoding habilita a codificação em formato plain-text XML e JSON (JavaScript Object Notation). Este tipo de encoding é comumente utilizado quando necessitamos expor o serviço para consumo via POX, REST, RSS ou AJAX.

Você deve avaliar cuidadosamente qual destes encodings escolher. É importante lembrar que se você precisar um combinado destes detalhes, como por exemplo, suportar interoperabilidade e boa performance, você pode expor diferentes endpoints, onde cada um deles é específico para cada uma dessas características.

Todas essas classes estão contidas dentro do namespace System.ServiceModel.Channels, herdando de uma classe base chamada MessageEncodingBindingElement. A hierarquia dos encodings está sendo exibida através da imagem abaixo:

Figura 2 - Hierarquia dos encodings.

A tabela abaixo ilustra o encoding correspondente (padrão) para cada um dos bindings existentes dentro do .NET Framework:

Binding Encoding
BasicHttpBinding TextMessageEncodingBindingElement
WebHttpBinding * WebMessageEncodingBindingElement *
WSDualHttpBinding TextMessageEncodingBindingElement
WSFederationHttpBinding TextMessageEncodingBindingElement
WSHttpBinding TextMessageEncodingBindingElement
NetTcpBinding BinaryMessageEncodingBindingElement
NetPeerTcpBinding BinaryMessageEncodingBindingElement
NetNamedPipeBinding BinaryMessageEncodingBindingElement
NetMsmqBinding BinaryMessageEncodingBindingElement

* Tipos contidos dentro do Assembly System.ServiceModel.Web.dll que está disponível a partir do .NET Framework 3.5.

Agora que já sabemos quais são os encodings definidos como padrão para cada um dos bindings existentes, precisamos entender como devemos proceder para customizar e/ou alterar os encodings dos bindings existentes. Todo binding herda direta ou indiretamente de uma classe base denominada Binding. Essa classe fornece um método, também abstrato, chamado CreateBindingElements, que fornece como retorno uma coleção do tipo BindingElementCollection. A finalidade deste método, quando sobrescrito nas classes derivadas (BasicHttpBinding, NetPeerTcpBinding, etc.), é retornar uma coleção contendo todos os elementos (segurança, transação, codificação, etc.) que compõem o binding, devidamente configurados e na ordem correta.

Para alternarmos entre padrão de codificação Text ou MTOM, podemos recorrer à propriedade MessageEncoding, que aceita como parâmetro um dos itens especificados pelo enumerador WSMessageEncoding. O trecho de código abaixo ilustra como efetuar a configuração de um determinado binding para suportar o formato MTOM.

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

BasicHttpBinding b = new BasicHttpBinding();
b.MessageEncoding = WSMessageEncoding.Mtom;

Observação Importante: A propriedade MessageEncoding está disponível apenas para bindings que suportam interoperabilidade, como é o caso dos bindings HTTP. Como dissemos acima, os outros bindings são úteis e tem uma melhor performance em ambiente totalmente homogêneo.

A customização na codificação que fizemos através do código acima também é suportada à nível de configuração. Para cada um dos encodings há um elemento correspondente que podemos utilizar durante a configuração do binding no arquivo de configuração da aplicação. Os elementos são: <textMessageEncoding />, <mtomMessageEncoding />, <binaryMessageEncoding /> e <webMessageEncodingElement />. É importante lembrar que o mesmo tipo de codificação terá que ser utilizado em ambos os lados, caso contrário, uma exceção do tipo ProtocolException será atirada.

Para finalizar, você pode recorrer à técnica de logging (já abordada neste artigo) para que você consiga visualizar/interceptar a geração ou leitura do conteúdo da mensagem e analisar a sua codificação. A imagem abaixo ilustra o mesmo conteúdo sendo trafegado em um formato normal (Text) e, em seguida, fazendo o uso do MTOM.

Figura 3 - Diferenciando o padrão de codificação utilizado.

Formas de Transferência

Uma vez que determinamos qual será o binding e o encoding a serem utilizados pelo serviço, precisamos agora entender as possíveis formas de transferência de dados e como incorporá-las ao serviço. É importante dizer que as funcionalidades aqui abordadas não são válidas para todos os bindings e que, no decorrer desta seção, veremos como e onde podemos aplicar essas técnicas.

O WCF suporta quatro modos de transferência de mensagens (opções do enumerador TransferMode):

  • Buffered: Com esta opção, a mensagem é carregada totalmente na memória antes de ser enviada ao destinatário. Esta opção é a configuração padrão para todos os bindings existentes dentro do .NET Framework.

  • Streamed: Já com a opção Streamed, somente o header da mensagem será carregado em memória (não devendo ultrapassar a quantidade máxima especificada na propriedade MaxBufferSize), enquanto o body será definido como um stream, permitindo que pequenas partes de um conteúdo sejam lidas por vez. Essa configuração afetará tanto a requisição quanto a resposta.

  • StreamedRequest: Apenas aplicará o stream em uma requisição, enquanto a resposta será colocada em buffer. É uma boa opção apenas quando aceitamos Stream como parâmetro, em operações one-way.

  • StreamedResponse: Apenas aplicará o stream em uma resposta, enquanto a requisição será colocada em buffer. Esta opção é útil apenas quando o retorno do método devolve um Stream.

Para efetuar tal configuração, devemos recorrer ao uso da propriedade TransferMode que, por sua vez, aceita uma das quatro opções contidas no enumerador TransferMode. Essa propriedade está apenas exposta em bindings que suportam a técnica de streaming, e são eles: BasicHttpBinding, NetTcpBinding e NetNamedPipeBinding. O motivo de apenas estes bindings suportarem o streaming é que há algumas regras funcionais que devem ser explicitamente seguidas, como por exemplo, assinaturas digitais são definidas e computadas em cima do conteúdo da mensagem e, com a opção de stream habilitada, o conteúdo não estará totalmente à disposição para executar a tarefa de verificação da mesma. Como a opção Buffered é a padrão e não necessita nenhuma mudança, não há o que falar sobre ela a não ser como o WCF a trata internamente, como já vimos acima.

Além de restrições funcionais impostas pelo modo Streamed, ainda há algumas regras que precisamos seguir para conseguir expor um serviço como stream, e essas regras envolvem a mudança em como desenhamos o contrato de serviço. A idéia é que os métodos que irão compor o contrato devem receber ou retornar uma instância da classe Stream, contida dentro do namespace System.IO; isso irá depender se o serviço irá retornar algum conteúdo binário para o cliente ou se o cliente enviará algum conteúdo binário para o serviço.

Nestes casos (uso da classe Stream), o parâmetro ou o retorno de um método (operação) corresponderá ao corpo da mensagem. Para exemplificar o uso desta técnica em nossos serviços, podemos criar um contrato (Interface) que possui dois métodos: Upload e Download, onde recebe e retorna uma instância da classe Stream, respectivamente:

using System;
using System.IO;
using System.ServiceModel;

[ServiceContract]
public interface IImagens
{
    [OperationContract]
    Stream Download(string imagemId);

    [OperationContract]
    string Upload(Stream imagem);
}

Como podemos notar, a forma como desenhamos o contrato muda drasticamente. A sua implementação terá que saber como lidar com o Stream; no caso do método Download a idéia é, dado uma string que representa o nome do arquivo, devemos capturá-lo e retornar o mesmo através do método; já no método Upload, recebem o Stream que corresponde à imagem e salvamos a mesma no disco e retornamos o nome que é randomicamente gerado.

Observação: Por questões de espaço, a implementação do contrato não será exibida aqui, mas está contida dentro do projeto de exemplo, relacionado ao artigo.

Como mostrado e comentado acima, você terá o Stream como sendo o body da mensagem. Mas e se, em algum momento, quisermos passar alguma informação extra? Como o body é exclusivamente reservado para o conteúdo do Stream, podemos recorrer aos headers da mensagem para acomodar tais informações. Mais uma vez, isso mudará a forma como criamos o contrato, necessitando especificar, declarativamente, o contrato da mensagem, informando os headers adicionais e o corpo da mensagem, que continuará sendo o Stream. O trecho de código ilustra como devemos proceder para a criação deste contrato:

using System;
using System.IO;
using System.ServiceModel;

[MessageContract]
public class StreamData
{
    [MessageHeader]
    public string NomeDoArquivo;

    [MessageBodyMember]
    public Stream Conteudo;
}

No exemplo acima, o atributo MessageContractAttribute define que uma classe irá corresponder à mensagem SOAP que será trafegada; já o atributo MessageHeaderAttribute especifica um membro que fará parte do header da mensagem e, finalmente, o atributo MessageBodyMemberAttribute determinará o membro que será o corpo da mensagem. Com isso, a Interface que representa o contrato do serviço também sofrerá mudanças: ao invés dela lidar diretamente com Streams, ela receberá e retornará instâncias da classe StreamData. O código abaixo ilustra uma Interface bem semelhante a que utilizamos acima (IImagens), mas agora com essa nova técnica:

using System;
using System.IO;
using System.ServiceModel;

[ServiceContract]
public interface IArquivos
{
    [OperationContract]
    StreamData Download();

    [OperationContract]
    void Upload(StreamData dados);
}

Observação: Mais uma vez, por questões de espaço, a implementação do contrato não será exibida aqui, mas está contida dentro do projeto de exemplo, relacionado ao artigo.

Independente da técnica que você utilize durante a criação do contrato, um detalhe muito importante e que precisa ser comentado é em relação ao fechamento do Stream que trafega de um lado para outro. Quem será responsável por fechá-lo? Geralmente o Stream será enviado tanto do cliente para o servidor, quanto do servidor para o cliente. Quem envia o Stream não deve fechá-lo; o runtime do WCF fará isso. Já do lado de quem receberá o Stream, deverá fechá-lo depois que utilizá-lo.

Um último detalhe: quando estamos operando com dados que excedem a configuração padrão para envio e recebimento de informações, precisamos nos atentar para especificar um valor que consiga acomodar as informações que serão trocadas. Os bindings relacionados com esta técnica possuem duas propriedades que afetam diretamente o Streamed, sendo elas: MaxBufferSize e MaxReceivedMessageSize. Como essas configurações não estão contempladas dentro do contrato (WSDL) do serviço, então fica a cargo do desenvolvedor especificar um valor, diferente do padrão, caso queira aceitar um conteúdo maior do que o especificado.

O formato Streamed causa dependência de plataforma e, conseqüentemente, não é interoperável como o MTOM. Além disso, tem restrições à nível de contrato, está restrito a ser utilizado por apenas três bindings e a segurança poderá ser somente garantida pelo transporte. Já como ponto positivo, temos um consumo de memória menor e também uma melhor performance em virtude da natureza do envio. Pensando nestas características, é provável que você já consiga saber se deve ou não optar pelo uso dele.

Serialização

É importante dizer que o processo de serialização é independente da transferência (encoding). A serialização é um processo que define como os objetos serão mapeados para XML Infosets, enquanto o encoding determinará como estes XML Infosets serão escritos (em bytes) e enviados para algum dos encodings que vimos acima e que, por sua vez, ocorre em nível de transporte.

Um outro ponto importante é em relação ao contrato. Independentemente do encoding escolhido, ele não acarretará em nenhuma mudança no contrato do serviço, ao contrário do processo de serialização que, por sua vez, poderá influenciar na criação do mesmo. Como a serialização é um processo importante e um pouco extenso, ela não será abordada neste artigo.

Conclusão: Como pudemos notar no decorrer do artigo, o WCF desacopla o máximo possível a implementação da execução, permitindo que o contrato não tenha conhecimento à respeito de como o serviço está sendo exposto e consumido. Mas, em alguns casos, podemos mudar o comportamento padrão, não se resume a apenas mudar uma configuração mas sim a mudança da Interface que determina o contrato do serviço. É importante analisar cuidadosamente cada um dos encodings e os modos de transferência para saber qual deles se encaixa melhor em sua solução.

TransferenciaCodificacaoDeDados.zip (1.29 mb)

Tags: , ,

CSD | WCF

WcfTestClient

by Israel Aece 28. August 2008 11:10

O utilitário WcfTestClient.exe que está integrado ao Visual Studio .NET não precisa ser necessariamente incializado a partir de um projeto do tipo WCF Service Library que, por sua vez, tem a finalidade de gerar uma DLL contendo o contrato e implementação do serviço e, o utilitário é utilizado para testá-los, sem a necessidade de criar um host, pois ele já faz isso.

É importante dizer que podemos iniciá-lo a partir do prompt de comando do Visual Studio, passando como parametro uma Url já existente para que possamos testá-lo, sem a necessidade de criar um projeto dummy para isso. Abaixo está o exemplo:

C:\>WcfTestClient http://IADevServer/ServicoDePublicacaoDeArquivos/

Tags: ,

CSD | WCF

Integrando Windows Live ID ao ASP.NET

by Israel Aece 27. August 2008 11:11

Como todos sabem, o Windows Live ID é a evolução do Passport. Antigamente, para integrar uma aplicação ASP.NET ao sistema de autenticação do Passport, além de uma SDK que voce precisa entender e customizar, havia custos envolvidos, o que fez com que a terceira forma de autenticação suportada pelo ASP.NET não vingasse.

Atualmente, o Windows Live ID torna-se muito menos complexo e mais simples de acoplar à aplicações ASP.NET e, além disso, não é mais necessário pagar para utilizá-lo. Tudo o que precisa ser feito é um cadastro prévio, que consiste nas informações a respeito da aplicação que fará o uso do Windows Live ID. Para um passo à passo de como configurá-lo, podemos seguir o um artigo que neste endereço.

Além disso, ainda temos (ainda em CTP) o Windows Live Tools, que adiciona alguns controles na barra de ferramentas do Visual Studio .NET. Este CTP contempla, entre vários controles, os controles IDLoginView, IDLoginStatus e a classe LiveMembershipProvider. Esta última, herda diretamente da classe SqlMembershipProvider e traz a possibilidade de integrar uma credencial Live a um usuário dentro da estrutura de segurança do ASP.NET.

Tags: ,

ASP.NET | Security

WCF - Chamadas Assíncronas

by Israel Aece 20. August 2008 13:34

Muitas vezes desenvolvemos um método para desempenhar alguma tarefa e, depois de devidamente codificado, invocamos o mesmo a partir de algum ponto da aplicação. Dependendo do que este método faz, ele pode levar certo tempo para executar e, se o tempo for consideravelmente alto, podemos começar a ter problemas na aplicação, pois como a chamada é sempre realizada de síncrona, enquanto o método não retornar, a execução do sistema que faz o uso do mesmo irá congelar, aguardando o retorno do método para dar seqüência na execução.

Entretanto, para fornecer uma melhor experiência ao usuário e tornar o desempenho da aplicação muito mais performático, podemos optar por executar, uma determinada tarefa de forma assíncrona. Isso irá possibilitar o disparo de um processo custoso em uma thread de trabalho a parte da aplicação, permitindo assim que a aplicação continue funcionando enquanto o processo custoso é executado em paralelo e, quando o mesmo finalizar, podemos recuperar o resultado e exibir o mesmo ao usuário.

A finalidade deste artigo é mostrar como implementar o processamento assíncrono tanto do lado do cliente (proxy) bem como do lado do servidor (contrato) em serviços WCF. Antes de prosseguir, é necessário que estejamos familiarizados com a arquitetura de processamento assíncrono dentro da plataforma .NET. Basicamente, temos alguns métodos dentro do próprio .NET Framework que existem 3 versões para o mesmo: o primeiro deles é para uso síncrono; já os outros dois (BeginXXX e EndXXX) são para a chamada assíncrona deste mesmo processo. Convencionou-se que métodos síncronos são nomeados de acordo com a sua funcionalidade, exemplo: Read, Write; já, as versões assíncronas destes mesmos métodos são: BeginRead, EndRead, BeginWrite e EndWrite.

A versão 2.0 do ADO.NET também já traz suporte ao processamento assíncrono para tarefas comuns de acesso a dados, como a execução de queries complexas para modificação de registros no banco de dados e também quando necessitamos recuperar dados com alguma query mais complexa (mais detalhes podem ser vistos neste artigo). O suporte ao processo assíncrono não para por aí, acesso aos arquivos no disco, acesso a serviços e até mesmo os delegates trazem nativamente esse suporte.

Processamento Assíncrono no Cliente

Neste cenário, nada é necessário ser realizado durante a criação e implementação do contrato. Isso funcionará de forma muito semelhante ao que já acontecia com os artigos ASP.NET Web Services, ou seja, para cada método criado no contrato, é também criado uma versão assíncrona (BeginXXX e EndXXX) do método quando fazemos a referência ao serviço no cliente. Aqui há ligeira mudança: os métodos que dão suporte assíncrono não são implicitamente criados. A imagem abaixo ilustra o local que devemos marcar para definir a criação dos métodos que dão suporte ao processo assíncrono:

Figura 1 - Ao efetuar uma Add Service Reference, clique na opção Advanced para marcar a opção que está marcada em vermelho.

Por padrão, esta opção não está marcada. Uma vez que você opta por marcá-la, será criada uma versão assíncrona para cada método que há dentro do contrato. A partir daí o desenvolvedor que consume o proxy do serviço precisará se atentar para efetuar a chamada para o método de forma assíncrona, o que exigirá a forma em que a chamada será executada. Quando se trata um processo assíncrono, temos alguns cuidados a respeito do disparo do método: o método que dá início ao processo (BeginXXX) geralmente espera os parâmetros necessários para a execução da tarefa (caso exista), uma instância de um delegate do tipo AsyncCallback (explorado mais abaixo) e, finalmente, um object que representa uma informação "contextual".

Observação: A opção "Generate asynchronous operations" é o mesmo que especificar o parâmetro /async do utilitário svcutil.exe.

Para exemplificar, temos um contrato chamado ICliente que possui um método chamado CalcularComissao que, por sua vez, retornará um valor que representa o valor calculado das comissões. Essa tarefa trata-se de um processo que, para fins de exemplo, será encarado como uma tarefa custosa e que levará certo tempo para executar. O código abaixo ilustra o contrato a ser utilizado (note que não há nada de especial):

using System;
using System.ServiceModel;

[ServiceContract]
public interface ICliente
{
    [OperationContract]
    decimal CalcularComissao(string codigo);
}

Uma vez adicionado à referência, podemos notar que haverá três métodos a nossa disposição do lado do cliente: CalcularComissao para processo síncrono, BeginCalcularComissao e EndCalcularComissao para processamento assíncrono. Durante a geração do proxy o método BeginXXX é decorado com o atributo OperationContractAttribute que, por sua vez, tem uma propriedade chamada AsyncPattern e, neste caso, está definida como True. Com essa configuração, o runtime não invocará o método BeginCalcularComissao do contrato, mesmo porque ele não existe; na verdade o runtime irá utilizar uma thread do ThreadPool para disparar sincronamente o método definido pela propriedade Action e, que neste caso é o método CalcularComissao.

Vou supor que a chamada para o método de forma síncrona já é conhecida e está sedimentada. O foco aqui será entender o funcionamento do processo assíncrono. O ponto de partida é o método BeginCalcularComissao: ele dará início ao processo e, além de receber o callback e o object como parâmetro (como já foi dito acima), esse método também espera os parâmetros que o próprio método exige para desempenhar a tarefa a qual ele se destina a fazer. Seguindo a arquitetura de processamento assíncrono do .NET Framework, o método BeginCalcularComissoes retorna um objeto que implementa a Interface IAsyncResult; esse objeto armazena uma referência para o processo que está sendo executado em paralelo.

Como já pudemos notar, não é o método BeginXXX que retorna o resultado. Isso é uma tarefa que pertence exclusivamente ao método EndXXX. A questão é quando invocá-lo. Uma vez que você invoca o mesmo, e o processo ainda não tenha sido finalizado, o programa trava a execução, esperando pelo retorno do processo. Dependendo do quanto tempo ainda falta para finalizar, podemos cair no mesmo problema do processamento síncrono. O trecho de código abaixo ilustra como consumir o método BeginCalcularComissoes:

using (ClienteClient proxy = new ClienteClient())
{
    IAsyncResult ar = proxy.BeginCalcularComissao("123", null, null);

    //fazer algum trabalho

    decimal resultado = proxy.EndCalcularComissao(ar);
    Console.WriteLine(resultado);
}

No exemplo acima podemos ter algum benefício, pois o método BeginCalcularComissao dispara o processamento assíncrono e já devolve o controle da execução para o programa e, conseqüentemente, permite que façamos algum trabalho em paralelo enquanto o serviço calcula as comissões. O problema é que se no momento da chamada do método EndCalcularComissao (que retornará o resultado) o processo ainda não finalizou, a execução irá bloquear a execução até que o processo assíncrono retorne. Vale lembrar que essa técnica às vezes é necessária: imagine um momento em que o programa não pode continuar a sua execução, pois depende o resultado do processo assíncrono para continuar.

Há ainda uma outra possibilidade de testar o retorno do processo assíncrono, que é chamado de pooling. Essa técnica consiste em antes de invocar o método EndCalcularComissao e possivelmente bloquear a execução, podemos testar se o processo finalizou ou não. Se repararmos no exemplo de código acima, o método BeginCalcularComissao retorna um objeto que implementa a Interface IAsyncResult. Essa Interface fornece um método chamado IsCompleted que retorna um valor booleano indicando se o processo foi ou não finalizado. Isso garantirá que chamaremos o método EndCalcularComissao somente que o processo realmente finalizou. O código abaixo ilustra o uso desta propriedade:

using (ClienteClient proxy = new ClienteClient())
{
    IAsyncResult ar = proxy.BeginCalcularComissao("123", null, null);

    //fazer algum trabalho

    if (ar.IsCompleted)
    {
        decimal resultado = proxy.EndCalcularComissao(ar);
        Console.WriteLine(resultado);
    }
    else
    {
        Console.WriteLine("O processo ainda não finalizou.");
    }
}

Finalmente, a última técnica que temos para disparar um método de forma assíncrona é com a utilização de callbacks. Com esta alternativa, ao invés de ficarmos testando se o processo finalizou ou não, ao invocar o método BeginCalcularComissao, passamos uma instância da classe AsyncCallback com a referência para um método do lado do cliente que deve ser disparado com o processo assíncrono for finalizado.

Uma vez especificado o método que será utilizado como callback, não é mais necessário que você armazene o objeto que armazena a referência para o processo assíncrono (IAsyncResult), pois isso será automaticamente fornecido para o callback; além disso, ainda é importante dizer que o método de callback é executado sob a thread de trabalho, antes dela ser devolvida para o ThreadPool. Como já foi comentado acima, informamos o método a ser disparado a partir de uma instância do delegate AsyncCallback, como é mostrado no código abaixo:

private static ClienteClient _proxy;

private static void TestandoProcessoAssincronoComCallback()
{
    _proxy = new ClienteClient();
    _proxy.BeginCalcularComissao("123", new AsyncCallback(Callback), null);
    Console.ReadLine();
}

private static void Callback(IAsyncResult ar)
{
    decimal resultado = _proxy.EndCalcularComissao(ar);
    Console.WriteLine(resultado);
}

Observação: Podemos recorrer à utilização de métodos anônimos ou até mesmo das expressões lambda para evitar a criação de um método a parte para ser disparado quando o callback acontecer.

Além do tradicional modelo de chamadas assíncronas (APM), temos a possibilidade da chamada assíncrona baseada em eventos. A idéia aqui é, antes de invocar a operação, você poderá assinar à um evento que será disparado somente quando o processo assíncrono finalizar. Isso evitará de ter um trabalho manual para analisar se o processo finalizou ou não (poll, waiting, etc.). Internamente durante a geração do proxy, o código que é auto-gerado já inclui a implementação necessária para o modelo baseado em eventos.

Basicamente é criado mais uma versão do método, agora com o sufixo XXXAsync que, internamente, faz a chamada para os métodos BeginXXX/EndXXX que, como já sabemos, dispararam a operação de forma assíncrona. Além disso, um delegate do tipo EventHandler<T> também será criado para representar o callback que, quando disparado, invocará o evento do lado de quem está consumindo o serviço. Abaixo um exemplo de como efetuar a chamada assíncrona baseada em eventos:

using (ClienteClient proxy = new ClienteClient())
{
    proxy.CalcularComissaoCompleted += 
        new EventHandler<CalcularComissaoCompletedEventArgs>(proxy_CalcularComissaoCompleted);

    proxy.CalcularComissaoAsync("2");
    Console.ReadLine();
}

private static void proxy_CalcularComissaoCompleted(object sender, 
    CalcularComissaoCompletedEventArgs e)
{
    Console.WriteLine("Fim.");
}

Se notarmos a implementação interna do proxy, veremos que o método XXXAsync faz o uso do método InvokeAsync, da classe ClientBase<T>. Este método está disponível somente a partir do .NET Framework 3.5. Sendo assim, alguns detalhes durante a geração do proxy precisam ser analisados:

  • Via "Add Service Reference": se voce estiver fazendo a referencia em um projeto que esteja utilizando o .NET Framework 3.5 e voce opta pela geração dos métodos que dão suporte ao processamento assíncrono, ele também criará os tipos necessários para suportar o modelo de eventos.

  • Via svcutil.exe: neste caso voce precisará especificar através do parametro /async e, além disso, especificar a versão do .NET Framework através do parâmetro /targetClientVersion, apontando para Version30 ou, se quiser utilizar o modelo baseado em eventos, utilizar a opção Version35.

Processamento Assíncrono no Servidor

Tudo que vimos acima consiste em permitir que o cliente que consome o serviço execute o método de forma assíncrona, evitando que a aplicação não seja bloqueada enquanto o método está sendo executado. A partir de agora iremos analisar como implementar o processamento assíncrono no servidor. Isso consistirá em uma mudança considerável no contrato do serviço e que veremos mais abaixo.

A finalidade do processo assíncrono do lado do servidor é permitir aumentar a escalabilidade e a performance sem afetar os clientes que consomem o serviço. É importante dizer que os processos assíncronos do lado do cliente e do lado do servidor trabalham de forma independente, visando benefícios totalmente diferentes. A idéia do processamento assíncrono do lado do servidor visa principalmente a execução de tarefas que tem um custo alto para execução e, alguns casos comuns, são consultas a alguma informação no banco de dados, leitura/escrita de arquivos no disco, etc.

Isso permitirá a liberação da thread que está executando o serviço seja liberada enquanto o processo custoso acontece em paralelo, permitindo à você executar alguma tarefa em paralelo enquanto o processo ocorre ou até mesmo não ter threads sendo bloqueadas enquanto aguarda este processamento. Mais uma vez, você pode combinar este recurso as técnicas de processamento assíncrono que já existem dentro do .NET Framework, como é o caso do ADO.NET 2.0, classes do namespace IO, etc. Para o exemplo, iremos simular um processo custoso na base de dados.

Como já foi dita acima, o contrato do serviço mudará. Precisaremos desenhá-lo para se enquadrar no padrão de processamento assíncrono do .NET Framework que, como já sabemos, para cada método, teremos na verdade um par onde, o primeiro corresponde ao início da execução (BeginXXX) e, o segundo, corresponde à finalização do processamento (EndXXX). O código abaixo ilustra como devemos proceder para definirmos tais métodos:

using System;
using System.ServiceModel;

[ServiceContract]
public interface ICliente
{
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginRecuperar(AsyncCallback callback, object state);

    Cliente[] EndRecuperar(IAsyncResult ar);
}

O código acima ilustra exatamente os passos que devemos seguir para possibilitar a chamada assíncrona pelo WCF. Os métodos que desejamos que sejam invocados assincronamente pelo WCF devem, ao invés de ter apenas um único método para realizar a tarefa, devemos obrigatoriamente dividi-los em duas partes (métodos): BeginXXX e EndXXX. É importante dizer que apenas o método BeginXXX será decorado com o atributo OperationContractAttribute, definindo a propriedade AsyncPattern para True. Ainda seguindo o padrão do processamento assíncrono do .NET Framework, o método Begin deve receber como parâmetro um objeto do tipo AsyncCallback (que mais tarde apontará para o método EndXXX) e um Object, retornando um objeto que implementa a Interface IAsyncResult. Já o método EndXXX deverá efetivamente retornar a informação que o método destina-se a fazer e, como parâmetro, deve receber um objeto que implementa a Interface IAsyncResult.

Quando o contrato acima for exposto pelo WCF e algum cliente consumi-lo, apenas terá um único método chamado Recuperar. Como foi dito anteriormente, a idéia aqui é evitar que o cliente saiba como isso está implementado dentro do serviço. Neste caso, não faz sentido você ter "uma versão síncrona do método" e, caso tenha, por padrão, o WCF sempre irá utilizá-la.

Uma vez que o contrato esteja definido, devemos implementá-lo na classe e, conseqüentemente, expor a mesma para ser consumida. A implementação da Interface ICliente assim como qualquer outra que exige o processamento assíncrono, demanda alguns cuidados no momento da implementação em relação ao formato tradicional. Como estamos utilizando o ADO.NET 2.0 e o mesmo já traz nativamente o suporte assíncrono a execução de queries de forma assíncrona, podemos integrá-lo a execução:

using System;
using System.Threading;
using System.Data.SqlClient;
using System.Collections.Generic;

public class ServicoDeClientes : ICliente
{
    private const string SQL_CONN_STRING = "...;Asynchronous Processing=True";
    private SqlConnection _connection;
    private SqlCommand _command;

    public IAsyncResult BeginRecuperar(AsyncCallback callback, object state)
    {
        this._connection = new SqlConnection(SQL_CONN_STRING);
        this._command = new SqlCommand("SELECT Nome FROM Cliente", this._connection);

        this._connection.Open();
        return this._command.BeginExecuteReader(callback, state);
    }

    public Cliente[] EndRecuperar(IAsyncResult ar)
    {
        List<Cliente> resultado = new List<Cliente>();

        using (this._connection)
            using(this._command)
                using (SqlDataReader dr = this._command.EndExecuteReader(ar))
                    while (dr.Read())
                        resultado.Add(new Cliente() { Nome = dr.GetString(0) });

        return resultado.ToArray();
    }
}

No método que inicia o processo abrimos a conexão com a base de dados, informamos a query e, finalmente, invocamos o método BeginExecuteReader passando o callback e o object que é passado como parâmetro para o método BeginRecuperar. Finalmente, o método EndRecuperar é invocado e através dele coletamos o resultado da execução do processo pesado, preparamos o mesmo e retornamos. Este será o resultado que será encaminhado ao cliente.

O código do lado do cliente não muda em nada. Independentemente da implementação que você utilize do lado do servidor, do lado do cliente nada mudará, ou seja, mesmo que você crie dois métodos (Begin e End) para compor o processamento assíncrono, o WCF sempre disponibilizará um único método para o cliente. Ele poderá invocá-lo de forma síncrona ou assincronamente.

Conclusão: No decorrer deste artigo pudemos entender como integrar o processamento assíncrono em serviços WCF. É possível realizar essa técnica em ambos os lados (cliente e servidor), mas é importante lembrar que os mesmos trabalham de forma independente que, apesar de serem semelhantes, tem finalidade completamente diferente e, sendo assim, necessitamos entender o contexto e aplicar a implementação ideal.

ChamadasAssincronas.zip (211.70 kb)

Tags:

Async | CSD | WCF

Criação de projeto "dummy" para cliente WCF

by Israel Aece 20. August 2008 11:12

Quando estamos fazendo testes com serviços WCF, a idéia é sempre criar um serviço e consumí-lo em alguma aplicação cliente para testar se tudo corre como esperado. Mas quando o exemplo é simples, onde queremos testar apenas algumas funcionalidades, podemos utilizar uma técnica para criar o host e também, no mesmo projeto, poderíamos consumir o serviço sem a necessidade de criar um projeto dummy para isso.

As primeiras linhas consistem na criação e configuração do host e, depois que ele estiver aberto, então podemos recorrer ao uso da classe genérica ChannelFactory<TChannel> que podemos informar o contrato que estamos utilizando/testando, o ponto de acesso (endpoint) e o binding. O método CreateChannel utiliza o endpoint e o binding especificado no construtor para criar e estabelecer o canal de comunicação.

string address = "http://localhost:9388";

using (ServiceHost host = new ServiceHost(typeof(Servico), new Uri[] { new Uri(address) }))
{
    host.AddServiceEndpoint(typeof(IContrato), new BasicHttpBinding(), string.Empty);
    host.Open();

    using (ChannelFactory<IContrato> srv =
        new ChannelFactory<IContrato>(new BasicHttpBinding(), new EndpointAddress(address)))
    {
        Console.WriteLine(srv.CreateChannel().Metodo("Israel Aece"));
    }
}

Tags:

CSD | WCF

Novo comportamento do EventValidation

by Israel Aece 14. August 2008 11:17

Há algum tempo eu comentei a respeito de alguns cuidados que precisamos ter com o EventValidation. Um outro cenário em que este mesmo erro está propício a acontecer, é quando estamos dentro de uma página muito complexa ou com muitas informações e que a sua renderização demora para acontecer por inteira.

O EventValidation se baseia em um campo oculto chamado __EVENTVALIDATION para validar o controle que gerou o postback. O próprio runtime do ASP.NET embuti este controle no final da página (pouco antes da tag de fechamento do formulário (</form>)), durante o processo de renderização. O grande problema que temos aqui é que as vezes, a renderização pode ocorrer parcialmente e, caso um controle que cause postback apareça para o usuário e ele clicar, um postback será efetuado sem o envio do campo oculto __EVENTVALIDATION, pois ele ainda não foi renderizado.

O Service Pack 1 do .NET Framework 3.5 resolve isso, ou seja, agora a renderização deste campo (e de todos os outros campos ocultos "auto-gerados"), por padrão, passa a ser efetuada no ínicio do formulário, permitindo assim efetuar postbacks sem esperar a página renderizar por inteira.

E, para finalizar, é importante dizer que voce pode controlar isso através do atributo renderAllHiddenFieldsAtTopOfForm do elemento pages, no arquivo Web.config:

<pages renderAllHiddenFieldsAtTopOfForm="true|false" />

Tags:

ASP.NET | Security

Melhorias no WCF com SP1

by Israel Aece 14. August 2008 11:15

O Service Pack 1 do .NET Framework 3.5 e o Serviço Pack 1 do Visual Studio 2008 trouxeram algumas melhorias interessantes no WCF:

  • UriTemplate: agora podemos incluir um valor padrão para algum parametro que será invocado via REST. Algo como: [WebGet(UriTemplate = "RecuperarUsuarios/{quantidade=5}")].
  • Atributo DataContract/DataMember: a partir de agora não precisamos explicitamente definir quais são os objetos e propriedades que serão expostos para serviços quando estamos trabalhando com tipos complexos. Ele entende que tudo será exposto. Temos apenas que nos preocuparmos em marcar o que não queremos que seja serializado.
  • Partial Trust: possibilidade de fazer o uso do Event Log em ambiente parcialmente confiável.
  • Hosting Wizard: basicamente é a mesma funcionalidade do "Publish Web Site", mas para um serviço WCF.
  • WCF Options: quando tem um projeto do tipo Wcf Service Library na solução, nas propriedades deste projeto temos uma aba chamada WCF Options. Dentro da mesma, há uma opção chamada: Start WCF Service Host when debugging another project in the same solution. Ao marcá-la, ao rodar qualquer projeto em modo de depuração, o WCF Test Client irá iniciar para tornar o serviço disponível para que outras aplicações na mesma solução o utilizem.

Tags: , , ,

CSD | WCF

O atributo Action do WebForm

by Israel Aece 14. August 2008 11:15

Alguém havia me solicitado isso em algum momento e agora é possível.

Tags:

ASP.NET

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

Indicações

Introdução ao ASP.NET Web API - e-Book