Comunicação Local no Silverlight

Como sabemos, podemos consumir serviços WCF em aplicações Silverlight. Isso nos permite colocar alguma tarefa em um serviço na aplicação que faz a hospedagem do serviço ou até mesmo em outros locais, fora do domínio qual ela se encontra. A ideia aqui é permitir a comunicação entre a aplicação Silverlight (que roda dentro do navegador) e serviços que estão hospedados além dessa máquina.

Mas e se precisarmos de algo mais simples, como por exemplo, a comunicação entre duas aplicações Silverlight que rodam dentro do navegador em uma mesma máquina? Utilizar o WCF aqui pode ser extremamente complicado, pois exigirá desenvolvermos uma série de funcionalidades para permitir esse interação, e ainda podemos ter um grande overhead, já que a comunicação não será local e dependerá de que a conexão esteja disponível com o servidor/serviço com qual queremos dialogar.

Para esse tipo de cenário o Silverlight fornece algo bem mais simples de se trabalhar, que é uma funcionalidade conhecida como “comunicação local”. A ideia aqui é criar aplicações que geram informações, e aquelas que estiverem interessadas, podem capturá-las e tratar da forma que achar mais conveniente. Esse tipo de comunicação pode ser utilizada por plugins que rodam dentro da mesma página, em páginas diferentes, estando ou não dentro do mesmo domínio.

Através das imagens abaixo podemos analisar os cenários mais comuns. Na primeira imagem (1) temos uma única página ASPX hospedando duas aplicações Silverlight, e se precisarmos efetuar a comunicação entre elas, podemos utilizar o recurso que veremos aqui. Já na segunda imagem (2), temos duas página distintas dentro de um mesmo domínio, mas cada página hospeda uma aplicação Silverlight diferente, e da mesma forma, a comunicação também será possível. Na terceira (3) e última imagem, temos duas aplicações, mas cada uma delas está hospedada em um domínio diferente, mas isso também pode ser permitido, mas falaremos mais disso abaixo.

A implementação é relativamente simples. Para utilizarmos a comunicação local entre aplicações Silverlight, vamos recorrer aos tipos que estão disponível a partir do namespace System.Windows.Messaging. As principais classes que teremos aqui são: LocalMessageSender e LocalMenssageReceiver. Como podemos perceber, a primeira classe é responsável por enviar mensagens, enquanto a segunda se encarrega de receber tais mensagens.

Aqui é importante comentarmos sobre algumas “limitações”: tudo o que podemos mandar de um lado à outro deve ser, obrigatoriamente, uma string. Qualquer coisa diferente disso, exigirá um trabalho extra, como serializar o objeto em algum formato Xml ou Json. Dependendo da quantidade de informação que você levar de um lado para outro, considere o uso do Json ao invés do Xml, que é bem menos verboso, e isso pode evitar você atingir o limite máximo do tamanho da string, que deve ser menor ou igual a 40KB. Uma outra alternativa aqui é serializar as informações em formato binário e utilizar Base64 para codificá-la.

Se vamos gerar uma aplicação que gera informações, então temos que recorrer ao uso da classe LocalMessageSender. Em seu construtor ela recebe uma string que corresponde ao nome do destinatário das mensagens geradas por ela. Já o segundo parâmetro do construtor desta classe, recebe uma string contendo o domínio que receberá as mensagens. Você pode utilizar o membro estático chamado Global desta mesma classe, que corresponde à um “*”, e instrui o Silverlight que qualquer domínio poderá receber as mensagens. Finalmente, essa mesma classe fornece o método chamado SendAsync, que permite o envio de uma string para aqueles que desejarem capturá-la.

Chega o momento de efetuar a configuração do destinatário. Agora entra em cena a classe LocalMessageReceiver, que recebe em seu construtor três parâmetros: uma string com o nome do destinatário, uma das opções do enumerador ReceiverNameScope e, finalmente, uma coleção de strings, que representa os domínios de quais a aplicação poderá receber mensagens. Essa classe fornece um evento chamado MessageReceived, que é disparado quando uma nova mensagem chega para a aplicação, e para capturá-la, tudo o que precisamos fazer é nos vincularmos a este evento e acessar a mensagem através da propriedade Message do argumento MessageReceivedEventArgs. Para finalizar, o método Listen é responsável por começar a monitorar as mensagens que chegam e entregá-las para o evento acima.

Depois de conhecer essas classes, podemos já fazer o uso delas nas nossas aplicações. Para exemplificar, teremos duas aplicações Silverlight, hospedadas em locais diferentes. A ideia será permitir a comunicação entre elas, e para manter a simplicidade do exemplo, tudo o que a aplicação fará ao receber a mensagem, será colocá-la em um TextBox. Abaixo temos a configuração de uma das aplicações, utilizando tudo o que vimos acima:

public partial class MainPage : UserControl
{
    private LocalMessageSender sender;
    private LocalMessageReceiver receiver;

