Desenvolvimento - C#

WCF - Tipos de Mensagens

Desconsiderando a possibilidade de chamadas assíncronas, temos três alternativas que podemos utilizar ao construir uma operação (método) de um serviço: request-reply, one-way e duplex (callbacks). O foco deste artigo é explorar tais alternativas e como elas influenciam na configuração e implementação e execução do serviço.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Baixe o exemplo.

Tradicionalmente, em qualquer tipo de aplicação, podemos criar um método que faz alguma tarefa. Ao criá-lo, podemos consumí-lo na mesma aplicação ao até mesmo referenciar a classe em que ele está contido e também consumí-los nos mais variados projetos. Ao realizar a chamada para este método, devemos esperar a sua execução e, quando finalizada, damos continuidade na execução do programa.

Ao criar uma operação em um serviço WCF, ela também se comportará da mesma forma. Mas essa não é a única alternativa fornecida pelo WCF. Desconsiderando a possibilidade de chamadas assíncronas, temos três alternativas que podemos utilizar ao construir uma operação (método) de um serviço: request-reply, one-way e duplex (callbacks). O foco deste artigo é explorar tais alternativas e como elas influenciam na configuração e implementação e execução do serviço.

Request-Reply

Quando criamos um método que retorne alguma informação dentro de uma interface que será o contrato do serviço, podemos decorar este método com o atributo OperationContractAttribute, que dirá ao runtime do WCF que ele deverá ser publicado e, conseqüentemente, consumido pelos clientes que referenciarem o serviço. Com essa configuração básica, o método já estará baseado no padrão request-reply.

Como o próprio nome diz, ao invocar o método a partir de um cliente, uma mensagem será enviada ao serviço através do proxy, contendo as informações necessárias para a execução do método. Neste momento a mensagem viaja pela rede até o respectivo serviço; ao chegar do outro lado, o dispatcher abre a mensagem e efetivamente executa o método requirido. Ao finalizar a execução do mesmo, uma nova mensagem é gerada e devolvida como retorno para o mesmo cliente que a requisitou. Ao chegar do outro lado (cliente), o proxy abre a mensagem, captura o resultado (ou o erro) e devolve para a aplicação.

Durante toda a execução do método, a aplicação cliente ficará bloqueada enquanto o resultado não voltar ou até o momento em que o timeout seja atingido. Com exceção dos bindings NetPeerTcpBinding e NetMsmqBinding, todos suportam operações do tipo request-reply. Para um melhor entendimento a imagem abaixo ilustra o processo entre o cliente e o serviço quando um método baseado no padrão request-reply é executado:

Figura 1 - Modelo Request-Reply.

One-Way

Quando você tem operações que não retornam valores e, principalmente, o cliente não está interessado no sucesso ou falha da mesma, podemos recorrer à técnica chamada de one-way. Também conhecida como fire-and-forget, ela permite que o proxy envie a mensagem ao seu destino, sem esperar pela sua execução. Há um bloqueio mínimo do lado do cliente, que é apenas necessário para que o proxy mande a mensagem e o serviço a enfileire. A partir daí, o cliente está livre para dar continuidade na execução do programa.

Como a característica desde tipo de mensagem é não notificar o cliente de sucesso ou falha, qualquer exceção que ocorra durante a execução do método não notificará o cliente (salvo algumas exceções que veremos mais abaixo). Neste caso, podemos utilizar algumas funcionalidades fornecidas pelo binding que está sendo utilizado, como o uso de sessões confiáveis ou até mesmo a possibilidade da criação de um contrato de callback em conjunto com operações duplex que veremos mais tarde, ainda neste artigo. Através da imagem abaixo, podemos visualizar a mensagem viajando entre cliente e servidor, sem haver uma mensagem de resposta correlacionada:

Figura 2 - Modelo One-Way.

A criação do contrato para suportar este tipo de mensagem muda ligeiramente em relação ao formato tradicional e todos os bindings suportam este tipo de mensagem. Já que a idéia das operações do tipo one-way é não ter retorno, os métodos não devem retornar nada, ou seja, devem ser void. Qualquer tipo de dado que você coloque diferente disso em operações marcadas como one-way, uma exceção do tipo XXXXX será lançada. Como o padrão é request-reply, para dizermos ao WCF que a operação trata-se de uma operação one-way, devemos definir a propriedade IsOneWay, fornecida pelo atributo OperationContractAttribute, como True. O trecho de código abaixo ilustra esta configuração:

using System;
using System.ServiceModel;

[ServiceContract]
public interface ICliente
{
    [OperationContract(IsOneWay = true)]
    void Notificar(string email);
}
Imports System
Imports System.ServiceModel

<ServiceContract()> _
Public Interface ICliente
    <OperationContract(IsOneWay:=True)> _
    Sub Notificar(ByVal nome As String)
End Interface
C# VB.NET

