WCF – Usando o MsmqIntegrationBinding

Há algum tempo eu mostrei neste artigo como podemos fazer o uso do Message Queue com WCF, e lá foi discutido todos os benefícios de se envolver uma fila na comunicação entre as duas partes. A exigência daquele modelo de utilização é que ambos os lados precisam utilizar a API do WCF para se comunicar com o Message Queue.

O problema é que as vezes já temos uma solução implementada e operante. Antes do WCF, a única forma que tínhamos para interagir com o Message Queue na plataforma .NET era através das classes contidas no assembly System.Messaging.dll. Além disso há outras soluções que recebem ou enviam mensagens para uma fila, escritas em outras tecnologias.

Para trabalhar com Message Queue no WCF, temos dois bindings: NetMsmqBinding e MsmqIntegrationBinding, onde o primeiro deles já foi esgotado naquele outro artigo. O segundo trata-se de um binding criado exclusivamente para interagir com filas em que as mensagens sejam postadas ou recebidas por outras tecnologias que não o WCF. A finalidade deste artigo é mostrar como proceder para utilizar este binding e interoperar com soluções que já estão em funcionamento.

Para exemplificar vamos interoperar com aplicativos que recebem e postam mensagens utilizando a API System.Messaging.dll, que existe desde a primeira versão do .NET Framework. Abaixo temos dois cenários distintos, separados em duas pastas: WCFNoCliente e WCFNoServidor. No primeiro cenário utilizaremos o WCF para enviar uma mensagem para a fila e o serviço irá extrair a mensagem de lá utilizando as classes contidas no assembly System.Messaging.dll. No segundo cenário, será o inverso, ou seja, utilizaremos as classes contidas no assembly System.Messaging.dll para enviar a mensagem, e utilizaremos o WCF no serviço para extrair a mensagem da fila e processá-la. A imagem abaixo ilustra a estrutura dos projetos de teste:

O primeiro passo é desenhar o contrato do serviço, que irá formalizar a comunicação. A ideia será ter um serviço que processa pedidos de algum comércio eletrônico. Sendo assim teremos duas classes: Pedido e Cliente, que são autoexplicativas. Cada uma delas tem propriedades que as descrevem, que também não precisam ser exibidas aqui. Uma característica de serviços que são expostos através do Message Queue, é que as operações eles devem ser sempre one-way, assim como já foi discutido anteriormente. Abaixo temos a definição do contrato que será utilizado como exemplo:

[ServiceContract]
[ServiceKnownType(typeof(Pedido))]
[ServiceKnownType(typeof(Cliente))]
public interface IComercioEletronico
{
    [OperationContract(IsOneWay = true, Action = “*”)]
    void AdicionarPedido(MsmqMessage<Pedido> mensagem);
}

Infelizmente por se tratar de um cenário específico, a nossa operação deverá ter apenas um único parâmetro, e ele deve ser do tipo MsmqMessage<T>, que está debaixo do namespace System.ServiceModel.MsmqIntegration, e representará o body da mensagem. Caso você precise de mais parâmetros, então o correto é que você inclua isso em uma mesma classe e a defina como o parâmetro genérico T da classe MsmqMessage<T>. É importante dizer que o contrato irá definir a estrutura da mensagem a ser enviada para a fila (WCFNoCliente) ou a mensagem a ser extraída da fila (WCFNoServidor).

um detalhe importante na criação do contrato é o uso do atributo ServiceKnownTypeAttribute, que é exigido para todos os elementos mencionados direta ou indiretamente no contrato. Isso é necessário para que o serializador conheça todos os tipos envolvidos, caso contrário, a mensagem não será recebida pelo WCF, e ao ligar o tracing, vamos nos deparar com a seguinte mensagem de erro:

System.ServiceModel.ProtocolException: An error was encountered while deserializing the message. The message cannot be received. —&gt; System.Runtime.Serialization.SerializationException: An error occurred while deserializing an MSMQ message’s XML body. The message cannot be received. Ensure that the service contract is decorated with appropriate [ServiceKnownType] attributes or the TargetSerializationTypes property is set on the MsmqIntegrationBindingElement.

Utilizando o WCF no Servidor

Como será um serviço WCF que irá extrair as mensagens da fila, o primeiro passo aqui é a implementação do contrato na classe que representará o serviço. Aqui tudo é muito parecido com um serviço WCF tradicional, mas com uma pequena diferença que está na operação. Como a classe MsmqMessage<T> representa a mensagem que está dentro da fila, ela é muito mais do que a instância da classe Pedido, ou seja, temos outras propriedades que estão relacionadas ao protocolo.

Entre as várias propriedades expostas pela classe MsmqMessage<T> temos a propriedade Body, que retorna um object que representa a instância da classe que foi serializada no corpo da mensagem pelo cliente, que no nosso caso é a classe Pedido. Abaixo temos a implementação do serviço com o processamento da mensagem:

public class ServicoDeComercioEletronico : IComercioEletronico
{
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public void AdicionarPedido(MsmqMessage<Pedido> mensagem)
    {
        Pedido pedido = mensagem.Body as Pedido;

        if (pedido != null)
        {
            Console.WriteLine(“—- Processando o Pedido: {0}”, pedido.Codigo);
            Console.WriteLine(“—- Cliente: {0}”, pedido.Cliente);
            Console.WriteLine(“—- Valor: {0:N2}”, pedido.ValorTotal);
            Console.WriteLine(“—- Data: {0:dd/MM/yyyy HH:mm}”, pedido.Data);
            Console.WriteLine();
        }
    }
}

Note que decoramos a operação com o atributo OperationBehaviorAttribute definindo as propriedades TransactionScopeRequired e TransactionAutoComplete para True, pois utilizando o binding MsmqIntegrationBinding não temos o controle explícito das transações, que para isso exige que o binding suporte sessões, e não é o caso do MsmqIntegrationBinding.

Depois da implementação definida, agora chega o momento de configurar o serviço. Como já sabemos, vamos recorrer ao binding MsmqIntegrationBinding. Para uma fácil interoperabilidade, o endereço em que o serviço é exposto deve utilizar o scheme msmq.formatname ao invés do net.msmq, que permite entre vários modelos, o acesso direto a fila (através de format-name), sem a necessidade de contatar o Active Directory (AD). Abaixo temos a configuração declarativa do serviço:

<system.serviceModel>
  <services>
    <service name=”Servico.ServicoDeComercioEletronico”>
      <endpoint address=”msmq.formatname:DIRECT=OS:.private$MinhaFila”
                binding=”msmqIntegrationBinding”
                bindingConfiguration=”bindingConfig”
                contract=”Biblioteca.IComercioEletronico” />
    </service>
  </services>
  <bindings>
    <msmqIntegrationBinding>
      <binding name=”bindingConfig”>
        <security mode=”None” />
      </binding>
    </msmqIntegrationBinding>
  </bindings>
</system.serviceModel>

Com o serviço criado, podemos agora inicializarmos a criação do cliente. Obviamente que não iremos referenciar o serviço, mesmo porque nem criamos o endpoint que expõe o documento WSDL. A ideia aqui será utilizar os tipos que estão dentro do assembly System.Messaging.dll para enviar a instância da classe Pedido para a fila, e utilizar o WCF do outro lado para processar.

Depois do assembly referenciado na aplicação, instanciamos a classe MessageQueue e informamos o caminho para a fila (via path-name). Depois instanciamos a classe Pedido e a configuramos com os dados do Pedido que foi realizado e, finalmente, enviamos a instância para a fila através do método Send. Neste momento, a instância da classe Pedido será serializada como o body da mensagem.

using (MessageQueue queue = new MessageQueue(@”.private$MinhaFila”))
{
    Pedido pedido = new Pedido();
    pedido.Codigo = 123;
    pedido.Data = DateTime.Now;
    pedido.ValorTotal = 1983.81M;
    pedido.Cliente = new Cliente() { Codigo = 39, Nome = “Israel Aece” };

    queue.Send(pedido, MessageQueueTransactionType.Single);
    Console.WriteLine(“Mensagem Enviada com Sucesso.”);
}

Observação: Como a fila é transacionada, precisamos definir o tipo da transação a ser utilizada pelo Message Queue no momento do envio da mensagem. Se o controle da transação está sendo controlada por um elemento externo, como é o caso do TransactionScope, então é necessário definir o MessageQueueTransactionType para Automatic, para que o envio da mensagem possa participar da mesma transação, criada previamente.

Utilizando o WCF no Cliente

Neste cenário inverteremos as tecnologias que utilizamos. Agora no serviço utilizaremos os tipos do assembly System.Messaging.dll e no cliente o WCF. Aqui criaremos a instância da classe MessageQueue com a finalidade de extrair a mensagem que lá foi postada, enviada por um cliente que utiliza o WCF. E para isso utilizaremos o método Receive, que da mesma forma que o método Send, também precisa estar protegido por uma transação.

O método Receive retorna uma instância da classe Message (namespace System.Messaging), que entre várias propriedades, temos a Body, que retorna um object representando a classe que foi serializada, e que no nosso caso é a classe Pedido. O código abaixo ilustra como fazemos para extrair a mensagem:

using (MessageQueue queue = new MessageQueue(@”.private$MinhaFila”))
{
    queue.Formatter = 
        new XmlMessageFormatter(new Type[] { typeof(Pedido), typeof(Cliente) });

    Pedido pedido = queue.Receive(MessageQueueTransactionType.Single).Body as Pedido;

    if (pedido != null)
    {
        Console.WriteLine(“—- Processando o Pedido: {0}”, pedido.Codigo);
        Console.WriteLine(“—- Cliente: {0}”, pedido.Cliente);
        Console.WriteLine(“—- Valor: {0:N2}”, pedido.ValorTotal);
        Console.WriteLine(“—- Data: {0:dd/MM/yyyy HH:mm}”, pedido.Data);
        Console.WriteLine();
    }
}