    public MainPage(string aplicacaoLocal, string aplicacaoRemota)
    {
        InitializeComponent();

        this.sender = new LocalMessageSender(aplicacaoRemota, LocalMessageSender.Global);
        this.receiver = new LocalMessageReceiver(
            aplicacaoLocal, ReceiverNameScope.Global, LocalMessageReceiver.AnyDomain);

        this.receiver.MessageReceived += 
            new EventHandler<MessageReceivedEventArgs>(receiver_MessageReceived);
        this.receiver.Listen();
    }

    private void receiver_MessageReceived(object sender, MessageReceivedEventArgs e)
    {
        this.Mensagens.Text += string.Format(“{0}{1}”, e.Message, Environment.NewLine);
    }

    private void EnviaMensagem_Click(object sender, RoutedEventArgs e)
    {
        this.sender.SendAsync(this.Mensagem.Text);
    }
}

O código da outra aplicação será exatamente igual. O único detalhe ali é o construtor da classe MainPage, que recebe como parâmetros o nome local e o nome remoto. O nome remoto é utilizado no construtor da classe LocalMessageSender, pois quero enviar mensagens para este destino. Já o nome local é utilizado pelo LocalMessageReceiver, que é de onde estou interessado em receber mensagens. Mais detalhes sobre estes parâmetros na próxima seção do artigo.

Com isso em funcionamento, ao rodar as aplicações já podemos perceber o resultado. Através da imagem abaixo, vemos que o conteúdo do TextBox da aplicação 01 foi para a aplicação 02 e vice-versa.

Parametrizando as Aplicações

Acima pudemos perceber que a classe que corresponde ao controle do Silverlight (MainPage), foi parametrizada com as duas informações, que correspondem de onde a aplicação quer receber e para onde ela quer enviar informações. Esses nomes não precisam, necessariamente, corresponder a nome de aplicações. A ideia é ter um nome coerente com o tipo de informação que está sendo gerada.

Outro detalhe importante é com relação ao nome que você define para o recebedor das mensagens, pois podemos ter a mesma aplicação carregada em outra instância do navegador, e ao definir o mesmo nome, uma exceção do tipo ListenFailedException será disparada para informar que já existe um recebedor registrado com este mesmo nome.

Para evitar isso, utilizaremos a “ponte” que existe entre o Silverlight e o código HTML que o hospeda. Isso permitirá gerarmos um valor randômico via Javascript e definir na inicialização do plugin, que será encaminhada para aqueles parâmetros que vimos no construtor da classe MainPage. Para que isso funcione, temos que adicionar um novo elemento <param /> na tag object que hospeda a aplicação Silverlight (*.xap), e nomear este parâmetro como initParams. O código abaixo ilustra a adição deste novo parâmetro, com algumas informações suprimidas por questões de espaço.

   
            name=”initParams”
        value=”AplicacaoLocal=Aplicacao01,AplicacaoRemota=Aplicacao02″ />
   

A configuração refere-se à aplicação 01. Para a aplicação 02, esses parâmetros estão invertidos, já que terão papéis diferentes.

Com isso, o valor colocado ali será entregue em formato de um dicionário ao evento Startup da classe Application, que estará acessível através do arquivo App.xaml. É neste momento que extraímos os parâmetros deste dicionário e preenchemos o construtor da classe MainPage, assim como podemos notar através do código abaixo:

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = 
        new MainPage(e.InitParams[“AplicacaoLocal”], e.InitParams[“AplicacaoRemota”]);
}

Conclusão: Neste artigo vimos a utilização de uma técnica interessante que nos permite a comuicação local (no mesmo computador) de aplicações Silverlight. Podemos utilizar esse recurso para cada vez mais criar aplicações independentes, e se mais tarde, precisarmos de interação entre elas, podemos recorrer à este recurso simples, mas que traz um grande poder as mesmas.

ComunicaoLocalSL.zip (144.66 kb)

Visualizador para JSON

O JSON é um formato que temos para serializar informações, e que possibilita aplicações AJAX consumirem serviços de forma muito mais simples, dispensando todo o “overhead” do XML ou SOAP. É um formato relativamente simples, assim como podemos ver abaixo, onde temos um cliente e seus respectivos pedidos:

