Impacto de Exceções no Proxy WCF

Uma das preocupações que devemos ter em qualquer tipo de desenvolvimento é com o tratamento de erros. Eu já comentei neste artigo e vídeo as possibilidades que temos para fazer isso dentro do WCF, utilizando suas próprias características de interceptação, transformação (promoção) e propagação das exceções para faults.

Já sabemos que se conhecermos todos os eventuais problemas que podem acontecer, podemos já definí-los no contrato do serviço (através do atributo FaultContractAttribute), para que assim o cliente possa ser notificado do que realmente aconteceu. Só que ainda há uma questão que não foi tratada, que é justamente como fica o estado do proxy depois que a falha acontece no serviço?

A resposta para essa pergunta dependerá de qual tipo de exceção que está sendo disparada, e também de qual binding está sendo utilizado por aquele endpoint. Em grande parte dos cenários, vemos que o desenvolvedor utilizar a instância de um mesmo proxy para efetuar várias chamadas para o mesmo serviço, não importando se é ou não para uma mesma operação.

Envolver as chamadas para as operações dentro de um bloco try/catch, evita apenas que a aplicação cliente saiba se comportar quando um determinado erro ocorre. Mas dependendo do que aconteceu no serviço, você não conseguirá reutilizar a mesma instância do proxy. Depois da falha, qualquer tentativa de comunicacão não chegará mais até o serviço, resultando assim naquela famosa exceção do tipo CommunicationObjectFaultedException, com a seguinte mensagem:

System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state

Digamos que você não está preocupado com as exceções que ocorrem dentro do seu serviço. Dentro de uma determinada operação, uma exceção CLR (ArgumentNullException, IndexOutOfRangeException, etc.) é disparada. No exemplo abaixo, estou efetuando o teste para saber se o parâmetro é ou não nulo, e sendo, uma exceção do tipo ArgumentNullException está sendo disparada:

public string Ping(string value)
{
    if (value == null)
        throw new ArgumentNullException(“value”);

    return value;
}

Como exceções não tratadas do lado do serviço são consideradas um risco, o canal de comunicação (proxy) não ficará mais disponível. Para os bindings que suportam sessão (NetTcpBinding, WSHttpBinding, etc.), isso quer dizer que se você tentar invocar uma segunda requisição, ele estará em um estado inválido (Faulted), o que obrigará a você recriar o proxy (instanciar novamente) para depois utilizá-lo. A solução para este caso, já foi comentada nos artigos que referencie acima, que é trabalhar explicitamente com a classe FaultException (ou até mesmo FaultException<T>):

public string Ping(string value)
{
    if (value == null)
        throw new FaultException(“value”);

    return value;
}

Desta forma, o canal de comunicação não será afetado, e seguramente você poderá utilizá-lo para efetuar novas requisições. Já aqueles bindings que não suportam sessão, como é o caso do BasicHttpBinding ou do WebHttpBinding (utilizado para REST/AJAX), não serão danificados, mesmo se você não estiver se preocupando com o uso da classe FaultException.

Como muitas vezes as operações recorrem a outras classes, e que muitas você não tem acesso à elas, então você dificilmente conseguirá mapear todas as exceções que podem acontecer. Para garantir que o proxy consiga se “restaurar” de um estado falho, você pode recorrer ao evento Faulted, exposto pela interface ICommunicationObject e também pela classe ClientBase<TChannel>. Este evento é disparado quando o proxy entre em estado falho, e assinando este evento, você poderia reconstruir o proxy no exato momento, como eu mostro no código abaixo:

ChannelFactory<IContrato> factory = 
    new ChannelFactory<IContrato>(new NetTcpBinding(), new EndpointAddress(“net.tcp://localhost:3732/srv”));

IContrato proxy = factory.CreateChannel();
((ICommunicationObject)proxy).Faulted += (o, e) => proxy = factory.CreateChannel();

Usando LINQ To SQL com WCF

Para aqueles que trabalham com LINQ To SQL e querem expor as entidades geradas pela ferramenta via WCF, terá que tomar alguns cuidados. Quando essas entidades não mantém um relacionamento, algo que é bem díficil, você consegue retorná-las a partir das operações que o serviço irá disponibilizar.

O problema começa a aparecer quando você deseja enviar para os clientes, entidades que possuem relacionamentos com outras entidades. Um exemplo clássico e que ilustra bem o cenário, é quando temos categorias e produtos, onde cada produto deverá pertencer à somente uma única categoria. Neste caso, temos uma relação direta entre elas. Mas o ponto que torna isso difícil é que o LINQ To SQL cria uma relação bidirecional entra elas. Isso quer dizer que a entidade Categoria terá uma propriedade chamada Produtos, que como o próprio nome diz, retorna a coleção dos produtos daquela categoria; além disso, a classe Produto expõe uma propriedade chamada Categoria, que retorna a instância de uma categoria em qual o produto está contido.

Quando falamos em orientação à objetos, isso é perfeitamente válido e comum. O problema ocorre quando você tenta serializar essa estrutura a partir do WCF. Como eu comentei neste outro post, o WCF tem um comportamento diferente quando há referências circulares, e não conseguirá fazer a serialização porque ele ficará em uma espécie de loop, pois com há referência bidirecional, ao serializar uma categoria, ela serializa os respectivos produtos, e para cada produto a sua respectiva categoria, e para esta categoria os seus produtos, e por aí vai. O serviço roda sem maiores problemas, mas você terá uma exceção quando quando o cliente tentar acessá-lo.

Analisando a imagem abaixo, podemos visualizar a estrutura das classes que compõem o exemplo, e logo ao lado, você pode reparar nas propriedades do arquivo (superfície) DBML, verá que existe uma propriedade chamada Serialization, que pode receber apenas dois valores: None e Unidirectional. O primeiro deles permite que as propriedades das entidades sejam serializadas de acordo com as regras impostas pelo serializador padrão do WCF, que graças a possibilidade de serializar qualquer propriedade, mesmo que elas não estejam decoradas com os atributos DataContractAttribute e DataMemberAttribute (POCO). A segunda opção, Unidirectional, faz com que ele somente consiga serializar o relacionamento em uma única direção para evitar as referências circulares. No nosso caso, teremos a entidade Categoria com a propriedade Produtos, mas a classe Produto não terá uma propriedade que define a sua categoria.

Alterando a opção de serialização para Unidirectional, a forma que você efetua a consulta também terá que mudar. Isso fará com que o LINQ To SQL não consiga trazer os dados (produtos) relacionados aquela categoria, algo que é transparente quando estamos utilizando o LINQ To SQL diretamente. Para conseguir fazer com que os dados relacionados também sejam carregados, temos que recorrer a classe DataLoadOptions, como é mostrado abaixo:

public Categoria[] RecuperarCategorias()
{
    using (DBContextDataContext ctx = new DBContextDataContext())
    {
        DataLoadOptions opts = new DataLoadOptions();
        opts.LoadWith<Categoria>(c => c.Produtos);
        ctx.LoadOptions = opts;

        return (fromin ctx.Categorias select c).ToArray();
    }
}

Mas como disse acima, isso funcionará mas você perderá a navegação bidirecional. Felizmente, podemos recorrer à propriedade boleana IsReference, que é exposta pelo atributo DataContractAttribute, definindo isso na classe Categoria. Isso permitirá a criação da navegação bidirecional, mas há um trabalho manual a ser feito para que isso funcione. Quando você muda a propriedade Serialization para None, nenhuma das propriedades é decorada com o atributo DataContractAttribute/DataMemberAttribute; já se definir essa propriedade para Unidirectional, as propriedades que são problemáticas, não estarão decoradas com o atributo DataMemberAttribute.

Sendo assim, o exemplo final fica como é mostrado abaixo, conseguindo ter no cliente, a navegação bidirecional. Obviamente que alguns membros foram omitidos por questões de espaço.

[Table(Name = “dbo.Categoria”)]
[DataContract(IsReference = true)]
public partial class Categoria
{
    [DataMember]
    [Column(…)]
    public int CategoriaId

    [DataMember]
    [Column(…)]
    public string Nome

    [DataMember]
    [Association(…)]
    public EntitySet<Produto> Produtos
}

[Table(Name = “dbo.Produto”)]
public partial class Produto
{
    [Column(…)]
    public int ProdutoId

    [Column(…)]
    public int CategoriaId

    [Column(…)]
    public string Nome

    [Association(…)]
    public Categoria Categoria
}

Gerenciamento de Channels

Algum tempo atrás eu falei sobre os internals de um proxy WCF. Como eu havia dito, existe um grande overhead quando criamos um novo proxy, que está condicionado as complexidades que podem ou não estarem habilitadas (segurança, transações, mensagens confiáveis, etc.).

Como disse naquele mesmo post, o ponto mais custoso da criação, que é a factory (ChannelFactory<TChannel>), está sendo reutilizada, em nível de AppDomain, ou seja, mesmo que você não mantenha a instância do proxy gerado (ClientBase<TChannel>) pela IDE do Visual Studio ou pelo utilitário svcutil.exe, o runtime do WCF irá reciclar a factory, e em seguida, a colocará em um cache.

Quando estabelecemos a comunicação entre o cliente e o serviço, utilizamos um canal de comunicação, referido também como channel. Estes são utilizados por nós, na maioria das vezes implicitamente, para efetuar as requisições para o respectivo serviço. Na verdade, é a factory que fornece um método chamado CreateChannel, que retorna a instância de um TransparentProxy. Este tipo nos permite efetuar uma conversão para o contrato exposto pelo documento WSDL, e assim invocar as operações como se elas fossem simples métodos locais. Se você abrir a classe que é gerada quando você faz a referência à um serviço, verá que dentro das operações sempre há uma chamada para a propriedade Channel, que por sua vez, irá até o método CreateChannel.

Como a parte custosa já está sendo reutilizada de forma performática pelo WCF, as vezes surge a dúvida se devemos ou não manter um outro cache, mas este para armazenar as instâncias dos channels que são retornados pelo método CreateChannel, mas que a classe ClientBase<TChannel> não se preocupa com isso. O objeto retornado por esse método implementa a interface ICommunicationObject, que fornece entre vários membros, um método autoexplicativo chamado Close, que é responsável por encerrar mover o estado do proxy corrente para Closed e também descartar os recursos que ele utiliza.

Assim como todo recurso caro, é importante você sempre descartá-lo quando não precisar mais dele. Isso também é o caso dos channels. Mas é importante você analisar cuidadosamente o cenário. Se você faz chamadas subsequentes, não convém a todo momento invocar o método CreateChannel para criar um novo canal; reutilize-o durante essas chamadas, e somente feche-o quando realmente não precisar mais dele, dentro daquele escopo.

Da mesma forma, antes de descartá-lo completamente, analise se você não pode manter um cache para estes channels. Fazer pooling de channels é uma boa alternativa para diminuir ainda mais os custos de criação deles. Você pode manter os channels desde que:

  • Não há um contexto de segurança exclusivo para cada usuário (SCT).
  • O mesmo channel não é utilizado por múltiplas threads ao mesmo tempo.
  • Não há manutenção de estado.

Na primeira situação o problema acontece porque as credenciais são definidas durante a criação da factory, e não podem mais serem alteradas. Já o segundo cenário, acontece porque os channels são thread-safe, ou seja, eles não dão suporte ao envio de mensagem de forma concorrente. Se em uma thread A você está utilizando o channel C1 para mandar uma mensagem grande, a thread B que utilizará o mesmo channel C1, somente conseguirá enviar a mensagem depois que a thread A finalizar. A última das situações implica quando você mantém uma sessão entre o cliente e o serviço, pois ela está fortemente ligada ao channel correspondente.

Tecnologias que circundam o WCF

Em 2006 a Microsoft lançou a versão 3.0 do .NET Framework, que nada mais era do que “grandes blocos” que foram adicionados ao 2.0. Entre esses grandes blocos, temos o WCF. Como todo mundo sabe, ele é o novo pilar para comunicação dentro da plataforma .NET. A estrutura deste framework, facilitou a entrada de novos produtos, também criados pela Microsoft, para atender cenários específicos.

Isso acaba facilitando bastante, já que grande parte da complexidade do WCF acaba sendo abstraída do desenvolvedor. Depois do .NET Framework 3.0, veio a versão 3.5, que incorporou novas funcionalidades, e mais tarde, no PDC 2009, a Microsoft publicou novos serviços, construídos em cima do WCF. Atualmente temos os seguintes tipos de serviços disponíveis:

  • Serviços SOAP: É o WCF em si. Possibilita a construção de serviços baseando-se em padrões de mercado, que tentam manter a interoperabilidade entre várias plataformas ou com outras tecnologias, como COM+, MSMQ, .NET Remoting, etc. Esses padrões regem transações, segurança, entre outras funcionalidades. A idéia aqui é permitir a construção de serviços orientado à operações que você precisa expor ao mundo, através dos mais diversos protocolos.
  • Serviços WebHttp: A partir da versão 3.5, a Microsoft trouxe a capacidade de construir serviços REST dentro do WCF. Usando métodos como POST, GET, PUT, etc., em conjunto URLs (onde você pode formatar do jeito que desejar), temos a flexibilidade de expor operações para serem consumidas diretamente, sem envolver essas requisições em envolopes SOAP, facilitando assim o consumo por aplicações AJAX, por exemplo.
  • Serviços para Dados: Semelhante a anterior, mas a idéia é expor via REST as informações contidas em um banco de dados. Inicialmente levava o nome de ADO.NET Data Services, mas depois do PDC foi renomeado para WCF Data Services.
  • Serviços de Workflow: Basicamente, a ideia é permitir que um workflow (construído pelo Windows Workflow Foundation (WF)) possa ser consumido e coordenado por serviços WCF. Situações onde você tem operações que possuem uma longa duração, a necessidade de manter o estado entre chamadas, esse tipo de serviço poderá ajudar.
  • Serviços RIA: WCF RIA Services estará disponível juntamente com o Silverlight 4.0, e simplificará a forma como você escreverá uma aplicação N-tier, onde o cliente será o próprio Silverlight.

UserName e Password em serviços WCF para Silverlight

Como sabemos, podemos utilizar usuário e senha para autenticar um usuário em um serviço WCF, mas há algumas regras que precisam ser seguidas, como eu já comentei neste post. Uma dessas regras é que não podemos trafegar essas informações sem alguma criptografia. Mesmo que o serviço seja exposto via HTTP, a utilização de um certificado (não do HTTPS), fará este papel.

Eu mostrei como customizar a autenticação neste artigo, e note que estou usando um certificado e o serviço não está disponível através do HTTPS. Além disso, o binding que está sendo utilizado é o WSHttpBinding, que sabe como efetuar a criptografia das informações. Agora imagine que este serviço pode ser consumido por uma aplicação Silverlight. Infelizmente o WCF do Silverlight não tem suporte ao WSHttpBinding, somente ao BasicHttpBinding, e mais recentemente, via NetTcpBinding.

Mas como podemos proceder neste caso? Para poder disponibilizar serviços WCF para uma aplicação Silverlight, precisaremos recorrer a segurança em nível de transporte, para garantir a proteção. Para isso, estamos obrigados à expor o serviço através de HTTPS, e as credenciais estarão protegidas dentro da mensagem. Mas antes da versão 3.0 do Silverlight, era díficil expor e/ou consumir serviços WCF que exigem usuário e senha, já que você precisava criar headers customizados para acomodar essas informações, assim como era (ou ainda é) feito pelos ASP.NET Web Services.

A partir da versão 3.0, o Microsoft introduziu mais uma opção no binding BasicHttpBinding (do Silverlight), que é a TransportWithMessageCredential. Com esta opção definida, o serviço deverá ser disponibilizado via HTTPS, e as credencias viajarão dentro da mensagem, sem precisarmos escrever qualquer código adicional. Felizmente, quando efetuamos a referência em uma aplicação Silverlight, o proxy gerado já disponibiliza a propriedade ClientCredentials, onde podemos atribuir o usuário e senha, que eventualmente vem de controles TextBoxes da tela.

A configuração do serviço é relativamente simples. O primeiro passo é criar o validador customizado de usuários, que nada mais é do que uma classe que herda de UserNamePasswordValidator, e dentro do método Validate, você escreve a regra para buscar e validar o usuário. Para maiores detalhes de como isso funciona, consulte este artigo. Depois note também que definimos a segurança como sendo TransportWithMessageCredential, conforme comentamos acima.

<system.serviceModel>
  <services>
    <service name=”ServicoDeClientes”
             behaviorConfiguration=”bhr”>
      <endpoint name=”srv”
                binding=”basicHttpBinding”
                contract=”IContrato”
                bindingConfiguration=”bc”/>
    </service>
  </services>
  <bindings>
    <basicHttpBinding>
      <binding name=”bc”>
        <security mode=”TransportWithMessageCredential”/>
      </binding>
    </basicHttpBinding>
  </bindings>
  <behaviors>
    <serviceBehaviors>
      <behavior name=”bhr”>
        <serviceMetadata httpsGetEnabled=”true”/>
        <serviceCredentials>
          <userNameAuthentication
            customUserNamePasswordValidatorType=”ValidadorDeUsuarios”
            userNamePasswordValidationMode=”Custom”/>
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

Depois da configuração do serviço, o publicamos e, consequentemente, referenciamos na aplicação cliente (Silverlight). Ao referenciá-lo, já podemos notar que o arquivo de configuração já está devidamente especificado com a mesma opção de segurança: TransportWithMessageCredential.

<configuration>
  <system.serviceModel>
    <client>
      <endpoint address=”https://localhost/SLTest/ServicoDeClientes.svc&#8221;
                binding=”basicHttpBinding”
                bindingConfiguration=”srv”
                contract=”ServicoDeClientes.IContrato”
                name=”srv” />
    </client>
    <bindings>
      <basicHttpBinding>
        <binding name=”srv”>
          <security mode=”TransportWithMessageCredential” />
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Com isso, tudo o que nos resta é instanciar o proxy e definir a propriedade ClientCredentials, que receberá os credenciais do usuário. Depois de configurado, você já está apto a chamar qualquer operação exposta pelo serviço, que antes dela ser efetivamente executada, o runtime do WCF avaliará as credenciais informadas, se certfificando de que são válidas, e somente a partir daí, irá invocar o método.

ServicoDeClientes.ContratoClient p = new ServicoDeClientes.ContratoClient();
p.ClientCredentials.UserName.UserName = this.Login.Text;
p.ClientCredentials.UserName.Password = this.Password.Text;

p.PingCompleted += (s, args) => this.textBlock1.Text = args.Result;
p.PingAsync();

A chamada da operação de forma assíncrona, nada tem nada a ver com a questão de segurança. Isso é apenas uma característica do WCF do Silverlight, e mesmo que não houvesse qualquer nível de segurança envolvido, a forma para invocar uma operação seria a mesma.

Serialização Circular no WCF

Quando construímos as classes que atenderão à um sistema específico, é muito comum termos uma propriedade que expõe um outro objeto, e este, por sua vez, você gostaria de também ter uma propriedade que referencia o seu “pai”. Isso é algo simples de se realizar, mas poderá haver problemas ao serializar essa classe, como por exemplo, quando precisar expor via WCF.

Levando em consideração a imagem acima, note que um cliente possui um endereço, e este endereço aponta para o cliente que o possui. Quando você tentar enviar o cliente através do WCF, você receberá uma exceção, já que ele ficará em loop infinito, tentando efetuar a serialização do cliente -> endereço -> cliente -> endereço -> cliente -> endereço e assim vai.

Para solucionar isso, podemos utilizar a propriedade IsReference na classe “pai”, que no nosso casso é Cliente. A partir de agora, o processo de serialização gerencia a criação de um identificador, para assim conseguir refenciá-lo ao invés de tentar serializar novamente. É importante se atentar para também alterar no proxy construído do lado do cliente, caso o teu contrato permita enviar a instância da classe Cliente para o respectivo serviço.

[DataContract(IsReference = true)]
public class Cliente { }

UserNames com autenticação Basic

Há algum tempo em comentei aqui sobre as opções de segurança no WCF, e mais tarde, como customizá-la para receber e validar usernames e passwords em um repositório qualquer. Antes da versão 3.5 do WCF, essa customização somente trabalha com segurança em nível de mensagem. Com o 3.5, a Microsoft adicionou esse suporte para também trabalhar com segurança em nível de transporte.

Isso é útil em cenários onde queremos utilizar a autenticação Basic do HTTP como meio de fornecimento das credenciais, mas ao invés da conta informada existir e de ser validada dentro do sistema operacional, vamos customizar essa regra, utilizando um repositório customizado, como uma base de dados, evitando que os clientes tenham, obrigatoriamente, contas cadastradas dentro do Windows.

Para que isso funcione, a configuração de segurança do binding deverá ser definida como Transport, e a forma de fornecimento de credenciais como Basic. Além disso, você também precisará implementar um validador customizado, herdando da classe abstrata UserNamePasswordValidator, como eu já mostrei neste artigo. Abaixo temos a configuração do lado do serviço:

<bindings>
  <basicHttpBinding>
    <binding name=”bindingConfig”>
      <security mode=”Transport”>
        <transport clientCredentialType=”Basic”/>
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

Apenas lembre-se de que o Basic faz com que as credenciais sejam trafegadas codificadas e não criptografadas, e justamente por isso, estamos recorrendo à segurança em nível transporte (que neste caso é o HTTPS), para garantir a integridade e confidencialidade da mensagem e das credenciais. Para informar as credenciais do lado do cliente, você faz da mesma forma que antes:

using (Servico p = new Servico)
{
    p.ClientCredentials.UserName.UserName = “IA”;
    p.ClientCredentials.UserName.Password = “123”;

    Console.WriteLine(p.Ping(“teste”));
}

O único problema desta técnica, é que você não poderá hospedar o serviço no IIS. Isso se deve ao fato de que quando o atributo clientCredentialType estiver definido como Basic, o IIS fará a verificação durante o carregamento do serviço, analisando se o modo de autenticação Basic está habilitado para aquele diretório virtual, e caso contrário, uma exceção do tipo NotSupportedException será disparada, informando que o modo Basic não está habilitado.

Só que se você habilitá-lo, o problema volta a acontecer, pois a ideia por trás desta técnica, é utilizar o Basic como forma de envio das credenciais, mas não de validá-las, e com isso, se você colocar um usuário e senha que não existe dentro do Windows, ele não deixará você acessar o serviço, e mesmo que você digite um usuário/senha válidos, a implementação da classe UserNamePasswordValidator nunca será executada. Para que isso seja possível, você precisa modificar o pipeline do ASP.NET, acoplando módulos customizados que farão esse trabalho.

Compartilhamento de Bindings

Quando você possui múltiplos endpoints para expor um serviço, na maioria das vezes, esses endpoints possuem características diferentes, pois você poderá publicá-lo através de HTTP para consumo externo, enquanto as aplicações locais, consomem este mesmo serviço através de TCP, para uma melhor performance.

A configuração abaixo ilustra a exposição de um mesmo serviço e de um mesmo contrato através dos protocolos TCP e HTTP. Cada um dos protocolos possuem características diferentes de comunicação, e justamente por isso, exigem bindings diferentes.

<system.serviceModel>
  <services>
    <service name=”App.Servico”>
      <host>
        <baseAddresses>
          <add baseAddress=”net.tcp://localhost:9838″/>
          <add baseAddress=”http://localhost:8478″/&gt;
        </baseAddresses>
      </host>
      <endpoint address=”srv”
                contract=”App.IContrato1″
                binding=”netTcpBinding”/>
      <endpoint address=”srv”
                contract=”App.IContrato1″
                binding=”basicHttpBinding”/>
    </service>
  </services>
</system.serviceModel>

Cada endpoint é composto por um endereço, binding e contrato, e cada endpoint possui seu próprio listener e channel stack, que varia de acordo com o binding escolhido. Em algumas situações, é comum ter mais do que um contrato para o mesmo serviço, onde queremos compartilhar o mesmo endereço para ambos contratos. Abaixo podemos ver um serviço que tem esse comportamento:

<system.serviceModel>
  <services>
    <service name=”App.Servico”>
      <host>
        <baseAddresses>
          <add baseAddress=”net.tcp://localhost:9838″/>
        </baseAddresses>
      </host>
      <endpoint address=”srv”
                contract=”App.IContrato1″
                binding=”netTcpBinding”/>
      <endpoint address=”srv”
                contract=”App.IContrato2″
                binding=”netTcpBinding”/>
    </service>
  </services>
</system.serviceModel>

Repare que temos dois contratos (IContrato1 e IContrato2) expostos através de TCP e usando o mesmo endereço. Isso somente é possível porque o WCF reutiliza a mesma instância do binding para efetuar a comunicação, e difere a execução baseando-se nas Soap Actions. Se cada binding tiver sua configuração específica (definido através do atributo bindingConfiguration), então isso fará com que o WCF crie instâncias diferentes do mesmo binding, resultando em uma exceção tipo do InvalidOperationException sendo disparada, informando que não é permitido associar uma mesma instância binding à endereços diferentes.

Se você tiver endereços distintos, então duas instâncias distintas do binding correspondente serão criadas para atender cada um deles. Esse mesmo cuidado você precisa ter ao configurar o serviço de forma imperativa. Ao chamar o método AddServiceEndpoint da classe ServiceHost, você deverá se preocupar em passar a mesma instância do binding, como por exemplo:

using (ServiceHost host = 
       new ServiceHost(typeof(Servico), new Uri[] { new Uri(“net.tcp://localhost:9838”) }))
{
    NetTcpBinding b = new NetTcpBinding();

    host.AddServiceEndpoint(typeof(IContrato1), b, “srv”);
    host.AddServiceEndpoint(typeof(IContrato2), b, “srv”);

    host.Open();
    Console.ReadLine();
}

Dectetando a desconexão

Um cenário muito comum em serviços, é a capacidade que os mesmos tem de conseguir invocar algum método do lado do cliente, em resposta ou notificação à algum acontecimento que ocorreu. Isso é conhecido no WCF como comunicação duplex, onde você cria um contrato e que o cliente será obrigado a implementá-lo para que, eventualmente, seja notificado de que algo aconteceu. Para maiores detalhes em como implementar isso, consulte esse artigo ou este vídeo.

Para extrair o canal de comunicação entre o serviço e o cliente, utilizamos o método genérico GetCallbackChannel<T> da classe OperationContext. Esse método retorna uma espécie de proxy, que também implementa a interface de callback, especificada através do parâmetro genérico T. Como a ideia é armazená-lo para mais tarde invocar, pode acontecer deste cliente já não estar mais online, e o canal de comunicação já encontra-se em um estado “inválido”.

Se você não se atentar à esse detalhe e, incondicionalmente, invocar o callback, você receberá uma exceção do tipo CommunicationObjectAbortedException, informando que o canal de comunicação com o respectivo cliente foi abortado. Antes de mostrar como dectectar a desconexão, precisamos conhecer um novo tipo fornecido pelo próprio WCF, e que tem um papel extremamente importante dentro deste framework: a interface ICommunicationObject.

Essa interface define um contrato para todos os objetos de comunicação dentro do WCF, gerenciando as conexões que são relizadas através dos métodos Open, Close e Abort (e suas versões assíncronas). Essa interface ainda fornece uma propriedade chamada State, que retorna uma das opções definidas no enumerador CommunicationState. Além disso, ela ainda fornece eventos que são disparados tão logo quando o canal entrar em um estado diferente. Entre os eventos, temos: OpenedClosed e Faulted.

Apesar do método GetCallbackChannel<T> retornar a instância tipo genérico especificado em T, ele pode ser convertido em ICommunicationObject, onde você terá acesso à todos os membros que vimos acima, correspondentes ao cliente que você deseja se comunicar. Tendo acesso a está interface, você tem duas opções para identificar a desconexão. A primeira delas é antes de invocar o callback, avaliar a propriedade State, e se ela ainda estiver aberta, você pode invocar sem maiores problemas. O exemplo abaixo ilustra isso:

ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();

if (((ICommunicationObject)callback).State == CommunicationState.Opened)
    callback.OnCallback(“Algum Parametro”);

A segunda opção consiste em assinar o evento Closed, ou dependendo da situação, o Faulted, que serão disparados quando o canal de comunicação for fechado ou quando ele entrar em um estado falho, respectivamente. Dessa forma você pode ser notificado quando isso acontecer, e tormar alguma decisão em cima disso. O código abaixo ilustra como proceder para assinar o evento Closed:

((ICommunicationObject)callback).Closed += (sender, args) => Console.WriteLine(“Fechou!”);

Esse tipo de técnica é comumente utilizada quando armazenamos do lado do serviço, a referência para um ou vários clientes, como é o caso de aplicações de chat ou algum modelo de publish-subscribe.

Serviços TCP no Silverlight

A cada dia que o passa, o Silverlight tem ganhado cada vez mais espaço nos sites que são construídos ao redor do mundo. Motivo para a Microsoft evoluir essa tecnologia rapidamente, adicionando cada vez mais funcionalidades dentro dele.

Os adeptos ao Silverlight não se limitam à aqueles construtores de sites institucionais, que na maioria das vezes, são criados para campanhas publicitárias e com poucas funcionalidades dinâmicas. Ele também está atingindo as empresas desenvolvem os softwares para uso interno, justamente porque a Microsoft incorpora nele, funcionalidades que permitem o uso em aplicações do tipo LOB (Line of Business).

Uma das novidades mais recentes, que afeta principalmente o uso de aplicações Silverlight em intranets, é que a partir da versão 4.0, teremos a possibilidade de referenciar e consumir serviços construídos em WCF, através do protocolo TCP (net.tcp).

Como disse no parágrafo acima, esse tipo de protocolo é útil em intranets, já que não haverá restrições de eventuais firewalls, e mesmo que tenha, tudo é negociável. É importante dizer, que as aplicações Silverlight estão condicionadas à utilizarem o seguinte intervalo de portas: 4502 à 4534, ou seja, somente conseguirá acessar um serviço WCF exposto através de uma delas.

Uma das principais características do protocolo TCP, é que possibilita a comunicação duplex, ou seja, permite que o serviço se comunique com o cliente, através de callbacks. Isso já era possível no Silverlight, através do Polling Duplex, que nada mais é do que uma especialização do protocolo BasicHttpBinding, mas que simula callbacks em cima do protocolo HTTP.

Outra grande vantagem do protocolo TCP quando comparado com o HTTP, é a performance, e mesmo em aplicações Silverlight, essa vantagem é bastante significativa, e um dos grande responsáveis por isso, é a codificação binária. Neste momento, para usar este protocolo, você deve abrir mão da segurança em nível de transporte, que não é suportado.

Os tipos necessários para acessar um serviço WCF através de TCP, estão contidos no assembly System.ServiceModel.NetTcp.dll, localizado em %Program Files%Microsoft SDKsSilverlightv4.0LibrariesClient. Mas isso não quer dizer que você terá uma classe chamada NetTcpBinding, assim como há no .NET Full. O Silverlight utiliza um CustomBinding, composto com os seguintes bindings elements: BinaryMessageEncodingBindingElement e TcpTransportBindingElement. Ao referenciar o serviço TCP em uma aplicação Silverlight 4.0, a IDE do Visual Studio 2010 já faz as seguintes entradas no respectivo arquivo de configuração:

<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name=”NetTcpBinding_IContrato”>
                    <binaryMessageEncoding />
                    <tcpTransport
                        maxReceivedMessageSize=”2147483647″
                        maxBufferSize=”2147483647″ />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint
                address=”net.tcp://localhost:4502/srv”
                binding=”customBinding”
                bindingConfiguration=”NetTcpBinding_IContrato”
                contract=”Servico.IContrato”
                name=”NetTcpBinding_IContrato” />
        </client>
    </system.serviceModel>
</configuration>

Ao contrário de outros tipos de comunicação e de aplicações, não basta simplesmente termos o cliente e o serviço funcionando para que eles possam se comunicar. Mesmo através de TCP, este protocolo também está condicionado às políticas de cross-domain.

Para comunicação HTTP, tudo o que precisamos é definir um arquivo XML na aplicação, que permite o acesso. Para suportar a comunicação através de TCP, o Silverlight resolve a questão da restrição de cross-domain de uma forma mais rebuscada. A Microsoft criou uma template de projeto chamada Silverlight TCP Socket Policy, que pode ser acessada a partir das templates Online do Visual Studio 2010 ou através do Visual Studio Gallery, neste endereço.

Essa template dá origem à um projeto do tipo Console, que expõe o arquivo de cross-domain (definido no arquivo SocketPolicy.cs), definindo o intervalo de portas que é permitido que aplicações Silverlight utilizem. Ao rodar as aplicações, certifique-se também de que este projeto esteja rodando, caso contrário, você receberá uma exceção do tipo CommunicationException, informando que você não tem permissões para efetuar o acesso ao serviço, sugerindo que talvez esteja faltando o arquivo de cross-domain.

Conclusão: Apesar de ainda estar em sua versão Beta, o Silverlight 4.0 traz uma série de melhorias e novas funcionalidades, que facilitará cada vez mais a criação de aplicações LOB, e com certeza, uma das principais limitações até então, era o consumo de serviços WCF através do protocolo TCP, que é fator determinante em aplicações deste tipo.