Da mesma forma que fizemos acima com o contrato do WCF, decorando-o com o atributo ServiceKnownTypeAttribute, temos também que mencionar quais sãos os tipos que compõem o body da mensagem que está na fila, e para isso podemos recorrer a instância da classe XmlMessageFormatter, e em seu contrutor informar um array contendo os tipos envolvidos.

Como não há referência do serviço no cliente, precisamos fazer com que o cliente conheça qual é o contrato do serviço, e justamente por isso que ele foi colocado em uma DLL a parte, chamada de Biblioteca. Utilizamos no cliente a classe ChannelFactory<TChannel> para criar a canal de comunicação entre a aplicação cliente e a fila.

Além do contrato, devemos também informar o binding que irá reger a comunicação, e que como sabemos, será o binding MsmqIntegrationBinding. Em seguida informarmos o endereço (format-name) da fila em que a mensagem será postada. Com a factory criada, agora podemos criar a instância do canal que é efetivamente o proxy:

using (ChannelFactory<IComercioEletronico> factory =
    new ChannelFactory<IComercioEletronico>(
        new MsmqIntegrationBinding(MsmqIntegrationSecurityMode.None),
        @”msmq.formatname:DIRECT=OS:.private$MinhaFila”))
{
    var proxy = factory.CreateChannel();

    Pedido pedido = new Pedido();
    pedido.Codigo = 123;
    pedido.Data = DateTime.Now;
    pedido.ValorTotal = 1983.81M;
    pedido.Cliente = new Cliente() { Codigo = 39, Nome = “Israel Aece” };

    proxy.AdicionarPedido(new MsmqMessage<Pedido>(pedido));
    Console.WriteLine(“Mensagem Enviada com Sucesso.”);
}

Note que depois da instância da classe Pedido criada e configurada, ela não é passada diretamente para o método AdicionarPedido. Como foi comentado acima, o contrato impõe que a classe Pedido seja envolvida por um wrapper (MsmqMessage<T>), que representará a mensagem dentro do fila, e a instância da classe Pedido será armazenada/serializada no body dela.

Assincronismo

Como sabemos, uma das características do uso do Message Queue é garantir o assincronismo, e permitir ao cliente enviar mensagens ao serviço mesmo que ele esteja offline, mascarando assim eventuais problemas que existam na comunicação entre as duas partes envolvidas.

Conclusão: No decorrer deste artigo vimos que podemos substituir a tecnologia que existe de um dos lados para uma tecnologia mais recente, como é o caso do WCF, e com isso tirar todo o proveito de seus recursos, mas tendo em mente que existem algumas poucas limitações e imposições.

UtilizandoMsmqIntegrationBinding.zip (20.48 kb)

Message Queue no WAS

Há algum tempo eu mostrei publiquei aqui um artigo que demonstra como utilizar serviços WCF em conjunto com o Message Queue, que como sabemos, leva a confiabilidade dos serviços para um outro nível. Para efeito de testes, aquele exemplo usa uma aplicação Console para hospedar o serviço que faz uso do Message Queue.

Uma das opções de hosting que temos é o WAS (Windows Activation Service), que incorpora ao IIS funcionalidades extras, permitindo ao mesmo hospedar serviços que vão além do protocolo HTTP. Quando habilitamos o WAS, ele também fornece um listener para gerenciar serviços que são hospedados no IIS mas fazem uso do Message Queue. Esse serviço chamado de Net.Msmq Listener Adapter, é o responsável pela ativação do worker process que gerencia a execução dos serviços WCF hospedados no IIS.

O serviço de ativação nada mais é que um serviço Windows, que quando é inicializado, percorre todas as filas públicas e privadas que existem no computador, qual monitorará as mensagens que chegarem para elas, e quando uma mensagem chegar, irá entregá-la para o serviço WCF, que por sua vez, irá processá-la. Mas para que ele consiga lidar tranquilamente com o Message Queue, alguns cuidados precisam ser tomados.

O primeiro é com relação ao nome da fila. Para que o WAS consiga entregar as mensagens de forma transparente, é importante que o nome da fila tenha o mesmo nome do diretório virtual, incluindo o arquivo *.svc. Por exemplo, se temos um diretório virtual chamado Servicos e o serviço WCF nomeado como ServicoDePedidos.svc, então o nome da fila deverá ser Servicos/ServicoDePedidos.svc. Outro detalhe importante é que as filas do Message Queue também são protegidas por ACLs, o que nos obriga a fazer com que a conta definida no worker process (muitas vezes o Network Service) também tenha acesso à fila em questão.

Com essas configurações, já temos os serviços fazendo uso do Message Queue e, consequentemente, explorando todo o potencial das funcionalidades que o IIS e o AppFabric nos disponibiliza.

Herança de Interfaces em contratos de serviço

Assim como as classes, as interfaces também podem fazer uso da herança para agregar funcionalidades e características de outras interfaces, e quando implementamos uma interface que herda de outra, todos os membros envolvidos na hierarquia deverão ser definidas na classe.

Como sabemos, são as interfaces que definem os contratos de serviços WCF, e podemos fazer uso de herança de interfaces para refinar o acesso à operações, conseguindo filtrar o que será exposto através de um determinado endpoint. Considere as interfaces a seguir:

[ServiceContract]
interface IContrato1
{
    [OperationContract]
    string Metodo1(string value);
}

[ServiceContract]
interface IContrato2 : IContrato1
{
    [OperationContract]
    string Metodo2(string value);
}

Note que a interface IContrato2 herda da interface IContrato1, o que obrigará a classe (serviço) que implementar a interface IContrato2, também definir a implementação para o Metodo1, definido na interface IContrato1. Como na criação do endpoint somos obrigados a informar um contrato, podemos permitir que internamente o consumidor veja ambos os métodos, pois estamos expondo o contrato IContrato2, enquanto externamente, o consumidor somente irá visualizar as operações expostas pelo contrato IContrato1, que neste caso é o método Metodo1.

host.AddServiceEndpoint(typeof(IContrato1), new BasicHttpBinding(), “contratoExterno”);
host.AddServiceEndpoint(typeof(IContrato2), new NetTcpBinding(), “contratoInterno”);

O fato de existir a herança, permitirá ao consumidor interno visualizar as operações do contrato IContrato2 e também as operações expostas pelo contrato IContrato1, dando a ele todas as funcionalidades que foram fornecidas pela herança dos contratos (interfaces).

System.Json no .NET Framework

Para aqueles que estiveram presentes na minha palestra sobre REST no TechEd 2010, puderam ver as opções que temos para a construção e consumo de serviços baseados neste modelo. Tive a oportunidade de mostrar de forma superficial, cada uma das tecnologias que circundam o WCF, e que foram construídas em cima dele, para atender a necessidade atual.

Já falei aqui sobre o WCF REST Starter Kit (HttpClient), que torna o consumo de serviços REST (construídos ou não em .NET) muito mais simples em aplicações .NET (Console, Windows, WPF, ASP.NET, etc.), abstraindo a necessidade de conhecer detalhes internos do protocolo HTTP, lidando facilmente com os formatos existentes, tais como: Xml, Atom e JSON.

O WCF já fornece suporte a construção de serviços REST há algum tempo. Temos a possibilidade de expor serviços neste modelo, possibilitando a serialização em qualquer um dos formatos acima, mas ainda, com um pequeno suporte para lidar com objetos que são criados dinamicamente por seus clientes. Isso quer dizer que o WCF, até então, exige que o cliente deva conhecer a estrutura do objeto que o serviço espera. O problema é que isso pode ser dinamicamente criado pelos clientes, algo que o JavaScript pode fazer muito bem.

Para ter uma integração maior com estes tipos de clientes, a Microsoft está incorporando ao WCF recursos para suportar melhor este tipo de cenário. Para isso, um novo conjunto de classes está sendo colocando dentro do .NET Framework, que representarão objetos JSON, algo que o Silverlight já possui há algum tempo, através de uma API chamada System.Json. Agregando isso no .NET Framework e, consequentemente, no WCF, podemos construir serviços que recebam e retornem objetos JSON, podendo combinar isso com variáveis do tipo dynamic, que também são suportadas pela linguagem.

É importante dizer que o que temos disponível atualmente, ainda está em fase de desenvolvimento, e o que verá aqui, poderá sofrer alguma alteração até a versão final. Isso está disponível no Codeplex, neste endereço. Várias novidades estão disponíveis, mas o foco neste artigo é apresentar a integração melhorada com JSON. Ao instalar este pacote, temos dois assemblies que compõem estas novas funcionalidades: Microsoft.Runtime.Serialization.Json.dll e Microsoft.ServiceModel.Web.jQuery.dll.

O primeiro assembly é onde temos o namespace System.Json, assim como no Silverlight. É dentro dele que estão todas as classes que servem para construir e representar objetos em formato JSON. A base para todas elas é a JsonValue, que define a estrutura de um objeto JSON dentro do .NET Framework, contendo também as funcionalidades comuns para todos os outros tipos JSON: JsonPrimitive, JsonObject e JsonArray. Todos herdam diretamente de JsonValue, implementando as funcionalidades específicas para cada uma delas.