{“Codigo”:123,”Email”:”ia@israelaece.com”,”Nome”:”Israel”,”Pedidos”:[{“Codigo”:1,”Data”:”/Date(1282228004105-0300)/”,”Valor”:1000},{“Codigo”:2,”Data”:”/Date(1282228004144-0300)/”,”Valor”:129}]

Para facilitar a vida dos desenvolvedores, principalmente quando lidamos com a construção de serviços, que exigem a exposição de informações mais complexas do que essa mostrada acima, pode ser útil termos um visualizador para mostrar graficamente a estrutura do documento JSON. Para isso, podemos recorrer ao JSONViewer, uma ferramenta que não exige nenhuma instalação, e tudo o que você precisa fazer é rodá-la. Abaixo podemos ver a imagem do documento JSON acima, totalmente formatado e de forma gráfica.

Depois de baixar, com uma configuração simples, que você encontra no site do projeto, verá que podemos incorporá-lo ao Fiddler, para já monitorar serviços que retornam os resultados em formato JSON.

Analisando a exceção ContractException

A partir de agora, durante a escrita do nosso código, podemos recorrer ao Code Contracts, que é uma biblioteca que está sendo desenvolvida pela Microsoft, e que permite trabalhar com condições e garantir com que elas sejam atendidas durante a execução ou até mesmo de forma estática. Caso alguma das regras impostas seja violada, uma exceção será disparada, caracterizando um bug no sistema que consome este componente. O uso desse novo recurso tende a diminuir consideravelmente o uso do código if-then-throw.

A exceção que é disparada é do tipo ContractException e, propositalmente, ela foi criada como internal. Isso evita que se crie tratadores de erro que capture este tipo de exceção, e com isso burle o processo de verificação que é realizado por essa biblioteca. A aplicação que consome a classe deve se preocupar apenas em capturar exceções tradicionais (ArgumentNullException, IndexOutOfRangeException, etc.), pois enquanto houver exceções do tipo ContractException sendo disparadas, provavelmente o consumidor não está passando as informações da forma correta.

Há alguns momentos em que gostaríamos de mudar este comportamento, como por exemplo, eliminar essa validação durante a escrita de algum teste (isso envolve outra discussão, que é TDD com DbC). Para exemplificar, considere o código abaixo, que possui uma condição pré (Requires) e outra pós (Ensures), que recorre ao Code Contracts para validar e garantir que as informações são passadas da forma correta para o método Creditar e também garante o estado da classe ContaBancaria, após a execução deste mesmo método.

public class ContaBancaria
{
    public decimal Saldo { get; private set; }

    public ContaBancaria(decimal saldo)
    {
        this.Saldo = saldo;
    }

    public void Creditar(decimal quantia)
    {
        Contract.Requires(quantia > 0, “A quantia para deposito deve ser maior que 0”);
        Contract.Ensures(Contract.OldValue(this.Saldo) + quantia == this.Saldo);

        this.Saldo += quantia;
    }
}

Se criarmos um código para consumir essa classe e, consequentemente, efetuar um crédito com valor negativo, teremos uma exceção do tipo ContractException. Para contornar esse comportamento, podemos recorrer ao evento estático ContractFailed da classe Contract. Esse evento define o argumento do tipo ContractFailedEventArgs, que expõe alguns membros interessantes, e alguns deles estão listados abaixo:

  • Condition: Uma string com a condição que não foi atentida.
  • FailureKind: Uma das opções exposta pelo enumerador ContractFailureKind, indicando se é uma condição pré, pós ou invariante.
  • Message: Uma string que representa a mensagem que descreve o problema.
  • OriginalException: Retorna a instância da exceção que causou o evento, que é utilizado quando você utiliza métodos que, por questões de compatibilidade, dispara exceções diferentes de ContractException.
  • SetHandled: Método que quando invoca, suprime o disparo da exceção que violou o contrato.
  • SetUnwind: Ao contrário do método anterior, quando invocado ele dispara a exceção ContractException depois que o tratador do evento ContractFailed é encerrado.

Com os membros que vimos acima, podemos agora ter acesso ao tipo do problema que ocorreu, e ainda conseguir burlar o problema de verificação que é realizado pelo Code Contracts. Abaixo temos um exemplo de como podemos proceder para fazer isso funcionar, ou seja, invocando o método SetHandled para que erros que violam o contrato não sejam mais entregues para o código consumidor:

static void Main(string[] args)
{
    Contract.ContractFailed += (sender, args) =>
    {
        Console.WriteLine(“{0} – {1}”, args.FailureKind, args.Condition);
        args.SetHandled();
    };

    new ContaBancaria(1000).Creditar(-100);
}

Sessão e Operações One-way

O WCF fornece várias tipos de mensagens, e entre eles, temos as operações one-way. Esse tipo de operação deve ser utilizado quando o cliente não precisa de qualquer retorno (resultado ou erro), uma espécie de fire-and-forget.

Esse tipo de mensagem pode ter um comportamento estranho quando exposto através de um binding que suporte sessões nativamente, como é o caso do NetTcpBinding. O protocolo TCP estabelece um canal duplex entre o cliente e o serviço, mantendo uma espécie de conexão ativa, onde a qualquer momento um pode tranquilamente se comunicar com o outro.

É importante dizer que mensagens one-way não são assíncronas, mas o tempo de entrega é tolerável. O maior problema, do qual falava anteriormente, refere-se ao fechamento do proxy depois da mensagem one-way disparada ao destinatário, ou seja, quando invocamos a operação one-way, tão logo já conseguimos continuar trabalhando na aplicação cliente, mas se tentarmos fechar o proxy, ficaremos bloqueados até que a operação one-way seja completamente finalizada. Mas se a ideia é disparar e esquecer, porque eu preciso esperar, mantendo o proxy aberto até finalizar a operação?

Para ter o comportamento fire-and-forget a sério, uma das alternativas que temos é habilitar as mensagens (ou sessões) confiáveis. Isso tornará a comunicação entre as partes muito mais descritiva, onde além das mensagens normais, ainda haverão uma porção de outras mensagens que são geradas pelo protocolo WS-ReliableMessaging, indicando se a mensagem foi entregue, quando ela foi entregue e a ordem de chegada, permitindo ao cliente continuar sem a necessidade de esperar. Para habilitar as mensagens confiáveis, consulte este artigo.

Apesar disso funcionar, a comunicação entre as partes ficará bem mais volumosa, já que várias mensagens de infraestrutura serão trocadas para garantir a entrega da mensagem principal. Para evitar todo este overhead causado pelo protocolo WS-ReliableMessaging, podemos recorrer a construção de um binding customizado, empilhando os vários elementos para compor a comunicação via TCP, mas com um detalhe, que é a adição de um novo elemento, chamado de OneWayBindingElement. A adição deste elemento fará com que o canal de comunicação se comporte semanticamente como um fire-and-forget. O código abaixo ilustra o seu uso, via código imperativo e declarativo, respectivamente:

private static Binding CreateOneWayTcpBinding()
{
    BindingElementCollection currentElements = new NetTcpBinding().CreateBindingElements();
    BindingElementCollection bindingElements = new BindingElementCollection();

    bindingElements.Add(new OneWayBindingElement());

    foreach (BindingElement be in currentElements)
        bindingElements.Add(be);

    return new CustomBinding(bindingElements);
}

<system.serviceModel>
  <bindings>
    <customBinding>
      <binding name=”OneWayTcpBinding”>
        <oneWay />
        <binaryMessageEncoding />
        <tcpTransport />
      </binding>
    </customBinding>
  </bindings>
</system.serviceModel>

Só que utilizar este procedimento possui dois detalhes importantes: o primeiro é que ao configurá-lo, o canal somente permitirá a comunicação através do modelo one-way, e com isso, o contrato que você está expondo obrigatoriamente precisa definir todas as operações neste mesmo modelo. Se tiver qualquer operação neste contrato do tipo request/reply, ao tentar subir o serviço, você irá se deparar com uma exceção do tipo InvalidOperationException, indicando justamente isso. Se você conseguir garantir com que todas as operações sejam one-way, então utilizar esta técnica pode trazer uma grande melhora em performance, quando comparado à opção anterior.

O segundo detalhe é com relação ao modelo de gerenciamento de instância. Mesmo que você diga ao serviço que ele deve ser PerSession, ele não conseguirá manter a sessão, ou seja, terá o comportamento do modelo PerCall, pois cada chamada para uma operação one-way encerrará a “sessão”.

Omitindo tipos genéricos durante a construção

Os Generics trouxeram um grande poder às linguagens .NET. O tipos genéricos podem ser utilizados por toda a classe onde ele está sendo criado e, consequentemente, podemos utilizar em seus construtores. O maior problema da criação de objetos que exigem um parâmetro genérico, é a necessidade de especificarmos esses tipos durante a sua construção, ou seja, o construtor não é capaz de inferir os tipos, o que torna o código um pouco ilegível. Para exemplificar, considere o seguinte código:

public class ProxyManager<TChannel, TBinding>
{
    private TChannel _channel;
    private TBinding _binding;

    public ProxyManager(TChannel channel, TBinding binding)
    {
        this._channel = channel;
        this._binding = binding;
    }

    //Outros Membros
}

Durante a sua criação, é necessário especificarmos os tipos genéricos para depois conseguir parametrizar o construtor com os valores efetivos. Se repararmos o código abaixo, note que não podemos omitir o que está entre os caracteres < e >, mesmo que construtores são uma espécie de método, eles não possibilitam a omissão dos mesmos, assim como acontece nos métodos genéricos.

var manager = 
    new ProxyManager<ServicoDeUsuariosClient, NetTcpBinding>(
        new ServicoDeUsuariosClient(), 
        new NetTcpBinding());

Para melhorar esse código e torná-lo mais simples de consumir a classe, podemos recorrer a criação de uma nova classe, que conterá um método estático que aceita exatamente os parâmetros genéricos exigidos pelo construtor da classe ProxyManager<TChannel, TBinding>, e o retorno será a própria classe criada, uma espécie de factory. O que acontece aqui é que todo o código burocrático acaba ficando dentro deste método.

public static class ProxyManager
{
    public static ProxyManager<TChannel, TBinding> Create<TChannel, TBinding>(TChannel channel, TBinding binding)
    {
        return new ProxyManager<TChannel, TBinding>(channel, binding);
    }
}

Com isso, a criação de um novo ProxyManager, se resume simplesmente ao código que vemos abaixo:

var manager = 
    new ProxyManager.Create(new ServicoDeUsuariosClient(), new NetTcpBinding());

Utilizando o Url Routing no WCF

Quando recorremos à uma das templates para a construção de serviços WCF (WCF Service Application ou WCF Service), ambas utilizam arquivos *.svc que representam o endpoint do serviço. Dentro deste arquivo temos apenas uma diretiva chamada @ServiceHost, que instrui o ASP.NET a como compilar o referido serviço.

Abaixo podemos visualizar um exemplo deste arquivo, que em seu atributo Service, permite apontar o tipo do serviço. Já no atributo CodeBehind, corresponde ao arquivo físico que possui a classe que representa o serviço e, finalmente, temos a atributo Factory, que é omitido por padrão, mas que tem a finalidade de criar instâncias da classe que gerencia o serviço (ServiceHost ou alguma de suas derivadas).

<%@ ServiceHost
    Language=”C#”
    Debug=”true”
    Service=”ServicoDeClientes”
    Factory=”System.ServiceModel.Activation.WebServiceHostFactory”
    CodeBehind=”~/App_Code/ServicoDeClientes.cs” %>

No WCF 4.0, a Microsoft incluiu a possibilidade de criarmos serviços WCF sem a necessidade da presença de um arquivo *.svc. Isso eliminará completamente a necessidade deste arquivo. Mas para poder excluir o arquivo, tudo o que precisamos fazer é elencar os serviços no arquivo Web.config, utilizando o sub-elemento serviceActivations, exposto pelo elemento serviceHostingEnvironment:

<system.serviceModel>
  <serviceHostingEnvironment>
    <serviceActivations>
      <add
        relativeAddress=”~/ServicoDeClientes.svc”
        service=”Servicos.ServicoDeClientes”
        factory=”System.ServiceModel.Activation.WebServiceHostFactory”/>
    </serviceActivations>
  </serviceHostingEnvironment>
</system.serviceModel>

Apesar de ainda precisar especifcar o arquivo no relativeAddress, ele não existe fisicamente. Note também que através do atributo service definimos o nome da classe que corresponde ao serviço e, finalmente, como já era de se esperar, podemos utilizar uma factory específica, que já configura o host com tudo o que é necessário para prepará-lo para receber e processar as requisições para uma determina situação. Com isso temos uma aplicação mais simples, onde tudo o que precisamos desenvolver é o contrato (interface) e o serviço (classe), assim como já fazemos com qualquer outro tipo de serviço WCF.

O maior problema desta técnica é a necessidade de ainda precisar informar a extensão do arquivo quando for efetuar uma requisição, e isso não é legal em um modelo REST. Lembre-se de que a funcionalidade que vimos acima, apenas elimina a necessidade do arquivo físico. Remover a extensão *.svc do atributo relativeAddress, não resolverá, pois vamos nos deparar com uma exceção do tipo ConfigurationErrorsException, dizendo que o valor informado no atributo relativeAddress não possui uma extensão válida.

Além do Url Rewrite Module (que já discuti no final deste artigo), temos agora a possibilidade de integrar o sistema de roteamento de URL do ASP.NET aos serviços WCF. Também na versão 4.0 do WCF, a Microsoft incluiu no assembly System.ServiceModel.Activation uma classe chamada ServiceRoute, que permite especificarmos exatamente os mesmos parâmetros que vimos acima e, automaticamente, será processado pelo handler correspondente (ServiceRouteHandler). Com isso, no evento Application_Start do arquivo Global.asax, devemos registrar mais uma rota, essa específica para o nosso serviço:

protected void Application_Start(object sender, EventArgs e)
{
    RouteTable.Routes.Add(
        new ServiceRoute(
            “ServicoDeClientes”,
            new WebServiceHostFactory(), 
            typeof(Servicos.ServicoDeClientes)));
}

A string que aparece sendo informada no construtor da classe ServiceRoute, define o nome do recurso para qual estaremos efetuando o roteamento. Os outros dois parâmetros refletem as mesmas configurações que vimos acima. Mas ainda é precisa se atentar a um detalhe: se você estiver utilizando um projeto que não vem com o módulo de roteamento de URL configurado, você terá que fazer isso manualmente, assim como eu mostro abaixo:

<system.web>
  <httpModules>
    <add
      name=”UrlRoutingModule”
      type=”System.Web.Routing.UrlRoutingModule, System.Web” />
  </httpModules>
</system.web>

O único detalhe aqui é a necessidade de habilitar o modelo de compatibilidade com o ASP.NET, caso contrário, esta funcionalidade não poderá ser utilizada. Para habilitar isso no contrato, precisamos recorrer ao atributo AspNetCompatibilityRequirementsAttribute, definindo-o como Allowed (para permitir que ele seja exposto por outros tipos de hosts), e depois devemos habilitar a compatibilidade no arquivo de configuração, através do elemento serviceHostingEnvironment, definindo o atributo aspNetCompatibilityEnabled como True. Abaixo é exibido as duas configurações:

namespace Servicos
{
    [AspNetCompatibilityRequirements(
        RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class ServicoDeClientes : IServico
    {
        //…
    }
}

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
</system.serviceModel>

Depois destas configurações, já podemos acessar o serviço diretamente sem informar a extensão *.svc. É importante dizer que isso só é necessário quando você hospeda o seu serviço no IIS, já que as requisições são sempre realizadas para um arquivo físico. Quando você cria um serviço REST e deseja expor através de algum outro host, nada disso será necessário.

TechEd Brasil 2010 – Palestras

Nos dias 13, 14 e 15 de Setembro, haverá em São Paulo o evento TechEd 2010. Este evento reune uma grande quantidade de palestras voltadas para as tecnologias mais atuais, e além disso, vários profissionais que trabalham com tecnologias Microsoft. Neste ano, estarei efetuando duas palestras, quais estão listadas abaixo, seguida de sua respectiva descrição:

Título: Visão Geral do Windows Identity Foundation (WIF)
Código: SIA303
Palestrante: Israel Aece
Nível: 300
Descritivo: Autenticação e Autorização são duas preocupações que existem em toda e qualquer aplicação. Dependendo do tipo de aplicação que está sendo construída, há uma forma diferente de você implementar essas funcionalidades, que exige o conhecimento específico da respectiva API que cada uma fornece, e como se isso não bastasse, ainda precisamos nos atentar a alguns detalhes que são comuns para qualquer situação, o que pode tornar certos aspectos redundantes. A finalidade desta palestra é abordar o novo framework da Microsoft chamado Windows Identity Foundation (WIF), qual podemos acoplar as nossas aplicações, para tornar essas tarefas árduas em algo bem mais simples.

Título: Implementando Serviços RESTful usando o Microsoft .NET Framework
Código: DEV305
Palestrante: Israel Aece
Nível: 300
Descritivo: Nesta sessão, aprenda como desenvolvedores .NET podem reutilizar seu conhecimento do Windows Communication Foundation (WCF) para aproveitar as ferramentas integradas e extensibilidade de um framework de WCF único, incluindo WCF WebHttp Services para RESTful Services, WCF Data Services e OData e WCF RIA Services para o desenvolvimento do início ao fim de aplicações Microsoft Silverlight.

Flexibilizando a requisição e resposta à serviços REST

Ao criar um serviço WCF para ser acessado através do modelo REST, devemos decorar as operações que o compõem o contrato com os atributos WebGetAttribute ou WebInvokeAttribute, dependendo de como as requisições devem chegar até elas.

As requisições para estas operações podem ser enviadas e recebidas através de dois formatos: Xml ou Json, e para configurar isso, podemos recorrer à duas propriedades fornecidas pelos dois atributos listados acima. Essas propriedades são: RequestFormat e ResponseFormat, onde ambas recebem uma das opções expostas pelo enumerador WebMessageFormat.

Isso quer dizer que devemos definir, de forma estática, o formato que a mensagem será aceita e como ela será devolvida para os clientes. O grande problema desta técnica é que as vezes você pode ter um mesmo serviço sendo consumindo por clientes diferentes, que lidam melhor com um formato específico. Por exemplo, você pode ter um mesmo serviço sendo consumido por uma aplicação Silverlight, que possui um suporte melhor ao Xml, e ao mesmo tempo, o mesmo serviço sendo consumido por um código jQuery, que lida melhor com o formato Json. Para tornar o serviço flexível, tínhamos que fazer isso de forma imperativa, ou seja, dentro da implementação da operação, tínhamos que validar qual o formato desejado pelo cliente, e com isso especificar no contexto da requisição qual o formato que deve ser utilizado pelo runtime do WCF para gerar o resultado. O código abaixo ilustra de forma resumida essa condição:

public class Servico : IContrato
{
    public string Ping(string value)
    {
        WebOperationContext.Current.OutgoingResponse.Format =
            VerificarFormato(…) == “json” ? WebMessageFormat.Json : WebMessageFormat.Xml;

        return “resultado da operação”;
    }
}

Geralmente o formato é fornecido por uma query string adicional ou analisando os headers da requisição HTTP, que informa o formato através do content-type. O código acima permite definirmos o formato da resposta baseando-se na preferência do usuário, mas como podemos perceber, há muito código para avaliar isso, e me obriga a misturar a minha implementação com código de infraestrutura.

Para facilitar, a Microsoft criou na versão 4.0 do WCF, uma nova propriedade na classe WebHttpBehavior, chamada AutomaticFormatSelectionEnabled. Trata-se de uma propriedade boleana, que quando definida como True, irá interpretar a requisição e gerar a resposta no mesmo formato estipulado pelo cliente, olhando para a propriedade content-type que está nos presente nos headers da requisição. Para habilitar, podemos recorrer ao seguinte código:

<?xml version=”1.0″?>
<configuration>
  <system.serviceModel>
    <services>
      <service name=”Service”>
        <endpoint
          address=””
          binding=”webHttpBinding”
          contract=”IService”
          endpointConfiguration=”edpConfig” />
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name=”edpConfig”>
          <webHttp automaticFormatSelectionEnabled=”true” />
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Com esta opção habilitada, meu contrato fica totalmente independente do formato, ou seja, se o cliente está requisitando em formato Json, o WCF será capaz de acatar a requisição, processá-la e devolver o resultado no mesmo formato. O mesmo acontece com o Xml. Finalmente, você pode ter um único serviço sendo capaz de receber e retornar mensagens no mesmo formato do cliente, não os obrigando mais ele a trabalhar com o formato específico, que as vezes diferente daquele que é o mais comum.

Escolhendo o gerenciamento de estado no ASP.NET

Uma das principais necessidades em uma aplicação Web é a manutenção de estado. O protocolo HTTP determina que todas as aplicações que rodam sobre ele, não mantém nenhuma espécie de informação do lado do servidor, ou seja, ao requisitar qualquer recurso, uma vez que o conteúdo é enviado ao cliente solicitante, tudo o que foi gerado do lado do servidor para atende-lo, será descartado. Caso uma nova requisição ocorrer, mesmo que seja para o mesmo recurso, tudo será reconstruído.

Mas o que é o “estado”? Trata-se de dados que representam certas informações para o usuário ou para a aplicação, e que precisam ser armazenadas em algum local para conseguirmos restaurá-las no momento em que precisarmos delas. Depois que a requisição foi atendida pelo servidor, se ele precisar armazenar informações para aquele usuário, precisamos fazer uso de alguma das alternativas que são disponibilizadas pela tecnologia que estamos utilizando, e com isso, sermos capazes de manter esses dados mesmo que o usuário esteja totalmente “desconectado” da aplicação, para que quando ele voltar ao servidor, os dados continuarão disponíveis, refazendo o estado da requisição antes dela ser enviada ao navegador.

As alternativas que temos depende da tecnologia que estamos utilizando. No ambiente Microsoft, a tecnologia para desenvolvimento de aplicações Web é o ASP.NET. Ele, por sua vez, traz uma porção de opções para utilizarmos, e atualmente temos: Application, Caching, Controles Hidden, ViewState, Cookie, Session, Profile, QueryString e Context.Items. Para você escolher o que melhor se encaixa a sua necessidade, é necessário avaliar detalhes como segurança e performance. São essas duas categorias que mais influenciam na escolha de qual opção utilizar para manter as informações.

Para tentar auxiliar na escolha de cada um, criei um fluxograma que tenta exibir a melhor opção de acordo com a necessidade, balanceando entre escopo, performance e segurança. Abaixo temos a imagem que representa esse fluxograma, e na sequência, uma descrição mais detalhada de cada condicional que vemos nele.

  • Compartilhar: O compartilhamento consiste em ter uma informação que pode estar acessível entre vários usuários que acessam a mesma aplicação. Isso quer dizer que qualquer alteração que um usuário faça, já refletirá para todos aqueles que a acessam.
  • Dependência ou Expiração: Algumas vezes, a informação que é compartilhada entre os usuários poderá ter critérios de invalidação. Se você tem algo na memória e quer determinar uma forma de removê-la de lá, você pode optar por dependência ou expiração. A primeira delas consiste em ser reativo, ou seja, seremos notificado quando alguma mudança ocorrer na origem da informação, enquanto a segunda opção, devemos especificar um valor para determinar quando ela deverá ser removida.
  • Acessível entre requisições (escopo de usuário): A ideia desta condição é determina se os dados precisam ou não sobreviver entre a navegação das páginas da aplicação.
  • Dados Complexos ou Sigilosos (escopo de usuário): Para as informações que devem sobreviver entre todas as páginas da aplicação, podemos dividir em dados complexos ou sigilosos. Informações simples, como por exemplo, o idioma selecionado pelo usuário, podemos recorrer aos Cookies para armazená-las. Já aqueles dados que vão muito além do que simples strings, como é o caso de um carrinho de compras, podemos recorrer a recursos mais poderosos, como Session ou Profile.
  • Persistência: Utilizar Session ou Profile dependerá, principalmente, se você quer ou não ter a capacidade de persistir as informações. A principal diferença entre armazenar o carrinho de compras na Session ou não Profile, é que na segunda opção, as informações sobreviverão entre reinicializações do navegador e do servidor, ao contrário do que acontece com a Session [1], que é descartada ao fechar o navegador.
  • Transferência: A transferência consiste em levar dados de uma página para a outra, que geralmente parametrizam as tarefas que esta segunda página irá desempenhar.
  • Transferência no Servidor: Caso você já esteja executando algum código no servidor, então você pode optar pelo Context.Items para enviar os dados para uma outra página, sem a necessidade de voltar ao navegador, e a partir dali, ir para a página solicitada. Utilizar o Context.Items garante que as informações sejam passadas de forma transparente, e o usuário ou até mesmo os interceptadores de requisições HTTP não conseguirão capturar esses dados. Agora, se tivermos a necessidade de parametrizar publicamente a chamada de uma página, então a QueryString é a melhor saída, mas apenas lembre-se de que ela possue limitação de tamanho e você não deve passar dados sensíveis ali.
  • Manter dados na própria página: Para casos onde precisamos manter dados para uso excluso da página que estamos utilizando, podemos recorrer aos controles Hidden [2]. Esse tipo de controle é capaz de guardar informações simples, e o seu uso serve apenas para manter informações que não sejam sensíveis, estando tão vulneráveis quanto as QueryStrings.

[1] – A configuração padrão da Session no ASP.NET faz com que as informações sejam armazenadas InProc, ou seja, na memória do próprio servidor Web. Você pode alterar esse comportamento, e eleger um servidor SQL Server para persistir essas informações. Mas para essa situação, o Profile pode ser melhor, já que possui várias outras características interessantes, como a possibilidade de tipar as informações que serão armazenadas, migração de usuários anônimos, etc.

[2] – No caso do ASP.NET Web Forms, o ViewState também pode ser utilizado para manter as informações em nível de página, mas que irá recorrer ao uso de controles Hidden para o armazenamento delas. É importante dizer que o ViewState é armazenado nestes controles de forma codificada, ou seja, é possível extrair o que armazenamos ali. Seguindo a mesma linha de alguns itens anteriores, você não deve colocar informações sigilosas dentro dele.

Todos as opções para armazenamento do estado são basicamente dicionários, ou seja, utilizam a chave como sendo uma string. Já o valor dependerá do que está utilizando, por exemplo, no caso de Caching, Application, Session, Profile ou Context.Items, você poderá armazenar qualquer objeto, desde que ele esteja decorado com o atributo SerializableAttribute. As outras opções apenas trabalham com simples strings como valor.

Detectando a desconexão – Parte 2

Há algum tempo eu mostrei aqui como podemos proceder para detectar a desconexão do cliente de um serviço WCF. Aquela solução tem a finalidade de detectar a desconexão do cliente ao tentar enviar um callback para ele, onde podemos analisar o estado do canal de comunicação com o mesmo, e se estiver em estado falho ou se já foi fechado, podemos desprezar a notificação.

O problema daquela técnica é que o serviço somente saberá que o cliente não está mais ativo, quando tentarmos acessar o canal para se comunicar com ele. Se o cliente já encerrou a aplicação, de forma normal ou não (matando o processo), a referência dele ainda existirá dentro do nosso serviço.

Para sermos notificados de que o cliente foi encerrado, novamente, de forma normal ou não, podemos recorrer à implementação da interface IInputSessionShutdown. Para bindings que suportam sessões, essa interface pode ser aplicada ao runtime, e sermos notificados quando o cliente encerrar a conexão com o serviço. Essa interface fornece dois métodos: DoneReceiving e ChannelFaulted. O primeiro método é disparado quando o cliente não puder mais receber as notificações, pois ele encerrou o canal de comunicação com o serviço; já o método ChannelFaulted é disparado quando o canal de comunicação do cliente entra em estado falho, ou seja, algum problema ocorreu na aplicação consumidora do serviço, que não foi capaz de encerrar o proxy de forma explicíta. Abaixo temos uma implementação simples deste recurso:

public class SessionShutdownTrigger : IInputSessionShutdown
{
    public void ChannelFaulted(IDuplexContextChannel channel)
    {
        Console.WriteLine(“ChannelFaulted”);
    }

    public void DoneReceiving(IDuplexContextChannel channel)
    {
        Console.WriteLine(“DoneReceiving”);
    }
}

Como podemos notar, ambos métodos recebem como parâmetro um objeto que implementa a interface IDuplexContextChannel, que corresponde ao canal de comunicação do cliente. Com esta classe criada, precisamos acoaplá-la ao runtime do WCF, e para isso recorremos aos pontos de estensibilidade que o WCF fornece, mais precisamente, ao uso da interface IContractBehavior, que nos permite modificar, examinar ou estender aspectos pertinentes à um contrato. Ao implementar essa interface, entre os vários métodos que ela fornece, temos o método ApplyDispatchBehavior, que nos permite interceptar e aplicar algum recurso customizado ao dispatcher do serviço. Justamente por isso, como parâmetro recebemos uma instância da classe DispatchRuntime, que por sua vez, fornece uma propriedade chamada InputSessionShutdownHandlers, que nos permite adicionar instâncias de classes que implementam a interface IInputSessionShutdown. O código abaixo ilustra essa classe:

public class SessionShutdownTriggerBehaviorAttribute : Attribute, IContractBehavior
{
    public void ApplyDispatchBehavior(ContractDescription contractDescription,
        ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.InputSessionShutdownHandlers.Add(new SessionShutdownTrigger());
    }

    //Outros Métodos
}

Essa classe por si só não funciona. Note que ela herda da classe Attribute, que me permite decorar a classe que representa o serviço, e com isso, ao rodá-lo, o WCF será capaz de perceber a presença deste atributo, e executar o método ApplyDispatchBehavior, incluindo a instância da classe SessionShutdownTrigger que criamos acima e, finalmente, quando a cliente encerrar o canal de comunicação, seremos notificados que isso ocorreu, e podemos executar algum código pertinentes aquele cliente, sem a necessidade de somente detectarmos isso quando precisarmos efetivamente comunicar com ele. Abaixo temos o contrato do serviço com esse atributo decorado:

[SessionShutdownTriggerBehavior]
public class Servico : IContrato
{
    public string Ping(string value)
    {
        return value;
    }
}