Se algum erro ocorrer durante a execução do serviço e o mesmo for configurado como PerCall (que não mantém estado/sessão) e, exposto via BasicHttpBinding ou WSHttpBinding (sem as opções de segurança e mensagens confiáveis habilitadas), o proxy do cliente não será afetado e, conseqüentemente, poderá fazer requisições subseqüentes; agora, se estiver sendo exposto através do WSHttpBinding com segurança, NetTcpBinding sem a opção de mensagens confiáveis habilitada ou através do binding NetNamedPipeBinding e uma exceção ocorrer durante o processamento da operação, isso afetará o proxy do lado do cliente, e ele não poderá mais executar operações a partir deste mesmo proxy, e o fechamento do mesmo não ocorrerá de forma segura. Finalmente, se esses últimos bindings estiverem com a opção de mensagens confiáveis habilitada, ele poderá continuar utilizando o mesmo proxy, mesmo que uma exceção ocorra.

Quando o serviço estiver exposto no modelo PerSession ou Single suportando sessão, fica um pouco mais difícil ter um controle sob as exceções que são disparadas. Se o serviço está exposto via NetTcpBinding ou NetNamedPipeBinding e um erro ocorrer, a sessão será encerrada, a instância do serviço será destruída e o proxy do cliente será afetado, não podendo fazer chamadas subseqüentes.

Duplex (Callbacks)

A terceira e última alternativa que temos no WCF é a possibilidade de efetuar callbacks. A finalidade desta técnica é permitir uma comunicação bidirecional, ou seja, o cliente invocar método de um serviço, bem como um serviço invocar um método do cliente. Esse tipo de comunicação permite, na maioria das vezes, um determinado serviço notificar o cliente de que algum evento ocorreu, dando a ele, uma chance de conseguir interagir com um cliente específico ou até mesmo vários clientes, através de um sistema publicador-assinante. A imagem abaixo ilustra o funcionamento deste tipo de mensagem:

Figura 3 - Modelo Duplex (callbacks).

Nem todos os bindings possibilitam esse tipo de comunicação. Os bindings NetTcpBinding e NetNamedPipeBinding fazem isso nativamente, devido à natureza dos protocolos utilizados. O protocolo HTTP não traz esse suporte, por se tratar de uma conexão que não mantém a ligação entre cliente e servidor. Para possibilitar que os callbacks funcionem a partir do HTTP, foi criado um binding chamado de WSDualHttpBinding que tem essa finalidade.

Quando fazemos uso do WSDualHttpBinding, o WCF utilizará um canal diferente para invocar o callback. Internamente, o que acaba sendo feito do lado do cliente - por parte do runtime do WCF - é a criação de um "endpoint" (incluindo uma porta disponível) para que o serviço relacionado consiga invocá-lo. Esse endereço temporário criado pelo WCF é registrado sob o Http.sys que é um componente de baixo nível e que faz parte do sistema de comunicação do Windows. Sendo assim, qualquer requisição que chegar para esse "endpoint", o Http.sys a encaminhará para o runtime do WCF que, finalmente, irá disparar a classe que implementa a interface de callback (mais detalhes a seguir) que, por sua vez, foi fornecida pelo contrato do serviço. A imagem abaixo ilustra como é realizada a comunicação entre servidor e cliente a partir do WSDualHttpBinding:

Figura 4 - Funcionamento do WSDualHttpBinding.

Para implementarmos esse tipo de comunicação, além de definirmos a interface que servirá como o contrato do serviço, precisaremos também criar uma segunda interface que especificará o callback (isso também é chamado de contrato de callback). Essa interface irá conter o método que deverá ser implementado pelo cliente e que o WCF irá disparar quando você achar necessário. Ao contrário de uma interface de contrato, a interface de callback apenas decora os métodos com o atributo OperationContractAttribute. O código abaixo ilustra como devemos proceder para criá-la:

using System;
using System.ServiceModel;

public interface ICallback
{
    [OperationContract]
    void NotificacaoRealizada(string msg);
}
Imports System
Imports System.ServiceModel

Public Interface ICallback
    <OperationContract()> _
    Sub NotificacaoRealizada(ByVal msg As String)
End Interface
C# VB.NET

Apesar das operações de callback não requererem que sejam one-way, é uma boa prática, já que diminui a possibilidade de deadlocks. Depois que o contrato de callback está definido, precisamos associá-lo ao contrato do serviço. Para isso, há uma propriedade chamada CallbackContract fornecida pelo atributo ServiceContractAttribute, que espera um tipo Type, especificando o contrato de callback. Uma vez associado o callback ao contrato de serviço, o WSDL irá contemplá-lo, permitindo que o cliente possa implementar o callback e informá-lo durante a criação do proxy. O código abaixo ilustra essa associação:

using System;
using System.ServiceModel;

[ServiceContract(CallbackContract = typeof(ICallback))]
public interface ICliente
{
    [OperationContract]
    void Notificar(string email);
}
Imports System
Imports System.ServiceModel