Como sabemos, a representação JSON de um objeto nada mais é que um dicionário, e devido a isso, a classe JsonValue implementa a interface IEnumerable<KeyValuePair<string, JsonValue>>, para permitir a iteração dos valores que ela armazena internamente. JsonPrimitive, como o próprio nome diz, refere-se aos tipos primitivos suportados no JavaScript: String, Number e Boolean. Depois temos a classe JsonObject, que define uma coleção de JsonValue, e seu indexador é do tipo string (chave) e, finalmente, o JsonArray trata-se de uma coleção que armazena instâncias da classe JsonValue, e seu indexador é um número inteiro. Abaixo temos uma imagem que mostra a hierarquia destas classes:

No outro assembly, temos a implementação de objetos específicos que fazem parte da infraestrutura do WCF, criados para que o mesmo consiga facilmente receber e interprertar as requisições realizadas que recebem e retornam objetos específicos para este modelo. Entre as classes que fazem parte deste novo assembly, temos: WebHttpBehavior3, WebServiceHost3 e WebserviceHostFactory3, seguindo a mesma linha das classes que foram criadas quando o REST foi introduzido no WCF. Basicamente a necessidade destas classes é justamente permitir que o behavior WebHttpBehavior3 seja acoplado a execução, que efetua algumas validações na requisição para determinar se ela está de conformidade com os objetos suportados, e além disso, acopla também um tratador de erro específico, convertendo eventuais problemas em objetos do tipo JsonObject.

Para exemplificar, vamos criar um serviço que será responsável por armazenar uma lista de itens, que será manipulada por um formulário HTML, recorrendo ao jQuery para dialogar com o respectivo serviço. O detalhe mais importante a se notar, são os tipos envolvidos nos parâmetros e no retorno dos métodos, que como podemos perceber, fazem parte da nova API que vimos acima. Abaixo temos a implementação deste serviço:

[ServiceContract]
public class ServicoDeCache
{
    private List<JsonObject> itens = new List<JsonObject>();

    [WebInvoke]
    public void Adicionar(JsonObject item)
    {
        this.itens.Add(item);
    }

    [WebGet]
    public JsonObject Extrair(int codigo)
    {
        return 
            (
                from i in this.itens 
                where i.AsDynamic().Codigo.ReadAs<int>() == codigo 
                select i
            ).SingleOrDefault();
    }

    [WebGet]
    public JsonArray Recuperar()
    {
        return new JsonArray(this.itens.ToArray());
    }
}

Os métodos são autoexplicativos, e manipulam uma lista interna que serve como repositórios para os itens. O método Extrair é aquele que merece uma maior atenção. Ele recebe um inteiro como parâmetro e retorna a instância da classe JsonObject, recorrendo à “LINQ To JSON” para extrair o elemento solicitado pelo cliente. O que chama atenção também é o método AsDynamic, que é declarado na classe JsonValue, e como havia mencionado, converte a instância do objeto atual em dynamic, permitindo o acesso as propriedades criadas pelo cliente de forma dinâmica (late-bound). Depois temos o método genérico ReadAs<T>, que também está acessível através da classe JsonValue, que recorre ao serializador JSON para tentar extrair o valor tipado daquela propriedade específica.

Observação: Como podemos perceber, diferentemente de serviços que construímos até então, a classe que representa o serviço não implementa uma interface de contrato. Isso se deve ao fato de serviços REST não possuem um contrato definido, sem geração de metadados e sem operações (já que serviços REST são baseados em recursos). Agradeço aqui ao Carlos Figueira, membro do time de WCF, que me esclareceu essa questão.

Depois do serviço implementado, temos que nos preocupar em expor o serviço através do host correto, e como vimos acima, deverá ser através do WebServiceHost3. Existem algumas formas de fazer isso, e uma delas é utilizar o Url Routing para determinar que o host para este serviço deverá ser o WebServiceHost3. Isso já é o suficiente para deixar o serviço disponível para consumo, pois ele se encarrega de criar os endpoints necessários, sem ter necessidade de criar qualquer configuração no arquivo Web.config. O código abaixo deve ser declarado no arquivo Global.asax, para registrar a rota.

public class Global : HttpApplication
{
    private void Application_Start(object sender, EventArgs e)
    {
        this.RegisterRoutes();
    }

    private void RegisterRoutes()
    {
        RouteTable.Routes.Add(
            new ServiceRoute(
                “ServicoDeCache”, 
                new WebServiceHostFactory3(), 
                typeof(ServicoDeCache)));
    }
}

Finalmente, depois de toda essa configuração reliazada, chega o momento de consumir o serviço através de algum cliente. Poderia ser através do Fiddler, mas para exemplificar melhor, vamos utilizar o jQuery para invocar duas das operações que disponibilizamos acima: Adicionar e Recuperar.

