Desenvolvimento - C#
WCF - Internals e Extensibilidade
A finalidade deste artigo é exibir alguns detalhes internos da arquitetura da WCF e, como foco principal, conhecer os pontos de extensibilidade que ele nos fornece.
por Israel AéceO Windows Communication Foundation traz nativamente diversas funcionalidades, quais podemos combiná-las e utilizá-las para tornar o serviço mais poderoso. A arquitetura que a Microsoft criou para suportar as diferentes (talvez até distintas) características dos serviços é altamente extensível, o que nos permite acoplarmos um determinado código em algum ponto predefinido, durante a execução do serviço. As interceptações podem ser realizadas em diferentes momentos da execução, podendo ser realizada do lado do cliente ou do serviço. A finalidade deste artigo é exibir alguns detalhes internos da arquitetura da WCF e, como foco principal, conhecer os pontos de extensibilidade que ele nos fornece.
Internals
Muitas vezes, quando queremos criar e consumir um serviço, basicamente criamos o contrato, o implementamos e, finalmente, hospedamos este serviço em algum host conhecido (para saber mais sobre hosting de serviços WCF, consulte este artigo). Quando o serviço está em execução, basta referenciá-lo em uma aplicação cliente e consumi-lo. Este é um processo relativamente simples mas que, internamente, há vários elementos (classes) que fazem tudo isso funcionar corretamente. Sendo assim, antes de efetivamente começarmos a discutir as formas de extensibilidade que temos dentro do WCF, devemos compreender a finalidade de alguns dessem elementos que compõem o runtime do WCF. Com a imagem abaixo, conseguimos visualizar graficamente como ocorre a requisição à um serviço WCF.
Figura 1 - Processo de execução de um serviço WCF. |
Na imagem acima temos do lado esquerdo o cliente e do lado direito o serviço. Na imagem de exemplo, o cliente (remetente) está enviando a mensagem para o serviço (destinatário). Aqui é importante ressaltar que o eles possuem uma certa simetria, ou seja, quando o serviço envia a resposta, então ele passa a ser o remetente e o cliente o destinatário da mensagem. Dentro do runtime do WCF a mensagem é representada pela classe Message, contida no namespace System.ServiceModel.Channels, e é responsável por abstrair a mensagem SOAP, fornecendo membros de acesso as informações contidas neste envelope.
O primeiro elemento e o mais conhecido é o proxy. Quando efetuamos a referência para um serviço através da IDE do Visual Studio .NET (ou através do utilitário svcutil.exe), automaticamente ele extrai as informações do documento WSDL exposto pelo serviço, e cria uma classe com as mesmas operações definidas pelo contrato do serviço. Apesar de parecer uma classe normal, ela herda da classe ClientBase<TChannel> qual tem a finalidade de abstrair vários detalhes complexos da comunicação, tornando o consumo do serviço extremamente fácil, como se fosse uma chamada para um método local. Quando um dos métodos do proxy é invocado, ele delega a execução para uma classe chamada ChannelFactory<TChannel>, que por sua vez, é responsável por criar e gerenciar a infraestrutura necessária para que o cliente envie informações para um serviço ou, de forma mais precisa, para um endpoint.
O próximo elemento que temos na imagem é a Channel Stack e, como podemos notar, ela está presente em ambos os lados. Cada channel dentro desta stack é responsável por uma tarefa distinta, como por exemplo: a codificação dos caracteres, a conversão com o protocolo de comunicação, e o suporte às implementações dos protocolos WS-*, como é o caso de transações, mensagens confiáveis, segurança, etc. Os elementos que irão compor a Channel Stack dependerá da configuração do binding que está sendo utilizado. É através deles que "ligamos" ou "desligamos" os recursos que temos a nossa disposição. Os únicos itens que sempre irão existir são o Encoder e o Transport, responsáveis pela codificação e pelo transporte, respectivamente.
Quando o proxy gerar a mensagem (representada pela classe Message), ele a encaminhará para a Channel Stack que, por sua vez, passará por cada um dos itens ali contidos, configurando esta mesma mensagem com suas respectivas funcionalidades. Deste lado do processo, o Encoder é responsável por transformar a classe Message em bytes e, em seguida, encaminhar o resultado para o channel responsável pela conversação com o protocolo. Do lado do serviço, ao receber a mensagem, a execução dos itens acontecem em ordem inversa, passando inicialmente pelo channel do transporte que recupera os bytes e os encaminham para o Encoder agrupá-los e transformar em uma instância da classe Message.
Por último, e não menos importante, temos o Dispatcher, que está do lado do serviço. Quando criamos um host (ServiceHost), definimos as URIs para o serviço. Cada uma dessas URIs terá seu próprio listener, que é representado pela classe ChannelDispatcher. Quando uma mensagem chegar, o ChannelDispatcher interroga cada um dos objetos EndpointDispatcher (falaremos dele mais abaixo) que estão associados a ele, verificando se cada endpoint pode ou não aceitar a mensagem, passando-a para um deles. Na maioria das vezes, o ChannelDispatcher terá apenas um único EndpointDispatcher associado, mas há alguns casos em que podemos ter mais do que um.
Já o EndpointDispatcher expõe algumas informações e pontos de customização relacionados ao endpoint, que podemos utilizá-los para controlar o processo de recebimento das mensagens. Ele fornece duas propriedades chamadas AddressFilter e ContractFilter, que trabalham em conjunto para assegurar que a mensagem recebida será encaminhada para o método correto dentro da instância da classe que representa o serviço. Uma outra propriedade importante desta classe é a DispatchRuntime. Ela retorna uma instância da classe DispatchRuntime que além de ter vários pontos para customização, é responsável por selecionar o método a ser invocado no objeto que representa o serviço, serializar e deserializar os parâmetros para o método, e ainda gerenciar o tempo de vida deste objeto. A imagem abaixo exibe o relacionamento entre as classes que falamos agora:
Figura 2 - Dispatchers. |
A classe DispatchRuntime ainda possui duas propriedades que trabalham em conjunto: OperationSelector e Operations. A primeira delas, retorna a instância de uma classe que implementa a Interface IDispatchOperationSelector, que possui apenas um único método chamado SelectOperation, recebendo a mensagem atual (através da classe Message) como parâmetro e retornando uma string que representa o nome do método requisitado, e será utilizada para procurar a instância da classe DispatchOperation dentro do dicionário, armazenado na propriedade Operations. Uma vez que o método requisitado é encontrado dentro deste dicionário, uma instância da classe DispatchOperation é retornada, sendo ela a responsável por deserializar os parâmetros da mensagem, invocar o método e, finalmente, serializar o resultado na mensagem de retorno.
A arquitetura do WCF possui inúmeros pontos de extensibilidade, onde podemos acoplar um código customizado para interceptar um determinado momento. É importante dizer que nem todos esses pontos serão abordados neste artigo, justamente porque exigem uma abordagem diferenciada, como é a caso da customização da autenticação e autorização, que antes de falar efetivamente sobre essa possibilidade, devemos ter uma introdução sobre a infraestrutura de segurança fornecida pelo WCF. O artigo irá se limitar em abordar os pontos de interceptação referentes à manipulação da mensagem, suas operações e parâmetros. Mais adiante, ainda neste artigo, também iremos analisar como podemos utilizar algumas extensões para o compartilhamento de dados no proxy ou no dispatcher.
Extensibilidade da Mensagem
A extensibilidade é uma característica do WCF. Ele fornece vários pontos em que podemos injetar um código customizado, para conseguir interceptar a execução do serviço. É importante dizer que tanto o proxy quanto o dispatcher fornecem vários pontos de extensibilidade, permitindo que a customização seja feita em ambos os lados. Para ilustrar onde podemos acomodar o código customizado, vamos analisar separadamente cada um dos lados da comunicação, exibindo o local da inserção e também a finalidade, ou melhor, o que você poderá fazer naquele "contexto". Para iniciar, vamos analisar a parte do cliente, responsável por invocar um método do serviço:
Figura 3 - Pontos de extensão do lado do cliente. |
Como podemos notar, para cada operação que invocamos, um objeto do tipo ClientOperation é criado. Este objeto tem a finalidade de criar um ponto para modificar ou estender a execução de uma operação específica de um determinado contrato. Depois dele, a mensagem é encaminhada para a próxima classe, que a ClientRuntime que, assim com a anterior, possui pontos de extensão para inserir um código customizado para todas as mensagens que são enviadas e/ou recebidas pela aplicação cliente.
Os pontos de extensibilidade deste lado estão em vermelho. O primeiro deles (1) é o inspetor de parâmetros que, como o próprio nome diz, nos permite analisar os valores dos parâmetros de uma determinada operação, que estão sendo enviados pelo serviço. Isso nos permitirá inserir algum código para validar se o valor está ou não em um formato correto, seguindo alguma expressão regular, etc. Já no segundo ponto (2), temos a possibilidade de interceptar a "serialização" e formatação da mensagem, utilizando as informações que tem até o momento para criar a e abastecer as informações em uma instância da classe Message. Finalmente, temos o terceiro e último ponto de extensão que é inspeção da mensagem, sendo o último estágio em que você conseguirá analisar a mensagem antes dela ser enviada à Channel Stack. Já do lado do serviço temos:
Figura 4 - Pontos de extensão do lado do serviço. |
Como falamos acima, o cliente e o serviço possuem uma certa simetria e podemos notar isso novamente na comparação dessas duas imagens. Note que do lado do serviço os processos acontecem em ordem inversa, começando pela Channel Stack devolvendo a instância da classe Message para o DispatchRuntime (já falamos sobre ela acima). Nesta classe possuímos dois pontos para injetarmos nosso código. O primeiro deles (1) é a inspeção da mensagem que, como do lado do cliente, podemos utilizar para efetuar alguma análise da mensagem que chegou ao serviço. Na sequência, temos a seleção da operação (2), onde é o momento do dispatcher escolher o método do serviço a ser executado, baseando-se na mensagem enviada pelo cliente. Depois disso, temos o objeto DispatchOperation, qual é criado um por operação e fornecerá outros três pontos de extensibilidade, sendo a possibilidade de interceptar a "deserialização" da mensagem (3) e a inspeção dos parâmetros (4), assim como ocorre no cliente. Finalmente, e não menos importante, ainda podemos customizar o disparo da operação solicitada pelo cliente (5).
Como podemos notar, cada um dos pontos de extensibilidade estão ligados à alguma objeto que faz parte do runtime do WCF. Para injetar um código customizado em algum destes pontos que vimos nas imagens acima, é necessário criar algumas classes e implementar algumas interfaces fornecidas pelo próprio WCF. Cada uma dessas interfaces disponibilizará métodos e propriedades que, quando adicionadas à execução, invocará o código ali definido. Para cada um dos estágios de interceptação, há uma interface específica que está dentro do namespace System.ServiceModel.Dispatcher, sendo:
|
Depois de analisado cada uma das interfaces que temos à nossa disposição para extensibilidade, então é o momento de analisarmos os códigos correspondentes à cada uma delas. Por questões de espaço, irei omitir essas implementações no artigo, e colocarei apenas os comentários correspondentes a cada interface aqui. Antes de iniciar, é importante que você abra o código fonte vinculado a este artigo para que possa acompanhar as explicações. Para facilitar, a imagem abaixo exibe a estrutura do projeto de exemplo e também cada uma das customizações que foram realizadas e serão comentadas a seguir:
Figura 5 - Estrutura das extensões criadas. |
Começando de baixo para cima, vamos falar sobre a classe ParameterInspector. Como a finalidade é inspecionar os parâmetros e também o valor de retorno, ela traz dois métodos: BeforeCall e AfterCall. O primeiro deles, recebe como parâmetro o nome do método que será invocado e um array de objetos representando os parâmetros de entrada (isso será vazio quando o método não tiver parâmetros). Além destes parâmetros, este método retorna um object que falarei a seguir. Já o método AfterCall, possui em sua assinatura o nome do método, um array de objetos de saída, um object representando o resultado da operação (será nulo quando a mesma for void) e, finalmente, um object chamado correlationState, que devolverá o objeto retornado pelo método BeforeCall para correlacionar as chamadas.
Como já dissemos acima, o OperationSelector é o responsável por selecionar a operação (método) que será invocado no serviço ou no cliente e, justamente por isso, essa classe implementa as interfaces IDispatchOperationSelector e IClientOperationSelector. A primeira delas disponibiliza um método chamada SelectOperation, enviando a instância da mensagem corrente e retornando uma string, representando o método que será invocado no serviço. Já do lado do cliente, a interface IClientOperationSelector possibilita interceptar qual dos métodos do proxy que está sendo invocado. Essa interface também possui um método chamado SelectOperation, mas com uma assinatura diferente, ou seja, recebe uma instância da classe MethodBase, indicando o método solicitado pela aplicação e também um array de objetos que representa os parâmetros para o método solicitado. Esse metódo também retorna uma string que representa o método (operação) do proxy a ser executado.
A classe OperationInvoker implementa a interface IOperationInvoker, fornecendo métodos para a chamada síncrona (Invoke) ou assíncrona (InvokeBegin e InvokeEnd). Quem determinará qual dos métodos será invocado é a propriedade boleana chamada IsSynchronous que já é autoexplicativa. O método Invoke recebe como parâmetro a instância do objeto que representa o serviço, os parâmetros de entrada e saída, retornando um objeto que representa o resultado do método invocado.
MessageInspector tem a responsabilidade de inspecionar as mensagens tanto do lado do serviço quanto do cliente e, justamente por isso, implementa as interfaces IDispatchMessageInspector e IClientMessageInspector. A primeira interface disponibiliza dois metódos: AfterReceiveRequest e BeforeSendReply. Como o próprio nome diz, o primeiro método é disparado depois que o serviço recebeu a requisição do cliente; já o segundo, é disparado antes de enviar o resultado para o cliente que solicitou. A interface IClientMessageInspector também traz dois métodos: AfterReceiveReply e BeforeSendRequest, sendo o primeiro método disparado depois de receber a resposta do serviço, e o segundo antes de enviar a requisição para o serviço.
Finalmente temos a classe MessageFormatter. Assim como a classe MessageInspector, o formatador da mensagem também pode ser interceptado tanto do lado do serviço quanto do lado do cliente e, justamente por isso, implementa as duas interfaces IDispatchMessageFormatter e IClientMessageFormatter. Do lado do serviço, temos os métodos DeserializeRequest e SerializeReply, sendo o primeiro deles disparado no momento da deserialização da requisição e, o segundo, no momento da serialização da resposta para o cliente. Já a interface IClientMessageFormatter disponibiliza os métodos DeserializeReply e SerializeRequest, invocando no momento da deserialização da resposta e na serialização da requisição, respectivamente. Com a implementação dessas interfaces podemos intervir em como os parâmetros e/ou resposta será serializado ou deserialização na mensagem.
Modificando o Runtime
Depois que analisamos todos os tipos que temos para customizar um ponto de extensão dentro do WCF, precisamos saber como acoplá-los na execução do serviço. As classes que implementamos acima não trabalham por si só; elas precisam ser incluídas em algum ponto dentro da execução, seja do lado do cliente (proxy) ou do lado serviço (dispatcher).
Uma das formas que temos para efetuar este acoplamento é utilizando os behaviors customizados. Atualmente temos quatro escopos diferentes de behaviors: operation behavior, contract behavior, endpoint behavior e service behavior. O primeiro deles afetará apenas a execução de uma operação específica. Os contract behaviors nos permite aplicar algo customizado para um determinado contrato. Já os endpoint behaviors são usados exclusivamente para um endpoint. E, finalmente, os service behaviors possibilitam a execução de algo para o serviço como um todo, independentemente de quantos endpoints houverem. O WCF já traz vários behaviors criados prontos para serem utilizados e, possibilita também criarmos nossos próprios behaviors, apenas implementando as interfaces IOperationBehavior, IContractBehavior, IEndpointBehavior ou IServiceBehavior.
Cada uma dessas Interfaces está dentro do namespace System.ServiceModel.Description, e fornecem métodos com nomes semelhantes, mas com a assinatura diferenciada, disponibilizando os objetos pertinentes ao contexto atual, como por exemplo as interfaces IOperationBehavior, IContractBehavior e IEndpointBehavior possuem quatro métodos: AddBindingParameters, ApplyClientBehavior, ApplyDispatchBehavior e Validate. Todos eles não retornam nenhuma informação (void) e, apesar de ter o mesmo nome e tipo de retorno em todas as interfaces, os parâmetros dos métodos nas interfaces são diferentes, justamente para trazer informações referentes aquele behavior.
Para tornar mais claro, se analisarmos o método ApplyClientBehavior da interface IOperationBehavior, ele traz em seu parâmetro um objeto do tipo ClientOperation, enquanto neste mesmo método da interface IEndpointBehavior, ele fornece o objeto ClientRuntime. E já que estamos falando de métodos, ApplyClientBehavior e ApplyDispatchBehavior são utilizados para extrairmos e/ou adicionarmos alguma informação nesses dois importantes objetos que fazem parte do runtime do WCF. Já o método AddBindingParameters disponibiliza uma coleção contendo os parâmetros do binding que estão vinculados a execução atual e, finalmente, o método Validate que nos permite efetuar alguma validação em cima do behavior (entenda isso como validar o contrato, endpoint, operação ou o serviço como um todo).
Observação: A única diferença da interface IServiceBehavior é que ela não possui o método ApplyClientBehavior. Isso se deve ao fato de que essa interface somente se aplica a customização do serviço.
Seguindo a mesma linha de explicação que utilizei acima para descrever cada uma das implementações de extensibilidades, vou também poupar espaço aqui ao invés de colocar todas as implementações de behaviors customizados, que precisamos criar para injetar o código criado acima. A imagem a seguir também ilustra os behaviors que fazem parte da solução, e será interessante abrir o código fonte vinculado a este artigo para acompanhar as explicações.
Figura 6 - Estrutura dos behaviros customizados. |
Inicialmente vamos falar sobre a classe ParameterInspectorBehaviorAttribute. Ela implementa a interface IOperationBehavior, customizando os métodos ApplyClientBehavior e ApplyDispatchBehavior. O primeiro deles fornece um parâmetro do tipo ClientOperation que, via propriedade ParameterInspectors, podemos adicionar uma instância de uma classe que implemente a interface IParameterInspector. O método ApplyDispatchBehavior trabalha de forma semelhante, mas como será aplicado ao serviço, então como parâmetro recebe uma instância da classe DispatchOperation.
A classe OperationSelectorBehaviorAttribute implementa a interface IEndpointBehavior, customizando também os métodos ApplyClientBehavior e ApplyDispatchBehavior. Ambos métodos expõe direta ao indiretamente os objetos ClientRuntime e DispatchRuntime respectivamente, e ambos oferecem uma propriedade chamada OperationSelector, onde podemos customizar como será efetuada a seleção da operação. Nos dois casos, estaremos definindo nesta propriedade a instância da classe OperationSelector, que criamos previamente.
Na sequência, temos a classe OperationInvokerBehaviorAttribute que implementa a interface IOperationBehavior, mas customizará apenas o lado do serviço. O método ApplyDispatchBehavior fornece uma instância da classe DispatchOperation que, por sua vez, disponibiliza uma propriedade chamada Invoker, onde definiremos uma classe que implemente a interface IOperationInvoker, que no nosso caso é a classe OperationInvoker, para customizarmos como a operação será invocada. E, finalmente, temos as classes MessageInspectorBehaviorAttribute e MessageFormatterBehaviorAttribute. A primeira responsável por inserir uma instância da classe MessageInspector na execução do serviço e do cliente, implementando as interfaces IEndpointBehavior e IServiceBehavior. E, para encerrar, a classe MessageFormatterBehaviorAttribute implementa a interface IEndpointBehavior, para definir uma instância da classe MessageFormatter para as operações tanto do lado do serviço (DispatchOperation) quanto do lado do cliente (ClientOperation), através da propriedade Formatter.
Todas essas classes implementam uma das interfaces de behaviors customizados, mas ainda não trabalham por si só. Ainda precisamos acoplar as instâncias dessas classes recém criados em algum ponto da execução. Se você notar, todas as classes herdam da classe Attribute. Isso se deve ao fato de que, podemos utilizar a programação declarativa, para adicionarmos essas classes à execução do WCF. Durante a execução, o WCF utiliza Reflection para extrair o behavior configurado via atributo e o adicioná-lo no local correto. Desta forma, a classe que representa o serviço já está decorada com alguns atributos criados, como podemos visualizar abaixo:
[MessageInspectorBehavior] public class ServicoDeUsuarios : IUsuarios { [OperationInvokerBehavior] [ParameterInspectorBehavior] public bool Adicionar(Usuario usuario) { Console.WriteLine("Usuario adicionado: {0}", usuario.Nome); return true; } [OperationInvokerBehavior] [ParameterInspectorBehavior] public Usuario[] RecuperarUsuarios() { return new Usuario[] { new Usuario(){ Nome = "Israel" }, new Usuario(){ Nome = "Claudia" }, new Usuario(){ Nome = "Juliano" } }; } } Imports System Imports Service.Contract Imports Service.Extensions.Behaviors <MessageInspectorBehavior()> Public Class ServicoDeUsuarios Implements IUsuarios <OperationInvokerBehavior(), ParameterInspectorBehavior()> _ Public Function Adicionar(ByVal usuario As Usuario) As Boolean _ Implements IUsuarios.Adicionar Console.WriteLine("Usuario adicionado: {0}", usuario.Nome) Return True End Function <OperationInvokerBehavior(), ParameterInspectorBehavior()> _ Public Function RecuperarUsuarios() As Usuario() _ Implements IUsuarios.RecuperarUsuarios Dim list As New List(Of Usuario)() list.Add(New Usuario("Israel")) list.Add(New Usuario("Claudia")) list.Add(New Usuario("Juliano")) Return list.ToArray() End Function End Class |
|||
C# | VB.NET |
As configurações que foram realizadas acima apenas refletem em mudanças do lado do serviço. Os mesmos atributos podem ser utilizados do lado do cliente e, justamente por isso, que eles foram criados em um Assembly isolado. Como sabemos, ao referenciar o serviço em uma aplicação cliente, o proxy é gerado e, com isso, uma interface contendo a mesma estrutura de métodos do serviço também é criada. Sendo assim, é perfeitamente possível aplicarmos os atributos na interface do proxy, quando desejado, interceptando a execução, mas agora do lado do cliente.
Já aqueles behaviors que implementam a interface IEndpointBehavior, devem ser adicionados na coleção de behaviors de um endpoint do lado do serviço ou do cliente. Do lado do serviço, ao criar um endpoint através do método AddServiceEndpoint da classe ServiceHost, ele retorna uma instância da classe ServiceEndpoint que, por sua vez, fornece uma propriedade chamada Behaviors que aceita instâncias da classe que implementam a interface IEndpointBehavior e, do lado do cliente, o proxy (ClientBase<TChannel>) trabalha da mesma forma, expondo uma propriedade chamada Endpoint.
Além das formas que vimos acima para adicionar um behavior à execução, podemos ainda definir esses behaviors através do arquivo de configuração, para assim ter uma maior flexibilidade. O WCF disponibiliza uma seção chamada behaviors para endpoints e outro para o serviço. Qualquer elemento elencado em uma destas coleções será automaticamente adicionado ao serviço/endpoint. Para suportar esta técnica, ainda é necessário a criação de uma classe adicional que representará o elemento dentro do arquivo de configuração. Esta classe deve herdar da classe abstrata BehaviorExtensionElement e sobrescrever os membros BehaviorType e CreateBehavior. O exemplo abaixo ilustra a criação de um desses elementos:
using System.ServiceModel.Configuration; public class OperationSelectorElement : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(OperationSelectorBehaviorAttribute); } } protected override object CreateBehavior() { return new OperationSelectorBehaviorAttribute(); } } ImportsSystem.ServiceModel.Configuration Public Class OperationSelectorElement Inherits BehaviorExtensionElement Public Overrides ReadOnly Property BehaviorType() As System.Type Get Return GetType(OperationSelectorBehaviorAttribute) End Get End Property Protected Overrides Function CreateBehavior() As Object Return New OperationSelectorBehaviorAttribute() End Function End Class |
|||
C# | VB.NET |
Basicamente, o método CreateBehavior retorna uma instância da classe que implementa uma das interfaces IServiceBehavior ou IEndpointBehavior. Depois desta classe criada, podemos inserir o behavior via arquivo de configuração. No código de exemplo vinculado ao artigo, o código cliente insere o behavior OperationSelector utilizando o código declarativo. Note que primeiramente é necessário registrar o behavior através da seção behaviorExtensions para em seguida utilizá-lo. O exemplo abaixo ilustra o registro e sua utilização:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint address="net.tcp://localhost:3838/srv" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IUsuarios" contract="ServicoDeUsuarios.IUsuarios" name="NetTcpBinding_IUsuarios" behaviorConfiguration="edpConfig"> </endpoint> </client> <extensions> <behaviorExtensions> <add name="operationSelector" type="Service.Extensions.Configuration.OperationSelectorElement, Service"/> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="edpConfig"> <operationSelector /> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel> </configuration> |
|||
*.config |
Compartilhando Estado
As opções que vimos acima no permite customizar algum comportamento da execução do WCF. Além destas técnicas, ainda temos um mecanismo para armazenar o estado de um objeto em diferentes contextos da execução, mantendo-o durante o tempo necessário. Você poderá criar essas extensões em diferentes contextos e, para isso, é necessário criar uma classe que implemente a interface IExtension<T>.
Essa interface fornece um parâmetro chamado T que, por sua vez, aceita qualquer tipo que implemente a interface IExtensibleObject<T>. Dentro do próprio WCF, temos as seguintes classes que implementam essa interface: ServiceHost, InstanceContext e OperationContext. Objetos inseridos dentro do contexto da classe ServiceHost, serão mantidos entre toda a vida do host; já os objetos inseridos na classe InstanceContext terão o contexto determinado pelo tempo de vida da instância da classe que representa o serviço e, finalmente, objetos inseridos na classe OperationContext, terão o seu tempo de vida mais reduzido, ficando ativo somente durante a execução de uma operação.
A interface IExtension<T> é bem simples, pois fornece apenas dois métodos chamados Attach e Detach. O primeiro deles é invocado quando a instância da classe que implementa esta interface for adicionada à coleção de extensões, através de uma propriedade chamada Extensions. Essa propriedade é fornecida por todas as classes que implementam a interface IExtensibleObject<T>. Já o segundo método, Detach, é disparado quando a instância desta classe for removida da coleção Extensions.
using System; using System.ServiceModel; public class ServiceHostBaseExtension : IExtension<ServiceHostBase> { public void Attach(ServiceHostBase owner) { //... } public void Detach(ServiceHostBase owner) { //... } } Imports System Imports System.ServiceModel Public Class ServiceHostBaseExtension Implements IExtension(Of ServiceHostBase) Public Sub Attach(ByVal owner As ServiceHostBase) _ Implements IExtension(Of ServiceHostBase).Attach "... End Sub Public Sub Detach(ByVal owner As ServiceHostBase) _ Implements IExtension(Of ServiceHostBase).Detach "... End Sub End Class |
|||
C# | VB.NET |
Note que implementamos a interface IExtension<T> definindo como tipo T a classe ServiceHostBase e, devido aos parâmetros genéricos, a instância da classe ServiceHostBase ao qual essa extensão for adicionada, é passada como parâmetro para os métodos Attach e Detach. Como definimos o tipo ServiceHostBase, o código abaixo ilustra como adicionar a instância dessa classe à propriedade Extensions do ServiceHost:
using (ServiceHost host = new ServiceHost(typeof(ServicoDeUsuarios), new Uri[] { new Uri("net.tcp://localhost:3838") })) { //outras configurações host.Extensions.Add(new ServiceHostBaseExtension()); host.Open(); Console.ReadLine(); } Using host As New ServiceHost(GetType(ServicoDeUsuarios), _ New Uri() {New Uri("net.tcp://localhost:3838")}) "outras configurações host.Extensions.Add(New ServiceHostBaseExtension()) host.Open() Console.ReadLine() End Using |
|||
C# | VB.NET |
Conclusão: Vimos ao longo deste artigo grande parte das possibilidades que temos dentro do WCF para extendê-lo. Com os exemplos mostrados aqui, podemos customizar grande parte da execução de um serviço, tendo vários pontos de interceptação tanto do lado do serviço quanto do lado do cliente.