Desenvolvimento - WCF/WPF

Internals - Proxy de serviços WCF

Para aqueles que desejam consumir um serviço (WCF ou não) através da infraestrutura do WCF que existe do lado do cliente, existem duas opções para referenciá-lo na aplicação cliente, e cada uma dessas opções fornecem suas vantagens e desvantagens. A finalidade deste artigo é tentar descrever como as coisas funcionam nos bastidores, para conseguirmos tirar o melhor proveito disso.

por Israel Aéce



Para aqueles que desejam consumir um serviço (WCF ou não) através da infraestrutura do WCF que existe do lado do cliente, existem duas opções para referenciá-lo na aplicação cliente, e cada uma dessas opções fornecem suas vantagens e desvantagens. A finalidade deste artigo é tentar descrever como as coisas funcionam nos bastidores, para conseguirmos tirar o melhor proveito disso.

A mais comum entre as formas de acesso um serviço, é a geração do proxy de forma automática, que através da IDE do Visual Studio ou do utilitário svcutil.exe, basta que eu informe o endereço do documento WSDL, e uma classe será criada com toda a estrutura necessária para gerenciar e invocar operações do serviço remoto. Nesta opção, todos os tipos que, eventualmente, o serviço exponha, serão automaticamente reconstruídos do lado do cliente, para que o mesmo seja capaz de enviar e/ou receber tais tipos.

Entre os tipos que são reconstruídos do lado do cliente, uma Interface é criada representando o contrato do serviço, contendotodas as operações que ele fornece. Além disso, uma classe também é criada, herdando da classe abstrataClientBase<TChannel>. Essa classe é a que conhecemos como "proxy". Ao herdar de ClientBase<TChannel>, o parâmetro genérico TChannel é substituído pela Interface que representa o contrato do serviço, que como vimos, foi reconstruída do lado do cliente. Além disso, a Interface do contrato do serviço também é implementada nesta classe, ou seja, ao instanciar o proxy, veremos os métodos que foram disponibilizados no serviço. Durante o tempo de execução, ao invocar esses métodos locais, o WCF encaminhará a requisição para o serviço.

Essa classe nada mais é do que um wrapper, que visa facilitar a vida de quem consome o serviço fornecendo, além dos métodos que refletem as suas operações,métodos que gerenciam a vida do canal de comunicação, tais como Open, Close, Abort, etc. Ao criar a instância desta classe ou descartá-la, uma série de tarefas custosas e complexas são realizadas, tais como: carregar as configurações do arquivo de configuração, criar toda a árvore de tipos que descreve o serviço, construção da channel stack (a complexidade depende das funcionalidades habilitadas) e o descarte dos recursos que a mesma utiliza para efetuar a comunicação.

Internamente essa classe mantém/referencia um objeto do tipo ChannelFactory<TChannel>, que é uma factory responsável por criar e configurar, efetivamente,o canal de comunicação entre o cliente e o serviço. Antes do .NET Framework 3.5 + SP1, essa classe era instanciada uma única vez, e depois é mantida viva durante a instância do proxy.Na versão mais recente, não existe mais essa afinidade, ou seja,a instância daChannelFactory<TChannel> é mantida em nível do AppDomain, e sendo assim, mesmo que você crie e descarte o proxy a todo o momento, grande parte do processo complexo e custoso, será mantido e reutilizado independentemente de quantas instâncias do proxy você venha a criar, gerando uma espécie de cache.

Essecache melhora consideravelmente o custo de inicialização, que na maioria das vezes, é sempre igual. Esse cache trabalha no formato MRU (most recently used), e está "limitado"à 32 objetos, e quando essa capacidade for atingida, os últimos vão sendo removidos. A cada instância do proxy, antes do WCF efetivamente criar a instância da classeChannelFactory<TChannel> correspondente, ele interroga o cache a procuradeste objeto (que atenda a essas mesmas características), e caso o encontre, evitará a criação de um novo.

Um cuidado especial que se deve ter é com relação as versões que temos no cache. O construtor do proxy possui vários overloads, e os objetos que estão colocados nocachesão versionados de acordo com esses parâmetros. Aqueles overloads que não aceitam um Binding como parâmetro, seguramente, objetos que estão no cache serão reutilizados, já que os demais parâmetros são imutáveis. Quando um Binding é passado como parâmetro para um dos construtores, o cachingserá desabilitado, já que não há como garantir que as informações definidas na instância do binding serão sempre as mesmas. A mesma regra se aplica quando você acessa as propriedades ChannelFactory, Endpoint e ClientCredentials do proxy antes do canal de comunicação ser criado e aberto.

Voltando as formas que temos de invocar um serviço, a segunda possibilidade é utilizar o ChannelFactory<TChannel> diretamente, sem criar o proxy por algumas das ferramentas que vimos acima. Essa técnica nos permite ter um maior controle do caching, já que essa funcionalidade ficará sob nossa responsabilidade. Você pode manter a factory em algum ponto do teu código, e quando precisar de um novo canal de comunicação com as mesmas configurações, basta invocar o método CreateChannel, que ele te retorna uma instância configurada. O ponto negativo desta técnica, é que o cliente já tem que, de alguma forma, conhecer os tipos e contratos que são expostos pelo serviço, cenário comum quando utilizamos assemblies compartilhados. O código abaixo ilustra como podemos proceder para fazer isso:

ChannelFactory<IContrato> factory =
new ChannelFactory<IContrato>(new NetTcpBinding(), "net.tcp://localhost:3922/srv");

IContrato proxy = factory.CreateChannel();
Console.WriteLine(proxy.Ping("Israel"));

Além de você já precisar conhecer os tipos, para invocar qualquer método que gerencie a vida do proxy (Open, Close, Abort, etc.), você precisará converter explicitamente para ICommunicationObject.E, para finalizar, quando não precisar mais da factory, tudo o que precisa fazer é também invocar o seu método Close, que descarta todos os recursos custosos que ela armazena.O exemplo abaixo ilustra como podemos proceder para encerrar o proxy e, eventualmente, a factory:

((ICommunicationObject)proxy).Close();

//Eventualmente
factory.Close();

Conclusão: Ambas técnicas tem seus pontos positivos e negativos. Muito do que vimos neste artigo, acaba sendo gerenciado de forma automática pelo WCF, mas conhecendo alguns detalhes internoscomo estes, nos permite identificar problemas de performance que, eventualmente, podemos estar sofrendo.
Israel Aéce

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