<ServiceContract(CallbackContract:=GetType(ICallback))> _
Public Interface ICliente
    <OperationContract()> _
    Sub Notificar(ByVal nome As String)
End Interface
C# VB.NET

Será a execução do serviço que determinará quando invocar o callback. Para que isso seja possível, primeiramente precisamos obter o canal de comunicação entre o serviço e o cliente e, para isso, recorrer à classe OperationContext. Ela fornece um método genérico chamado GetCallbackChannel que retornará a instância do canal entre o serviço e o cliente. Caso o retorno não seja nulo, então quer dizer que o cliente implementou (e se interessa) pelo callback. A partir de agora, no momento que achar conveniente, você poderá invocar o método de callback que será disparado no cliente. O exemplo abaixo exibe como invocar o callback:

using System;
using System.ServiceModel;

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall
    , ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class ServicoDeClientes : ICliente
{
    public void Notificar(string email)
    {
        bool houveProblemas = false;

        try
        {
            //faz a notificação
        }
        catch
        {
            houveProblemas = true;
        }
        finally
        {
            ICallback callback = 
                OperationContext.Current.GetCallbackChannel<ICallback>();

            string msg = 
                houveProblemas ? 
                "Houve problemas na notificação." :
                "Notificação realizada com sucesso.";

            if (callback != null)
                callback.NotificacaoRealizada(msg);
        }
    }
}
Imports System
Imports System.ServiceModel

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall, _
                 ConcurrencyMode:=ConcurrencyMode.Reentrant)> _
Public Class ServicoDeClientes
    Implements ICliente

    Public Sub Notificar(ByVal nome As String) _
        Implements ICliente.Notificar

        Dim houveProblemas As Boolean = False

        Try
            "faz a notificação
        Catch
            houveProblemas = True
        Finally
            Dim callback As ICallback = _
                OperationContext.Current.GetCallbackChannel(Of ICallback)()

            Dim msg As String = _
                If(houveProblemas, _
                   "Houve problemas na notificação.", _
                   "Notificação realizada com sucesso.")

            If Not IsNothing(callback) Then
                callback.NotificacaoRealizada(msg)
            End If
        End Try
    End Sub
End Class
C# VB.NET

Como dito anteriormente, o callback será contemplado no WSDL e, como o contrato do serviço, a interface que representa o callback também será disponibilizada para a implementação do lado do cliente. Essa interface deve ser implementada em uma classe, colocando o código desejado dentro do(s) método(s) fornecido(s) pela interface de callback. Com a classe implementada, você deverá informá-la na criação (construtor) do proxy. Essa nova versão do construtor do proxy aceita uma instância da classe InstanceContext. Essa classe envolve e gerencia a instância da classe onde a interface de callback foi implementada. Neste momento, o runtime do WCF ficará monitorando para que, quando o callback for disparado, o proxy seja notificado e, conseqüentemente, invocará o respectivo método do lado do cliente. O código ilustra essa configuração:

using System;
using System.ServiceModel;

using (ClienteClient proxy = 
    new ClienteClient(new InstanceContext(new GerenciadorDeRetorno())))
{
    proxy.Notificar("israel@projetando.net");
    Console.ReadLine();
}

//....

public class GerenciadorDeRetorno : IClienteCallback
{
    public void NotificacaoRealizada(string msg)
    {
        Console.WriteLine(msg);
    }
}
Imports System
Imports System.ServiceModel

Using proxy As New ClienteClient(New InstanceContext(New GerenciadorDeRetorno()))
    proxy.Notificar("israel@projetando.net")
    Console.ReadLine()
End Using

"...

Public Class GerenciadorDeRetorno
    Implements IClienteCallback

    Public Sub NotificacaoRealizada(ByVal msg As String) _
        Implements IClienteCallback.NotificacaoRealizada

        Console.WriteLine(msg)
    End Sub
End Class
C# VB.NET

Quando um callback faz parte do contrato, a instância da classe InstanceContext passa a ser obrigatória em qualquer uma das versões (overloads) do proxy. Um outro ponto importante é com relação ao tempo de vida do proxy pois, quando o callback acontecer, ele ainda deverá estar ativo para conseguir receber a notificação enviada pelo serviço.

Observação Importante: Quando você possibilita que callbacks sejam disparados pelo seu serviço, é importante que você se atente ao modelo de sincronização utilizado pelo mesmo pois, do contrário, você poderá enfrentar problemas de deadlocks. Caso você queira saber mais sobre os modos de gerenciamento de concorrência dentro do WCF e, principalmente, sobre o modo Reentrant, poderá recorrer à este artigo.

Conclusão: O artigo exibiu as três características possíveis que uma operação possa ser desenvolvida em WCF. Cada uma delas tem uma finalidade exclusiva que, se bem adotada, pode trazer grande melhoria em performance e também possibilitar uma maior interatividade entre cliente e serviço. Para finalizar, é importante dizer também que quando mencionado cliente neste artigo, podemos também estar nos referindo à outro serviço, algo que é perfeitamente possível.
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.