http://scripts/jquery-1.4.2.min.js
http://scripts/json2.js

    function Adicionar() {
        var nome = $(‘#Nome’).val();
        var codigo = $(‘#Codigo’).val();
        var item = { Nome: nome, Codigo: JSON.parse(codigo) };

        $.ajax(
            {
                type: ‘POST’,
                url: ‘./ServicoDeCache/Adicionar’,
                dataType: ‘json’,
                contentType: ‘application/json’,
                data: JSON.stringify(item),
                processData: false,
                success:
                    function () {
                        alert(‘Item cadastrado com sucesso.’);
                    }
            });
    }

    function Recuperar() {
        $.ajax(
            {
                type: ‘GET’,
                url: ‘./ServicoDeCache/Recuperar’,
                dataType: ‘json’,
                contentType: ‘application/json’,
                data: null,
                processData: false,
                success:
                    function (itens) {
                        $(‘#Itens’).empty();

                        $.each(itens, function (indice, item) {
                            $(‘#Itens’)
                                .append($(”)
                                .attr(‘value’, item.Codigo)
                                .text(item.Nome));
                        });
                    }
            });
    }

O primeiro método constrói um objeto com duas propriedades: Codigo e Nome, que são abastecidas com informações do formulário, e em seguida, utilizamos a biblioteca JSON2 para transformá-lo em JSON. Já o método Recuperar retorna um array contendo os elementos que foram adicionados na coleção interna do serviço, recorrendo ao método $.each do jQuery para iterar pelos dados e exibí-los na tela. Se quiser saber mais sobre consumo de serviços através do jQuery, consulte este endereço.

Conclusão: Vimos no decorrer deste artigo algumas novidades que a Microsoft está adicionando ao WCF e ao .NET Framework, possibilitando serviços WCF se adequar melhor à serviços baseados REST e JSON, bem como facilitar o consumo destes mesmo serviços por aplicações .NET “tradicionais”.

Filtros para MessageLogging

Já mostrei neste artigo e vídeo como podemos fazer para habilitar o log de mensagens do WCF, onde podemos capturar o envelope da mensagem SOAP que está sendo trafegado entre as partes envolvidas.

Somente o fato de habilitar já é o suficiente para capturar todas as mensagens que chegam ao serviço e também aquelas que são devolvidas para os respectivos clientes, contendo o resultado do processamento da operação. Dependendo do tamanho do envelope e do volume de requisições, em pouco tempo, você pode consumir uma grande quantidade de espaço em disco.

Para melhorar isso, o message logging do WCF fornece filtros que podemos aplicar aos headers do envelope SOAP, e com isso, analisar e decidir se queremos ou não catalogar aquela mensagem. Para exemplificar, considere o contrato abaixo, que possui duas operações simples:

[ServiceContract(Namespace = “http://www.israelaece.com/servicos“)]
public interface IContrato
{
    [OperationContract]
    ItemDeExtrato[] VisualizarExtrato(string conta);

    [OperationContract]
    void Debitar(string conta, decimal valor);
}

A operação VisualizarExtrato retorna um array de itens que fazem parte de um extrato de uma determinada conta, informada através do parâmetro conta. Já a segunda operação, recebe a conta e o valor a ser debitado da mesma. Como sabemos, podemos ligar o logging do WCF para começarmos a capturar as mensagens que chegam para este serviço como um todo. O código abaixo ilustra essa configuração:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name=”System.ServiceModel.MessageLogging”
              switchValue=”All”
              propagateActivity=”true”>
        <listeners>
          <add name=”Xml”
               type=”System.Diagnostics.XmlWriterTraceListener”
               initializeData=”C:TempMessageLogging.svclog” />
        </listeners>
      </source>
    </sources>
    <trace autoflush=”true” />
  </system.diagnostics>
  <system.serviceModel>
    <diagnostics>
      <messageLogging logEntireMessage=”true”
                      logMessagesAtServiceLevel=”true”
                      maxMessagesToLog=”100″
                      maxSizeOfMessageToLog=”200000″ />
    </diagnostics>
  </system.serviceModel>
</configuration>

Com isso, ao abrir o arquivo gerado pelo logging recém habilitado, veremos a mensagem que chegou para o serviço com a requisição de débito:

<s:Envelope xmlns:a=”http://www.w3.org/2005/08/addressing&#8221;
            xmlns:s=”http://www.w3.org/2003/05/soap-envelope”&gt;
  <s:Header>
    <Action a:mustUnderstand=”1″
            xmlns=”http://www.w3.org/2005/08/addressing&#8221;
            xmlns:a=”http://www.w3.org/2003/05/soap-envelope”&gt;http://www.israelaece.com/servicos/IContrato/Debitar</Action>
    <a:MessageID>urn:uuid:25eb06f3-b1d5-41a4-99c8-37f1a5905d7f</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address&gt;
    </a:ReplyTo>
    <a:To s:mustUnderstand=”1″>net.tcp://localhost:9392/srv</a:To>
  </s:Header>
  <s:Body>
    <Debitar xmlns=”http://www.israelaece.com/servicos”&gt;
      <conta>000001</conta>
      <valor>1200</valor>
    </Debitar>
  </s:Body>
</s:Envelope>

Mas o problema é que também as mensagens que chegam para a operação VisualizarExtrato também serão catalogadas, e como elas possuem uma grande quantidade de informações, neste caso, não temos a necessidade de monitorar essas mensagens. É aqui que entra em cena os filtros, tema deste artigo. Dentro do elemento messageLogging temos um sub-elemento chamado de filters, que nada mais é que uma coleção, onde você pode elencar diversos filtros, utilizando a linguagem/tecnologia XPath para analisar e escrever tais filtros. Exemplo:

<system.serviceModel>
  <diagnostics performanceCounters=”All”>
    <messageLogging logEntireMessage=”true”
                    logMessagesAtServiceLevel=”true”
                    logMessagesAtTransportLevel=”false”
                    maxMessagesToLog=”100″
                    maxSizeOfMessageToLog=”200000″>
      <filters>
        <add xmlns:a=”http://www.w3.org/2005/08/addressing&#8221;
             xmlns:s=”http://www.w3.org/2003/05/soap-envelope”>/s:Envelope/s:Header/a:Action%5Btext()=&#8221;http://www.israelaece.com/servicos/IContrato/Debitar“]</add>
      </filters>
    </messageLogging>
  </diagnostics>
</system.serviceModel>

Modificando o arquivo de configuração do serviço, estamos acrescentando um filtro que utilizaremos para catalogar as mensagens somente se a Action (que representa a operação) for igual a Debitar, descartando assim todas as mensagens que chegam para as outras operações e, consequentemente, eventuais dados gerados por elas.

Compartilhando recursos do ASP.NET entre proxies WCF

Quando hospedamos serviços WCF no IIS, podemos permitir ao serviço utilizar os recursos fornecidos pelo ASP.NET. Entre esses recursos, temos as variáveis de aplicação, de sessão, cookies, caching, etc. Para permitir isso, tudo o que precisamos fazer é habilitar através de um atributo na classe que representa o serviço, chamado de AspNetCompatibilityRequirementsAttribute. Ele fornece uma propriedade chamada RequirementsMode, que aceita uma das opções impostas pelo enumerador AspNetCompatibilityRequirementsMode. O código abaixo ilustra a sua configuração:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Servico01 : IServico01
{
    public string Ping(string value)
    {
        if (HttpContext.Current.Session[“Teste”] == null)
            HttpContext.Current.Session[“Teste”] = value;

        return HttpContext.Current.Session[“Teste”] as string;
    }
}

Note que na implementação estamos fazendo uso de variáveis de sessão. Além disso, precisamos ligar explicitamente essa compatibilidade com o ASP.NET no arquivo Web.config, que é utilizado pela aplicação que hospeda o serviço, e para isso, definimos o atributo aspNetCompatibilityEnabled do elemento serviceHostingEnvironment para True, conforme é mostrado abaixo:

<?xml version=”1.0″?>
<configuration>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
  </system.serviceModel>
</configuration>

E com essas configurações podemos de dentro de serviços WCF, utilizar as funcionalidades expostas pelo ASP.NET. É importante dizer que variáveis de sessão são controladas através de cookies, e mesmo em serviços WCF, eles continuam sendo utilizados para relacionar um determinado cliente e as suas respectivas variáveis de sessão que estão armazenadas no servidor.

Quando estamos publicando o nosso serviço através de um binding HTTP (BasicHttpBinding ou WSHttpBinding), eles possuem uma propriedade boleana chamada AllowCookies. O nome desta propriedade pode parecer confuso, pois a finalidade dela não é permitir que o binding utilize ou não cookies, mas sim determinar quem deverá manipulá-los.

Quando esta propriedade estiver definida como True (o padrão é False), o próprio runtime do WCF irá gerenciar os cookies. Isso quer dizer que ele criará uma instância da classe CookieContainer e a associará ao serviço, utilizando-a para todas as requisições subsequentes, ou seja, todo e qualquer cookie retornardo pelo serviço, será automaticamente reenviado nas futuras requisições para este mesmo serviço, sem a necessidade de controlar como isso é realizado.

O problema aparece quando temos mais do que um serviço dentro da aplicação (WebSite). Serviços diferentes, exigem proxies diferentes no cliente, e os cookies estão vinculado à um proxy específico, ou seja, se você utiliza dois proxies, sendo um para cada serviço (*.svc), os cookies não serão compartilhados, e com isso, os cookies e, consequentemente, variáveis de sessão, não serão compartilhados entre eles (serviços), gerando assim um resultado inesperado. A imagem abaixo ilustra a estrutura do lado do servidor, e como sabemos, existirá um proxy para cada serviço.

A implementação do Servico02 para o teste é extremamente simples, ou seja, ele apenas lê a informação de uma variável de sessão que foi criada pelo Servico01. Apesar deste serviço também estar configurado para exigir o modo de compatibilidade, isso não resolverá, já que o problema reside do lado do cliente, pois como ele não consegue manter o cookie entre os proxies, quando tentarmos executar este segundo serviço, o WCF e o ASP.NET entendem que não foi criada a sessão e, consequentemente, retornará nulo.

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Servico02 : IServico02
{
    public string Ping(string value)
    {
        return HttpContext.Current.Session[“Teste”] as string;
    }
}

Para começar a resolver o problema, o primeiro passo é desligar o controle automático de cookies pelo WCF, definido a propriedade AllowCookies para False (que já é o padrão), assim como é mostrado abaixo. É importante dizer que essa propriedade deve estar devidamente configurada no cliente, que com isso, será responsável por gerenciar manualmente os cookies gerados pelo serviço.

<bindings>
    <basicHttpBinding>
        <binding allowCookies=”false” />
    </basicHttpBinding>
</bindings>

Depois dessa configuração, o resto será através do próprio código. Se notarmos no exemplo abaixo, podemos perceber que temos os dois proxies, sendo um para cada serviço. Temos também uma string que receberá o valor do cookie gerado, que corresponderá ao Id da sessão. Logo depois da criação do proxy para o primeiro serviço, instanciamos a classe OperationContextScope, que cria um bloco de acesso ao contexto da operação, tendo acesso a recursos como headers e properties do serviço. Já dentro deste bloco, utilizamos a classe HttpResponseMessageProperty para extrairmos os valores pertinentes a resposta da execução do método Ping. Note que ela está sendo acessada depois da chamada ao método Ping. Com a coleção de headers em mãos, utilizamos o header SetCookie para extrair o valor do cookie de sessão do ASP.NET (ASP.NET_SessionId) e guardamos na string sessionIdCookie.

Se ainda invocarmos o método Ping a partir do mesmo proxy, não teremos problema, ou seja, ele conseguirá manter a variável de sessão. Mas aqui a situação é outra, ou seja, temos um segundo proxy para o consumo de um outro serviço, mas que queremos utilizar a mesma variável de sessão, gerada anteriormente. Para o segundo proxy, nós fazemos o processo inverso, ou seja, criamos o escopo de acesso aos recursos da requisição, e construímos a instância da classe HttpRequestMessageProperty e configuramos o cookie com aquele valor que salvamos depois de invocar o método através do primeiro proxy.

static void InvocarServico()
{
    string sessionIdCookie = null;

    using (Servico01Client proxy1 = new Servico01Client())
    {
        using (new OperationContextScope(proxy1.InnerChannel))
        {
            Console.WriteLine(proxy1.Ping(“teste”));

            HttpResponseMessageProperty response =
                OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;

            sessionIdCookie = response.Headers[HttpResponseHeader.SetCookie];
        }
    }

    using (Servico02Client proxy2 = new Servico02Client())
    {
        using (new OperationContextScope(proxy2.InnerChannel))
        {
            if (!string.IsNullOrWhiteSpace(sessionIdCookie))
            {
                HttpRequestMessageProperty request = new HttpRequestMessageProperty();
                request.Headers[HttpRequestHeader.Cookie] = sessionIdCookie;

                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = request;
            }

            Console.WriteLine(proxy2.Ping(“outro valor”));
    }
}

Com isso tudo criado, agora o método Ping do proxy2 será capaz de retornar o valor gerado e armazenado na variável de sessão através do proxy1. Caso você não utilize essa técnica, a requisição para os métodos do proxy2 serão enviadas sem o cookie, e com isso, o WCF e o ASP.NET não saberá que existe variáveis de sessão já criadas para aquele usuário, e com isso, não terá acesso as informações previamente criadas.

Utilizando async/await com proxies WCF

Como comentei anteriormente, a Microsoft está fazendo grandes melhorias para a próxima versão do C#, com o intuito de facilitar a programação assíncrona dentro do .NET Framework. No artigo mencionado, lá temos como proceder para invocar um código de forma assíncrona neste novo modelo, que é através da utilização da keyword async no método que deverá ser invocado de forma assíncrona, e dentro dele, utilizamos a keyword await, para dizer que a região a seguir trata-se de um callback.

Como sabemos, os delegates já trazem nativamente suporte para que eles sejam invocados de forma síncrona ou assíncrona. Mas como o modelo de programação muda partir destas novas funcionalidades que estão sendo criadas no C#, ou seja, passaremos a utilizar a classe Task<T> para representar o processo assíncrono, então podemos recorrer a um método chamado FromAsync da classe TaskFactory, que foi incluído no .NET Framework a partir da versão 4.0. Fazendo uso deste método, podemos invocar um delegate de forma assíncrona utilizando o seguinte código:

Func<string, string> ping = s => s + ” ping!”;

var task =
    Task.Factory.FromAsync<string, string>(
        ping.BeginInvoke, ping.EndInvoke, “Teste”, null);

Como podemos perceber no código acima, informamos para o método FromAsync o par de métodos Begin/End que compõem a chamada assíncrona para uma tarefa, dando origem assim à uma instância da classe Task, que daqui em diante, podemos utilizar em conjunto com o novo modelo que está sendo proposto pela Microsoft.

Mas como podemos utilizar esta técnica para consumir serviços WCF? Como já comentei neste artigo, podemos optar durante a referência para o serviço na aplicação cliente (Add Service Reference), por marcar a opção “Generate asynchronous operations”, e com isso, para cada operação existente no serviço, serão criadas três métodos, sendo um para a chamada síncrona, e os outros dois irão compor a chamada assíncrona (BeginNomDaOperacao/EndNomeDaOperacao). Sabendo que há como transformar o modelo de Begin/End em Tasks, poderíamos utilizar o código acima para consumir o serviço WCF de forma assíncrona e recorrer ao novo modelo. Mas ao invés de termos todo esse trabalho, já há uma forma de fazer com que o proxy também seja gerado com as opções de Tasks.

Para isso, utilizamos uma implementação que a Microsoft criou chamada de TaskAsyncWsdlImportExtension. Essa classe intercepta a referência do serviço na aplicação cliente, e cria a versão assíncrona baseando-se em Tasks para todas as operações que ele encontra no documento WSDL. Para isso funcionar, devemos referenciar a DLL que consta essa classe no projeto cliente (ela está incluída nos exemplos do CTP). Depois disso, devemos modificar o arquivo de configuração para definirmos essa estensão, assim como é mostrado através do código abaixo:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
 <system.serviceModel>
  <client>
   <metadata>
    <wsdlImporters>
     <extension type=”TaskWsdlImportExtension.TaskAsyncWsdlImportExtension, TaskWsdlImportExtension…” />
    </wsdlImporters>
   </metadata>
  </client>
 </system.serviceModel>
</configuration>

Em seguida, devemos efetuar a referência para o serviço e marcar a opção para a geração das operações assíncronas. Isso já é o suficiente para a classe TaskAsyncWsdlImportExtension entrar em ação e construir a nova versão deste método.

public partial class ContratoClient : ClientBase<IContrato>, IContrato
{
    public Task<string> PingAsync(string value)
    {
        return Task<string>.Factory.FromAsync(
            new Func<string, AsyncCallback, object, IAsyncResult>(((IContrato)(this)).BeginPing),
            new Func<IAsyncResult, string>(((IContrato)(this)).EndPing), value, null);
    }
}

A partir de agora, podemos efetivamente recorrer ao novo modelo de programação assíncrona do C#, ou seja, com a instância da classe Task em mãos, podemos fazer uso dela dentro de métodos marcados como async e, consequentemente, utilizar a keyword await para determinar o que queremos fazer com o resultado quando ele for devolvido. Note que o código fica bem mais sucinto quando comparado ao modelo Begin/End que fazemos atualmente.

private async static void InvocarServico()
{
    var temp = await proxy.PingAsync(“Algum Valor”);
    Console.WriteLine(“O resultado foi: {0}”, temp);
}

WCF – Comunicação P2P

Quando ouvimos dizer sobre programas P2P, sempre nos vem a mente aqueles softwares que compartilham diversos conteúdos (ilegal ou não, não é o caso a ser discutido aqui), e que geralmente estão em formato de músicas, vídeos, livros, etc. A ideia é cada ponto publicar o que deseja disponibilizar, para que outros pontos que estão dentro da mesma rede, possam baixar o que lhes interessam.

Mas publicar conteúdo não é a única finalidade deste tipo de comunicação. Podemos usufruir deste tipo de rede para colaboração, que é algo cada vez mais frequente nas empresas, e além disso, a possibilidade de distribuir processamento entre os vários pontos, para que todos consigam cooperar na execução de uma tarefa maior.

Cada um dos pontos que fazem parte de uma rede P2P, são conhecidos como nó (node) ou peer, e a rede como mesh (mesh network). Mas uma rede P2P pode ser definida de duas formas, onde a primeira é considerada pura, ou seja, não existirá um servidor – centralizador – envolvido, e cada ponto atuará como cliente e servidor. Já a outra forma, conhecida como híbrida, teremos um servidor fazendo parte da rede, mas a finalidade dele é apenas de rastrear ou de gerenciar a comunicação entre os pontos, mas não é responsável por armazenar qualquer tipo informação.

Para implementarmos uma rede P2P, a Microsoft disponibilizou dentro do .NET Framework um namespace chamado System.Net.PeerToPeer, que contém todos os tipos necessários para a construção de uma rede deste tipo. Mas como o WCF é o pilar de comunicação dentro do .NET Framework, a Microsoft resolveu permitir a exposição de serviços através do modelo P2P, podendo reutilizar o conhecimento que temos em serviços tradicionais para expor tarefas/dados através deste modelo. Isso evitará conhecermos detalhes de mais baixo nível, que é algo que teríamos que fazer se fossemos utilizar as classes contidas no namespace System.Net.PeerToPeer.

Apesar de utilizar o WCF como meio de criação de aplicações que estarão conectadas via P2P, temos algumas mudanças consideráveis em relação ao contrato e ao consumo destes serviços, justamente pela natureza dele. Para explicar melhor, vamos explorar o contrato, que é o primeiro passo a ser realizado para a construção do serviço. Abaixo temos a interface que possui um método chamado EnviarMensagem que possui duas strings como parâmetro, que representam o remetente e a mensagem, respectivamente. O principal detalhe a ser notar aqui, é a propriedade CallbackContract do atributo ServiceContractAttribute. Geralmente utilizamos uma interface de callback separada, mas como as mensagens enviadas e recebidas terão o mesmo formato, o contrato de callback deverá ser o mesmo que o contrato que descreve o serviço. Isso é referido como contrato simétrico.

[ServiceContract(CallbackContract = typeof(IContrato))]
public interface IContrato
{
    [OperationContract(IsOneWay = true)]
    void EnviarMensagem(string de, string mensagem);
}

Quando desenvolvemos um serviço WCF “tradicional”, o próximo passo é a criação da aplicação que servirá como hospedeira da classe que representará o serviço, e que deverá utilizar a classe ServiceHost (ou uma de suas derivadas) para gerenciar a execução do serviço. Mas como falamos acima, uma das alternativas de redes P2P é não possuir um servidor, ou seja, a criação de uma aplicação para hospedar o serviço, neste caso, não existirá.

Com essa grande diferença, tudo o que precisaremos fazer é a configuração do lado do cliente. E como sabemos, todo serviço WCF deve possuir três características essenciais, conhecidas como Address, Binding e Contract. Aqui o binding será o NetPeerTcpBinding, que foi criado justamente para abstrair toda a comunicação feita sobre o protocolo P2P, e que internamente, recorrerá aos tipos fornecidos pelo namespace System.Net.PeerToPeer, conforme falamos acima.

Seguindo em frente, devemos definir o endereço do serviço, que na verdade será o nome da rede (mesh) que todos os clientes farão parte. Mantendo a mesma padronização imposta pelo WCF, aqui o endereço deverá ser prefixado com net.p2p, a na sequência o nome do serviço. Um pouco mais abaixo, temos a configuração do binding, definindo a porta em que as mensagens serão processadas. Definindo a porta 0 (zero), fará com que o WCF escolha uma porta que não esteja sendo utilizada no momento.

Por fim, e não menos importante, temos o resolver. Como o próprio nome diz, o resolver é o responsável por manter e resolver os IDs daqueles que estão conectados na rede. Quando o canal é aberto, o WCF utiliza o resolver para resolver o ID daqueles que estão conectados à rede, e assim criar uma rede com pontos interconectados. O atributo mode define qual modelo de resolver será utilizado, e você pode escolher uma em três opções, que estão definidas através do enumerador PeerResolverMode, que estão listados abaixo:

  • Pnrp: Utiliza o padrão PNRP (Peer Name Resolution Protocol), que é um protocolo desenvolvido pela Microsoft que habilita a publicação e resolução dinâmica de nomes.
  • Custom: Com esta opção, você pode criar um serviço para a resolução de nomes e IDs daqueles que estão envolvidos na rede. Um exemplo mais detalhado será mostrado abaixo.
  • Auto: Quando configurado com a opção Auto e houver um resolver customizado definido, ele será utilizado; caso contrário, tentará fazer uso do protocolo Pnrp.

Abaixo temos toda a configuração do arquivo App.config da aplicação. Note que neste primeiro momento, estamos utilizando o resolver definido como Pnrp:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address=”net.p2p://servicos/chat”
                binding=”netPeerTcpBinding”
                bindingConfiguration=”bindingConfig”
                contract=”AplicacaoDeChat.IContrato”
                name=”ChatEndpoint”/>
    </client>
    <bindings>
      <netPeerTcpBinding>
        <binding name=”bindingConfig”
                 port=”0″>
          <security mode=”None” />
          <resolver mode=”Pnrp” />
        </binding>
      </netPeerTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Utilizar o PNRP parece ser a melhor alternativa, mas as vezes você está em um ambiente em que ele não é suportado, ou por algum motivo, você quer controlar como esse procedimento é realizado. Para isso que a opção Custom foi criada. Isso nos permitirá construir um serviço WCF, e lá customizar toda a regra de resolução de nomes/IDs.

Para essa customização ser feita, o primeiro passo é criar um serviço que fará essa tarefa. Felizmente o WCF já traz uma classe pronta, que corresponde ao serviço de resolução. Essa classe é chamada de CustomPeerResolverService, e implementa uma interface que define o seu contrato: IPeerResolverContract. Apesar de haver vários membros, essa interface fornece alguns principais métodos, que são autoexplicativos: Register, Resolve e Unregister. Caso essa implementação não te atenda, tudo o que você precisa fazer é criar a sua própria implementação, utilizando esta mesma interface.

Como ele será um serviço WCF puro, precisamos criar uma aplicação que sirva como hospedeira para ele, recorrendo ao ServiceHost. Note que logo abaixo temos o arquivo de configuração correspondente, que está expondo o serviço através do protocolo TCP:

CustomPeerResolverService cprs = new CustomPeerResolverService();

using (ServiceHost host = new ServiceHost(cprs, new Uri[] { }))
{
    cprs.Open();
    host.Open();

    Console.WriteLine(“[ Serviço de Resolução Ativo ]”);
    Console.ReadLine();
}

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name=”System.ServiceModel.PeerResolvers.CustomPeerResolverService”>
        <endpoint address=”net.tcp://localhost:8282/prs”
                  binding=”netTcpBinding”
                  bindingConfiguration=”bindingConfig”
                  contract=”System.ServiceModel.PeerResolvers.IPeerResolverContract” />
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name=”bindingConfig”>
          <security mode=”None”/>
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Quando este serviço estiver ativo, podemos definir o resolver customizado nas aplicações, e como já sabemos, devemos definir o atributo mode como Custom, e logo no interior do elemento resolver, especificarmos as configurações de acesso ao serviço de resolução, tais como o endereço, o binding e sua respectiva configuração. O código abaixo ilustra a mesma configuração que vimos acima, mas agora utilizando um resolver customizado ao invés do PNRP:

<bindings>
  <netPeerTcpBinding>
    <binding name=”bindingConfig”
             port=”0″>
      <security mode=”None” />
      <resolver mode=”Custom”>
        <custom address=”net.tcp://localhost:8282/prs”
                binding=”netTcpBinding”
                bindingConfiguration=”peerBindingConfig” />
      </resolver>
    </binding>
  </netPeerTcpBinding>
  <netTcpBinding>
    <binding name=”peerBindingConfig”>
      <security mode=”None” />
    </binding>
  </netTcpBinding>
</bindings>

Depois do resolver definindo (não importa qual modelo seja), chega o momento que criarmos a aplicação que fará uso deste tipo diferenciado de serviço. Como sabemos, não há um serviço rodando que podemos efetuar a referência. Tudo o que precisaremos fazer aqui é o “consumo direto”, criando manualmente as classes para efetuar essa comunicação.

Para o consumo, é necessário a criação de um canal de comunicação, e o responsável por isso é a classe ChannelFactory<TChannel>. Mas neste caso, o canal deverá ser duplex (bidirecional), o que nos obriga a utilizar uma factory especializada para esta situação, que é a classe DuplexChannelFactory<TChannel>. O construtor desta classe deve receber a instância da classe InstanceContext, que possui acesso à classe do lado do cliente que implementa a interface (contrato) de callback. Além dela, ainda é necessário apontar o nome do endpoint que está no arquivo de configuração, para que a factory possa criar os canais utilizando todas as configurações previamente estabelecidas.

Como estamos construindo um chat, estamos implementando o contrato de callback no próprio formulário. O método EnviarMensagem é o callback, que será disparado quando uma mensagem chegar, enquanto o método Enviar_Click é o tratador do evento Click do botão, que será disparado quando a aplicação desejar enviar uma mensagem. Abaixo temos o formulário já implementado, com algumas linhas de código omitidas por questões de espaço:

public partial class Conversacao : Form, IContrato
{
    private IContratoChannel channel;
    private string nomeDoUsuario;

    public Conversacao(string nomeDoUsuario)
    {
        InitializeComponent();

        this.nomeDoUsuario = nomeDoUsuario;

        this.channel = new DuplexChannelFactory<IContratoChannel>(
            new InstanceContext(this), “ChatEndpoint”).CreateChannel();
        this.channel.Open();
    }

    public void EnviarMensagem(string de, string mensagem)
    {
        this.Mensagens.Text += 
            string.Format(“{0}:{2}{1}{2}{2}”, de, mensagem, Environment.NewLine);
    }

    private void Enviar_Click(object sender, EventArgs e)
    {
        this.channel.EnviarMensagem(this.nomeDoUsuario, this.Mensagem.Text);
    }
}

Distribuindo as Mensagens

Aparentemente tudo funciona da forma correta, mas é importante ter cuidado com o modo em que as mensagens são enviadas para a rede. O P2P utiliza broadcast, ou seja, se uma aplicação mandar uma mensagem para a rede, todos os pontos que estiverem conectados, receberão a mesma. Na imagem acima temos a sensação de que somente as duas pessoas estão conversando, mas se uma outra instância desta mesma aplicação entrar no ar, ela também começará a receber as mensagens.

O WCF possui algumas formas para controlar a distribuição das mensagens entre os pontos que compõem a rede, onde a primeira é através de uma contagem e a segunda utilizando um filtro. No primeiro caso, podemos modificar o nosso contrato, criando ao invés de strings que representam as informações do mensagem, uma classe para incorporar todos os parâmetros necessários, e além disso, devemos incluir um novo campo para contabilizar o número de encaminhamentos que a mensagem deverá fazer.

Como a API que estamos utilizando abstrai grande parte da comunicação entre os pontos, podemos apenas incluir no contrato da mensagem um campo decorado com o atributo PeerHopCountAttribute, que será decrementado a cada ponto que ela for entregue, e quando atingir o número 0, por mais que existam ainda pontos, eles não receberão a respectiva mensagem. Abaixo temos a classe Mensagem com os campos que compõem a estrutura do negócio e um campo chamado Hops, que será utilizando apenas quando estiver sendo exposto através do binding NetPeerTcpBinding.

[MessageContract]
public class Mensagem
{
    [PeerHopCount]
    public int Hops;

    [MessageBodyMember]
    public string De;

    [MessageBodyMember]
    public string Para;

    [MessageBodyMember]
    public string Texto;
}

É importante dizer que o contrato que criamos acima (IContrato), também foi mudado para receber esta nova classe ao invés do parâmetros separados.

Para finalizar, podemos também fazer o uso de filtros, que permite criar alguma espécie de regra para entregar ou não a mensagem. Note que coloquei na classe Mensagem um campo chamado Para que não havia na implementação inicial. A finalidade deste campo é analisar se o ponto que está recebendo a mensagem é mesmo o usuário à qual se destina. Caso seja, entregaremos a mensagem e iremos parar a propagação para os pontos seguintes; caso contrário, não entregaremos e iremos optar por propagar a mensagem adiante.

A criação de um filtro consiste em implementar a classe abstrata PeerMessagePropagationFilter, que nos obriga a sobrescrever o método ShouldMessagePropagate. Esse método possui um parâmetro que corresponde a mensagem que está chegando e um segundo parâmetro do tipo PeerMessageOrigination, que determina se a origem dela é local ou remota. Esse parâmetro é útil para determinar se a mensagem partiu da própria aplicação, e que no nosso caso, deveremos entregar para ela própria para conseguir exibir o conteúdo na tela.

Esse método deve retornar uma das opções definidas no enumerador PeerMessagePropagation, que determinará se a mensagem deverá ser entregue apenas localmente, remotamente, ambos ou não deve fazer nada. Para exemplificar, abaixo temos a classe UserNameChatFilter, que analisará o conteúdo da classe Message que vem como parâmetro e determinará se ele deve ou não ser entregue para a aplicação local, olhando para a origem da mensagem e para o atributo Para do corpo da mensagem.

internal class UserNameChatFilter : PeerMessagePropagationFilter
{
    private string username;

    public UserNameChatFilter(string username)
    {
        this.username = username;
    }

    public override PeerMessagePropagation ShouldMessagePropagate(
        Message message, PeerMessageOrigination origination)
    {
        if (origination == PeerMessageOrigination.Local)
        {
            return PeerMessagePropagation.LocalAndRemote;
        }
        else
        {
            Mensagem temp =
                (Mensagem)TypedMessageConverter.Create(typeof(Mensagem), null).FromMessage(message);

            string to = temp.Para;
            return to == username ? PeerMessagePropagation.Local : PeerMessagePropagation.Remote;
        }
    }
}

Depois da classe criada, temos que adicioná-la à execução, e para isso vamos recorrer ao método GetProperty<T> da classe genérica DuplexChannelFactory<TChannel>. Esse método genérico é utilizado para extrair da channel stack, objetos que são específicos ao modelo de comunicação. Neste caso, vamos tentar recuperar o objeto PeerNode, que corresponde ao ponto atual. Essa classe possui uma propriedade chamada MessagePropagationFilter, que pode receber uma instância de alguma classe que implemente um filtro customizado (PeerMessagePropagationFilter), assim como fizemos acima. O código abaixo ilustra como acoplar o filtro à execução:

this.channel.GetProperty<PeerNode>().MessagePropagationFilter = 
    new UserNameChatFilter(nomeDoUsuario);

Com isso, ao enviar uma requisição para um usuário específico, o filtro entrará em ação, avaliando se deve receber a mensagem ou propagar para o próximo ponto existente na rede. Apesar de conseguirmos atingir o objetivo, talvez esse modelo não seja a melhor saída em algumas situações, inclusive aqui. Como na maioria das vezes queremos conversar com alguém específico, o melhor seria manter um contato direto, ao invés de inundar a rede em busca desta pessoa.

Conclusão: Apesar de mudar um pouco a forma de como se cria serviços WCF, vimos neste artigo que temos mais uma possibilidade de criar aplicações conectadas, que agora também nos permite usufruir de uma rede P2P utilizando um modelo se comunicação ligeiramente diferente do qual estamos habituados a trabalhar.

P2PComWCF.zip (24.53 kb)

Curso de WCF 4.0 em Campinas

wcf 4.0Dia após dia as aplicações estão cada vez mais conectadas. Essa interligação viabiliza novas oportunidades de negócios, agilidade e eficiência no processo de uma empresa, beneficiando assim todos aqueles que estão envolvidos.

Dentro da plataforma .NET da Microsoft, o WCF (Windows Communication Foundation) é o pilar de comunicação, que fornece uma infinidade de funcionalidades para expor, consumir e gerenciar serviços, sejam eles para serem consumidos em uma rede local ou através da internet.

Como essa necessidade está cada vez mais presente no dia a dia de cada desenvolvedor, o WCF possui cada vez mais espaço, já que ele substitui todas as tecnologias de aplicações distribuídas criadas pela Microsoft, incorporando em uma plataforma estensível e consistente, os principais modelos de comunicação que existem atualmente.

Tendo em vista todo esse cenário, eu vou ministrar um curso oficial de WCF 4.0, abordando desde os primeiros passos até cenários mais complexos, como roteamento, estensibilidade, segurança (incluindo claims), performance, etc. Abaixo estão listados os tópicos que serão abordados:

  • Módulo 1: Arquitetura Orientada à Serviços
  • Módulo 2: Introdução ao WCF
  • Módulo 3: Hospedagem de Serviços
  • Módulo 4: Contratos
  • Módulo 5: Endpoints e Behaviors
  • Módulo 6: Teste e Debug
  • Módulo 7: Segurança
  • Módulo 8: Tópicos Avançados (Estensibilidade, Processamento Assíncrono e Roteamento)

Como pré-requisitos para este curso, é necessário possuir familiaridade com a plataforma .NET, com o Visual Studio 2008/2010 e experiência em alguma linguagem .NET (C# ou VB.NET), incluindo conceitos relacionados a orientação à objetos.

O curso será ministrado na People Computação, na cidade de Campinas, em São Paulo, com toda infraestrutura fornecida por eles. A People é um centro oficial autorizado Microsoft, que ministra e aplica cursos e exames oficiais, e é onde eu ministro treinamentos desde meados de 2005.

Com 24 horas de duração, o treinamento será realizado toda terça-feira e quinta-feira, das 18:30 às 22:30 (sendo 4 horas por dia), iniciando em 23 de novembro até 09 de dezembro de 2010. A sala terá capacidade para apenas 12 alunos. Para maiores informações, valores e como se inscrever, entre em contato através do telefone (19) 3739-6400.

Autorização com WIF

Dentro do desenvolvimento de software, o termo autorização descreve o processo de verificar se um determinado usuário possui ou não o acesso à um determinado recurso. Mas é importante dizer que a autorização acontece depois da autenticação, pois primeiramente é necessário saber quem o usuário é, para depois saber que direitos ele tem no sistema.

Recentemente eu comentei em alguns artigos sobre a utilização de um sistema de identidade para autenticar os usuários. Uma das principais finalidades é a terceirização do processo de autenticação, delegando isso à uma outra aplicação/serviço, e assim, todas as aplicações que confiarem naquele autenticador (STS), poderão fazer uso do mesmo para autenticar seus respectivos usuários, ficando essas aplicações totalmente isentas em como isso deve acontecer.

Como comentando nos artigos acima, o WIF (Windows Identity Foundation) abstrai grande parte da complexidade para fazer isso tudo funcionar, mas a autorização ainda continua sendo uma responsabilidade da própria aplicação. A finalidade deste artigo é apresentar algumas alternativas para efetuar a autorização nas aplicações, utilizando as claims emitidas por um determinado autenticador para refinar o acesso.

Grande parte das aplicações fazem uso do modelo baseado em papéis (roles), onde concedem ou negam acesso à algum recurso da aplicação se o usuário está ou não contido em um grupo específico. Tecnicamente falando, sempre recorremos a utilização de alguma implementação da interface IPrincipal, que fornece um método chamado IsInRole, que dado o papel retorna um valor boleano indicando se o usuário corrente está ou não contido nele.

Como já comentei anteriormente, a autorização baseada em claims é muito mais flexível do que isso, ou seja, não estamos condicionados a somente trabalhar com papéis (simples strings), mas também avaliar se o usuário poderá ou não acessar um determinado recurso a partir da sua idade, ou somente se o seu cartão de crédito estiver válido, e assim por diante.

Uma das grandes preocupações que a Microsoft teve, foi em manter compatibilidade com o modelo de objetos que utilizamos desde a primeira versão do .NET Framework, mas conseguindo já tirar proveito deste novo modelo. E foi justamente pensando nisso, que ela criou as interfaces IClaimsIdentity e IClaimsPrincipal. Elas já foram comentadas com mais detalhes neste artigo, e somente para recapitular, uma das principais propriedades é a Claims, exposta pela interface IClaimsIdentity, que fornece o conjunto de claims que foram emitidas pelo autenticador para o usuário atual.

A propriedade RoleClaimType também foi criada com propósito de compatibilidade. Como todas as informações emitidas por um STS chegam para uma relying party através de claims, é necessário saber qual ou quais as claims representam os papéis (IPrincipal.IsInRole). Com isso, podemos continuar utilizando a autorização baseada em papéis, mas nos bastidores, são claims que estão sendo utilizadas. Com isso, por mais que começamos a utilizar as técnicas de autenticação através do WIF, a autorização pode continuar da forma que está, sem a necessidade de sofrer qualquer alteração.

Como sabemos, entre as claims que existem predefinidas nas especificações, ainda podemos criar claims customizadas. Isso quer dizer que um STS pode emitir uma claim sem vincular ela ao tipo role, que por padrão, a aplicação utiliza quando chamamos o método IsInRole. Mas felizmente isso pode ser configurado de forma declarativa, onde cada aplicação determina qual a claim que ela vai quer utilizar/avaliar quando o método IsInRole for chamado. Para efetuar essa customização, podemos recorrer ao sub-elemento roleClaimType do elemento samlSecurityTokenRequirement, assim como é mostrado abaixo:

<samlSecurityTokenRequirement>
  <roleClaimType value= “http://MinhaAplicacao/departamento&#8221; />
</samlSecurityTokenRequirement>

Como comentado acima, podemos continuar trabalhando com a segurança baseada em papéis. E para isso, utilizamos o método IsInRole exposto pela interface IPrincipal ou através da classe PrincipalPermission, ou ainda, do atributo PrincipalPermissionAttribute, que nos permite trabalhar de forma imperativa ou declarativa, respectivamente. Todas deverão recorrer as claims do tipo role (http://schemas.microsoft.com/ws/2008/06/identity/claims/role) ou alguma outra, conforme configuramos acima. Mas o WIF também fornece um novo par de classes para trabalhar explicitamente com claims, a saber: ClaimsPrincipalPermission e ClaimsPrincipalPermissionAttribute. Ambas também servem para utilizar de forma imperativa ou declarativa, respectivamente, mas como já podemos perceber, vai além de somente utilizar claims como roles. Podemos aqui utilizar outras claims que foram emitidas pelo STS, e tornar o processo de autorização muito mais inteligente.

Como comentado, o uso pode ser de forma imperativa ou declarativa. Utilizar um modo ou outro tem suas vantagens e desvantagens. De qualquer forma, a utilização é extremamente fácil, pois é semelhante ao modelo que já estamos acostumados a trabalhar dentro da plataforma .NET. Para exemplificar, o exemplo abaixo mostra a utilização de ambas as formas:

[ Modelo Imperativo ]

public string AlgumaOperacao()
{
    new ClaimsPrincipalPermission(“AlgumaOperacao”, “Executar”).Demand();

    //Processamento do Método
}

[ Modelo Declarativo ]

[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = “AlgumaOperacao”, Action = “Executar”)]
public string AlgumaOperacao()
{
    //Processamento do Método
}

Apesar de aparentemente funcionar como o modelo baseado em papéis, o WIF nos induz a não fixar o valor das claims pelo nosso código, que é algo que mais cedo ou mais tarde sempre acaba mudando, e justamente por isso que temos as propriedades Resource e Action, mas não devemos nos preocupar com o que significado delas agora, pois o seu uso está condicionado ao ambiente em que são utilizadas, e que serão detalhados mais abaixo. Um detalhe importante também é que as duas classes que representam a permissão não são responsáveis por efetivamente validar se o usuário poderá ou não acessar aquele recurso; isso é delegado à uma outra classe, chamada ClaimsAuthorizationManager, qual veremos a seguir.

O processo de autorização é altamente customizável, o que nos permite interceptar o momento em que o runtime avalia se o usuário possui ou não permissão, e com isso alterar o comportamento padrão, nos baseando em outras claims que foram emitidas para tomar a decisão se o usuário deverá ou não acessar o recurso.

Independentemente de qual ambiente estamos falando (ativo ou passivo), as classes que lidam com o processo de autorização são as mesmas. A diferença consiste nas informações que são colocadas dentro dessas classes, pois elas são contextualizadas, condicionados ao ambiente. A principal classe que irá gerenciar isso é a ClaimsAuthorizationManager, que fornece um local central para analisarmos a requisição e determinar se o usuário poderá ou não acessar o recurso. Essa classe possui um único método chamado CheckAccess, que é disparado antes de acessar o recurso em si, e dado o contexto da requisição, retorna um valor boleano indicando se o usuário terá acesso ao mesmo, e que por padrão sempre permite o acesso.

Como dito acima, o método CheckAccess recebe o contexto da requisição como parâmetro, fornecendo todas as informações necessárias para a tomada de decisão. Esse parâmetro é do tipo AuthorizationContext, e possui três principais propriedades: Principal, Resource e Action. A primeira propriedade retorna uma instância da classe ClaimsPrincipal, que representa o usuário atual. Já a propriedade Resource determina o recurso que você está tentando acessar, e isso varia de acordo com o ambiente. Finalmente, a propriedade Action determina a operação que está sendo aplicada ao recurso, e assim como a propriedade Resource, também varia de acordo com o ambiente.

Quando implementamos a classe ClaimsAuthorizationManager para o ambiente passivo, a propriedade Resource trará o caminho e nome da página ASPX que estamos tentando acessar e a Action deve retornar se está sendo acessada via GET ou via POST. Já no ambiente ativo, a propriedade Resource deverá representar o endereço do serviço que está sendo acessado, enquanto a propriedade Action deverá refletir a SOAPAction da operação. A propriedade Principal tem o mesmo significado para ambos ambientes, ou seja, representar o usuário que está tentando acessar o recurso. Como há algumas outras diferenças entre os dois ambientes, vamos analisar a sua implementação individualmente a partir de agora.

Autorização no Ambiente Passivo – ASP.NET

A implementação da classe ClaimsAuthorizationManager para o ambiente passivo, consistirá em avaliar se a página (ASPX) que o usuário está acessado poderá ou não ser acessado, de acordo com uma regra determinada. Cada página que for acessada por aquele usuário, o runtime do WIF em conjunto com o ASP.NET invocará o método CheckAccess, que é onde terá toda a lógica de autorização.

Como exemplo, vamos somente permitir o acesso à uma determinada página se o usuário for maior que 21 anos de idade. Como o STS emite uma claim chamada dataDeNascimento, podemos efetuar o cálculo em cima dela, para avaliar se o usuário poderá ou não acessar a página. Abaixo temos a implementação:

public class AutorizadorDePaginas : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        if (context.Resource.First().Value == “http://localhost:2721/PaginaRestrita.aspx&#8221;)
        {
            var claimDeDataDeNascimento =
                (
                    from in context.Principal.Identities[0].Claims
                    where c.ClaimType == “http://MinhaAplicacao/dataDeNascimento&#8221;
                    select c
                ).FirstOrDefault();

            if (claimDeDataDeNascimento != null)
            {
                DateTime dataDeNascimento = Convert.ToDateTime(claimDeDataDeNascimento.Value);
                return (DateTime.Now.Subtract(dataDeNascimento).Days / 365) >= 21;
            }

            return false;
        }

        return true;
    }
}

Em primeiro lugar verificamos se a página que está sendo acessada deve ser verificada quanto a permissão. Como sabemos que devemos filtrar o acesso a partir da idade, então vamos em busca de uma claim que represente a data de nascimento do usuário, e se encontrada, verificamos se ele já possui mais que 21 anos. Se ele tiver, então ele pode acessar, do contrário, terá o acessado negado.

Mas essa implementação por si só não funciona. Temos que saber como acoplá-la ao pipeline de processamento do ASP.NET. Na verdade, o responsável por invocar o método CheckAccess será o módulo ClaimsAuthorizationModule, que por padrão, não está configurado. Esse módulo se vincula ao evento AuthorizeRequest do objeto HttpApplication, e quando é disparado, irá extrair o autorizador configurado na seção do WIF e, consequentemente, irá passar a instância da classe AuthorizationContext, onde o Resource representará o caminho até a página ASPX e a Action deve representar o verbo HTTP (GET ou POST). Abaixo temos a configuração no arquivo Web.config que correspondem ao módulo e ao autorizador:

<system.web>
  <!– Outras Configurações –>
  <httpModules>
    <!– Outros Módulos –>
    <add name=”ClaimsAuthorizationModule”
         type=”Microsoft.IdentityModel.Web.ClaimsAuthorizationModule, Microsoft.IdentityModel, …”/>
  </httpModules>
</system.web>
<microsoft.identityModel>
  <service>
    <!– Outras Configurações –>
    <claimsAuthorizationManager
        type=”Servico.AutorizadorDePaginas, Servico, Version=1.0.0.0, …”/>
  </service>
</microsoft.identityModel>

Autorização no Ambiente Ativo – WCF

Já no WCF vamos trabalhar de forma parecida ao que vimos acima, ao que diz respeito a forma de implementar a classe responsável pela autorização do usuário. As ligeiras diferenças estão nas informações que são colocadas na classe AuthorizationContext e como ela é acoplada à execução.

Neste ambiente, a propriedade Resource representará o serviço que está sendo acessado, mais precisamente, o endereço do mesmo. Já a propriedade Action determinará a SOAPAction que o usuário quer invocar. A SOAPAction é um header que é incluído na requisição HTTP, e determina intenção da requisição SOAP. Esse header é representado por uma URI que não aponta necessariamente para um local válido na internet, e que indicará qual o método a ser invocado.

A ideia aqui é justamente analisar a SOAPAction, verificando se trata-se de uma operação que exige a validação da permissão do usuário. Caso seja, então vamos verificar se ele possui a idade suficiente para acessar a operação em questão. Abaixo temos a implementação, bem próxima a qual fizemos no ambiente passivo:

public class AutorizadorDeOperacoes : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        if (context.Action.First().Value == “http://tempuri.org/IConsultasFinanceiras/RecuperarLimiteDeCredito&#8221;)
        {
            var claimDeDataDeNascimento =
                (
                    from c in context.Principal.Identities[0].Claims
                    where c.ClaimType == “http://MinhaAplicacao/dataDeNascimento&#8221;
                    select c
                ).FirstOrDefault();

            if (claimDeDataDeNascimento != null)
            {
                DateTime dataDeNascimento = Convert.ToDateTime(claimDeDataDeNascimento.Value);
                return (DateTime.Now.Subtract(dataDeNascimento).Days / 365) >= 21;
            }

            return false;
        }

        return true;
    }
}

A principal diferença aqui é como o WIF acopla isso à execução no WCF, que deve executá-la sempre quando uma nova requisição chegar ao serviço. Para que o serviço faça uso do WIF, é necessário submetermos a instância da classe ServiceHost para o método estático ConfigureServiceHost da classe FederatedServiceCredentials, assim como já foi mostrado neste artigo. Assim como no ASP.NET, o que o WIF faz aqui é utilizar um ponto de estensibilidade do WCF para adicionar a implementação do ClaimsAuthorizationManager, e para isso, o WIF já possui implementado uma classe que deriva de ServiceAuthorizationManager, que é a IdentityModelServiceAuthorizationManager, e em seu método CheckAccessCore captura o autorizador implementado/configurado pelo WIF e invoca o seu método CheckAccess, abastecendo a instância da classe AuthorizationContext com informações que estão dentro do contexto da operação (OperationContext).

Sendo assim, tudo o que precisamos aqui é configurar o autorizador customizado, para que o runtime do WIF possa capturá-lo e, consequentemente, entregar ao WCF para efetuar a validação. O código abaixo ilustra essa configuração, que é realizada da mesma forma que no ambiente passivo:

<microsoft.identityModel>
  <service>
    <!– Outras Configurações –>
    <claimsAuthorizationManager
        type=”Servico.AutorizadorDeOperacoes, Servico, Version=1.0.0.0, …”/>
  </service>
</microsoft.identityModel>

Claims Explícitas

Para ratificar o que disse acima, em ambos cenários não temos as claims espalhadas pela aplicação. Como isso pode mudar, o ideal é manter centralizado, para facilitar a alteração caso ela seja necessária.

O mesmo vale para as classes que falamos acima: ClaimsPrincipalPermission e ClaimsPrincipalPermissionAttribute. Quando as utilizamos, nós vimos que tudo o que precisamos colocar ali é o Resource e Action, que determinam informações sobre o acesso e não sobre a claim que deve ser utilizada/avaliada.

Essas classes não são responsáveis por fazerem a verificação. Intermamente elas recorrem a implementação da classe ClaimsAuthorizationManager, que deve estar configurada da forma que vimos acima. Caso não tenhamos um autorizador explicitamente definido, o WIF utilizará uma implementação padrão, que sempre concederá o acesso. O mais interessante aqui é que as informações que colocamos nestas classes também são enviadas para a classe que herda de ClaimsAuthorizationManager, pois elas invocam o método CheckAccess, abstecendo o AuthorizationContext com as respectivos informações.

Nativamente o WIF não traz suporte para vinculação de claims à um determinado recurso, mas podemos criar algo customizado para isso. A ideia é ter algum repositório para catalogar as políticas de acesso à determinados recursos, por exemplo, para acessar uma determinada página ASPX ou uma operação de um serviço, precisamos verificar se a quantidade de anos da claim dataDeNascimento é maior que 21. Esse repositório pode ser um arquivo Xml, uma base de dados ou até mesmo o próprio arquivo de configuração (*.config). Criando algo customizado, nos permite tornar a implementação do ClaimsAuthorizationManager mais simples, dinâmica e ainda mantendo a centralização.

Conclusão: Vimos neste artigo o processo de autorização de uma aplicação que opta por fazer o uso do WIF para terceirizar a autenticação do usuário. Apesar de autorização ainda ser responsabilidade da relying party, o WIF se preocupou em fornecer classes para tornar esse processo mais suave, e não menos importante, mantendo compatibilidade com o legado, que são aplicações que se baseiam em papéis para refinar o acesso aos seus respectivos recursos.