Como configurar serviços com WCF Test Client

by Israel Aece 30. June 2009 08:16

Quando utilizamos a template de projeto WCF Service Library, todas as configurações do serviço estão no arquivo App.config. Ao rodar a aplicação, o utilitário conhecido como WCF Test Client é executado, fazendo tudo o que é necessário para efetuar o hosting do serviço recém criado. Como não temos acesso a criação e o gerenciamento do host (representado pela classe ServiceHost), não poderemos configurá-lo de forma imperativa.

Como grande parte das configurações do WCF pode ser realizada através do arquivo de configuração, podemos utilitizá-lo para adicionar behaviors existentes no WCF, mudar alguma característica do binding, etc. O problema aparece quando precisamos adicionar um behavior customizado. Neste caso, além da classe que o representa, ainda é necessário a criação de uma segunda classe, que definirá o respectivo behavior no arquivo de configuração.

Para isso, é necessário criar uma classe e herdar de BehaviorExtensionElement, implementando os membros BehaviorType e CreateBehavior, assim como mostro no final deste artigo. Depois desta classe criada, precisamos ainda registrar esta extensão no arquivo de configuração, antes de efetivamente fazer o uso dela. Para isso, utilizamos a seção behaviorExtensions, como mostrado abaixo:

<extensions>
  <behaviorExtensions>
    <add name="errorService"
         type="Host.ErrorServiceElement, Host, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>

E, finalmente, utilizá-la:

<behaviors>
  <serviceBehaviors>
    <behavior name="BehaviorConfig">
      <errorService />
      <serviceMetadata httpGetEnabled="True"/>
      <serviceDebug includeExceptionDetailInFaults="False" />
    </behavior>
  </serviceBehaviors>
</behaviors>

Tags: ,

WCF

Uma alternativa aos cursores

by Israel Aece 24. June 2009 12:39

Uma funcionalidade que existe no SQL Server é a capacidade de iterar pelos resultados de uma consulta através de cursores. Não sou especialista em SQL Server, mas sei que a sua utilização degrada consideravelmente a performance. Talvez se utilizá-lo em uma quantidade pequena de informações, ele pode executar bem, mas não sei ao o impacto que isso causa, devido aos recursos do sistema que ele utiliza para fazer funcionar.

Como havia a necessidade de melhorar o resultado de um relatório extremamente complexo, e as alternativas em T-SQL já estavam esgotadas, a solução foi recriar a Stored Procedure utilizando o .NET. Sustituimos os cursores por SqlDataReaders, e a diferença foi bastante significativa. E ainda nem precisei abrir mão da segurança, já que o Assembly com a Stored Procedure gerenciada, foi catalogado com o nível de segurança definido como Safe.

Tags: , ,

Data

WCF - Durable Services

by Israel Aece 23. June 2009 12:24

Vimos nos artigos anteriores como criar, hospedar e consumir serviços com o WCF. Todas as tarefas que esses serviços disponibilizavam tinham uma duração curta, ou seja, o processo completo se resumia apenas na chamada de uma única operação. Mas é muito comum, em aplicações distribuídas, termos processos que podem durar muito mais tempo para completar toda a tarefa. Neste caso, pode ser complicado manter ativa a instância do serviço ou do cliente por todo esse tempo.

A Microsoft introduziu na versão 3.5 do WCF uma funcionalidade chamada de Durable Services. Como o próprio nome diz, ele possibilita a criação de serviços que podem ser persistidos, sobrevivendo a eventuais reciclagens do host que o hospeda e também de reinicializações do cliente. Com este artigo vamos analisar como devemos proceder para incorporar esta funcionalidade em nossos serviços.

Independentemente do modelo de gerenciamento de instância que utilize, a classe que representa o serviço tem um tempo de vida determinado, e se por algum motivo a aplicação que hospeda o mesmo for reinicializada, todo o estado do objeto será perdido. Isso ocorre porque todo esse estado, que cada objeto que representa o serviço mantém, é armazenado em memória, ou seja, é volátil e não conseguirá sobreviver durante possíveis reinicializações, e pode comprometer a regra de negócio, caso você dependa desta informação.

Com os Durable Services, podemos persistir as informações em algum repositório ao invés de utilizar a memória. Isso irá garantir que as informações sejam mantidas, mesmo que o processo demore dias para ser concluído. Mesmo que a sessão com o cliente seja destruída, você conseguirá restaurar o estado mais tarde. A Microsoft já disponibilizou um provider para armazenar as informações no banco de dados SQL Server, mas nada impede de você customizar, optando pela criação de um provider que armazene as informações em arquivos XML.

Para guiar os exemplos, teremos o seguinte cenário: um serviço que irá fornecer as operações necessárias de um comércio eletrônico, como por exemplo a criação de um carrinho, a inserção de novos itens, recuperação dos itens selecionados e a finalização da compra. A idéia é que os itens selecionados pelo usuário, sejam mantidos além das sessões e também do desligamento da aplicação cliente e, eventualmente, do serviço.

Os tipos que usaremos estão contidos no Assembly System.WorkflowServices.dll. Antes de efetivamente começarmos a ver os tipos disponibilizados por esse Assembly, precisamos preparar a base de dados para que ela consiga acomodar as informações. Felizmente já temos todo o script pronto, apenas será necessário executá-lo. Para isso, basta ir até o seguinte endereço: %windir%\Microsoft.Net\Framework\v3.5\SQL\EN\. Lá temos quatro arquivos, sendo dois para a criação e dois para a exclusão. No nosso caso, devemos executar os seguintes arquivos: SqlPersistenceProviderSchema.sql e SqlPersistenceProviderLogic.sql, nesta mesma ordem. O primeiro é responsável por criar a tabela InstanceData, enquanto o segundo cria as Stored Procedures com toda a lógica de inserção, exclusão e carregamento das instâncias.

Podemos serializar o estado do serviço de forma binária (o padrão) ou através de Xml, e para suportar isso, temos duas colunas na tabela InstanceData, chamadas de “instance” e “instanceXml”. A primeira é utilizada quando o estado é serializado em formato binário, enquanto a segunda apenas será utilizada quando o conteúdo for persistido em Xml. Além dessas duas colunas, ainda temos a coluna “id”, que terá o seu valor propagado do serviço para o cliente e sendo devolvido do cliente para o serviço. Esse ID representa a instância (estado) do serviço que foi armazenada. Falaremos detalhadamente sobre ela mais tarde, ainda neste artigo.

Implementação

O contrato do serviço não sofrerá qualquer alteração. Você deve continuar decorando a interface com o atributo ServiceContractAttribute e as operações que serão expostas, com o atributo ServiceOperationAttribute. As mudanças começam a aparecer na classe que representará o serviço. O primeiro passo para a criação de um serviço durável, é decorar a classe que representa o serviço com o atributo DurableServiceAttribute. Durante o carregamento do serviço, este atributo irá garantir que o modo de gerenciamento de concorrência não esteja definido como Multiple e que o modo de gerenciamento de instância deve ser PerSession. É importante dizer que o estado deve ser de uso exclusivo de uma sessão. Se desejar que o estado seja compartilhado com todos os clientes (sessões), então você deve optar pelo modo Multiple de gerenciamento de instância. E mais um detalhe importante, temos que aplicar o atributo SerializableAttribute, assim como todas as classes que desejamos serializar.

Cada operação que irá compor o serviço durável, deverá ser decorada com o atributo DurableOperationAttribute. Esse atributo indica ao runtime que ao completar cada operação, o estado do serviço deverá ser persistido fisicamente. Essa classe possui duas propriedades, que por padrão são sempre False: CanCreateInstance e CompletesInstance. A primeira delas indica se uma nova instância do serviço deve ser criada ao executar a respectiva operação. Já a segunda propriedade, indica se a instância será removida da memória e excluída do repositório quando a operação for executada. A classe abaixo exibe como configurar esses atributos, com a implementação omitida para poupar espaço:

[Serializable]
[DurableService]
public class ServicoDeComercioEletronico : IComercioEletronio
{
    private List<ItemDaCompra> _produtos;
    private string _usuario;

    [DurableOperation(CanCreateInstance = true)]
    public void CriarCarrinho(string usuario) { }

    [DurableOperation]
    public void AdicionarItem(ItemDaCompra item) { }

    [DurableOperation]
    public ItemDaCompra[] RecuperarItensDaCompra() { }

    [DurableOperation(CompletesInstance = true)]
    public void FinalizarCompra() { }
}

Não há nenhuma mudança drástica na implementação do serviço, apenas temos que nos atentar ao estado dos membros internos, que serão mantidos entre as chamadas (lembre-se de que essa persistência sobreviverá mesmo após o host ou o cliente ser encerrado).

Mudanças mais significativas são realizadas para expor o serviço. Isso se deve ao fato da necessidade de propagar o ID que representa o estado o serviço. O ID em questão é o mesmo que é gerado durante a inserção do registro na tabela acima mencionada. Quando o runtime encontra uma operação que possui o atributo DurableOperationAttribute e com a propriedade CanCreateInstance definida como True, ele irá criar um registro na tabela InstanceData, capturar o ID gerado e devolver para o cliente. Todas as operações subsequentes devem embutir esse ID.

Antes da operação ser efetivamente executada, o runtime irá extrair a instância da base de dados, abastecer os membros privados previamente serializados, e depois disso irá executar a operação; ao retornar, o runtime devolve os dados para a base de dados, com as informações atualizadas. Finalmente, quando o runtime encontra uma operação com o atributo DurableOperationAttribute e com a propriedade CompletesInstance definida como True, o respectivo registro que representa o estado do serviço é excluído da base de dados.

Como podemos perceber, todo o processo acontece utilizando o ID gerado durante a primeira requisição, e a necessidade de mantê-lo durante as requisições futuras se faz necessário, caso queira manter o estado. Visando essa manutenção do ID, a Microsoft criou três novos bindings: NetTcpContextBinding, BasicHttpContextBinding e WSHttpContextBinding. Cada um deles herda diretamente dos bindings tradicionais (NetTcpBinding, BasicHttpBinding e WSHttpBinding), apenas trazendo o suporte necessário para gerenciar o ID de persistência.

Cada um destes bindings sobrescrevem o método CreateBindingElements, criando uma instância da classe ContextBindingElement. Este elemento é responsável por gerenciar como o ID será propagado entre o serviço e o cliente (ou vice-versa). Em seu construtor, recebe uma das seguintes opções expostas pelo enumerador ContextExchangeMechanism:

    - ContextSoapHeader: O ID será enviado através de um header na mensagem SOAP. É o valor padrão.
    - HttpCookie: O ID será definido em um cookie.

Cada um dos bindings utiliza um mecanismo diferente. O binding NetTcpContextBinding utiliza a primeira opção, mesmo porque não é possível utilizar cookies através de TCP. Já o BasicHttpContextBinding utiliza cookies para manter o ID, e irá disparar uma exceção caso não haja suporte aos mesmos. Finalmente, o binding WSHttpContextBinding utiliza cookies quando suportado, e SOAP Headers quando não há tal suporte. Com isso, não precisamos nos preocupar como o ID será propagado entre as partes, apenas teremos a responsabilidade de armazenar o ID para conseguir carregar uma instância previamente criada. O trecho de código abaixo ilustra como proceder para configurar a classe ServiceHost, utilizando o binding NetTcpContextBinding:

using (ServiceHost host =
    new ServiceHost(typeof(ServicoDeComercioEletronico),
        new Uri[] { new Uri("net.tcp://localhost:3832") }))
{
    host.Description.Behaviors.Add(ConfigurarPersistencia());
    host.AddServiceEndpoint(typeof(IComercioEletronio), new NetTcpContextBinding(), "srv");

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

Utilizar um dos bindings que vimos acima não é o bastante. Ainda precisamos configurar a persistência das informações, e como já era de se esperar, isso será feito através de um behavior de serviço, chamado PersistenceProviderBehavior. Note que há um método customizado chamado de “ConfigurarPersistencia”, que é o responsável por criar e retornar a instância do provider que fará a persistência das informações.

O construtor da classe PersistenceProviderBehavior recebe como parâmetro uma instância da classe PersistenceProviderFactory. Essa classe abstrata serve como base para todos os providers, inclusive aquele que a Microsoft já disponibilizou para efetuar a persistência no SQL Server. Caso você queira criar o seu próprio provider, então será necessário criar duas classes: o provider em si (herdando da classe PersistenceProvider) e a factory responsável por criar e gerir as instâncias do respectivo provider (herdando de PersistenceProviderFactory).

Como comentado acima, utilizaremos o provider para SQL Server, chamado SqlPersistenceProviderFactory, que está contido no namespace System.ServiceModel.Persistence. Para utilitizá-lo, é importante que você prepare a sua base de dados, rodando os scripts mencionados acima. Como estou utilizando a configuração imperativa, então vou criar a instância da classe SqlPersistenceProviderFactory, que em seu construtor receberá a string de conexão com a base de dados. Note que no código abaixo, além da string de conexão, ainda é passado um valor boleano, indicando como será efetuado a persistência, onde True indica que será serializada em Xml e False em formato binário (padrão).

static PersistenceProviderBehavior ConfigurarPersistencia()
{
    return new PersistenceProviderBehavior(
        new SqlPersistenceProviderFactory(
            ConfigurationManager.ConnectionStrings["SqlConnString"].ConnectionString, true));
}

Consumindo o Serviço

Para referenciar e invocar operações que compõem serviços duráveis, não há diferenças em relação ao que já conhecemos. Apenas devemos ter duas preocupações: utilizar o binding correspondente ao binding utilizado pelo serviço e também como e onde armazenar o ID que representa a instância remota, que por sua vez, deverá ser devolvido do cliente para o serviço, afim de carregar o respectivo estado do mesmo.

O método “CriarCarrinho”, responsável por criar a instância, deve somente ser invocado uma única vez. Se você não se atentar a isso e chamá-lo sempre, uma nova instância será criada, perdendo todo o sentido da funcionalidade fornecida pelos serviços duráveis. Como também já foi falado acima, sempre quando o método retorna, o repositório é acionado para armazenar o estado atual do objeto, que eventualmente a operação alterou. Se você não invocar o método “FinalizarCompra” (responsável pela exclusão do registro do repositório), a instância sobreviverá a eventuais reinicializações do host ou da aplicação cliente, podendo iniciar uma tarefa e finalizá-la mais tarde, sem a preocupação de perder todo o trabalho realizado até aquele momento.

Toda a mensagem que é enviada do cliente para o serviço, deverá conter o ID que irá relacionar a mensagem a uma determinada instância. O desafio aqui é manter esse ID entre as chamadas, mas lembrando que elas podem ser feitas dias depois, e com isso, utilizar a memória não resolve o nosso problema. O que precisamos é persistir esse ID fisicamente, para que em eventuais reinicializações, sejamos capazes de restaurar o mesmo, e reenviar novas requisições para serem relacionadas aquela instância específica. Utilizaremos as classes já conhecidas do namespace System.IO para efetuar essa tarefa, juntamente com o serializador binário que o .NET disponibiliza (BinaryFormatter).

O segredo é como extrair o ID que foi enviado/gerado pelo serviço do lado do cliente. O proxy gerado durante a referência do serviço, herda diretamente da classe ClientBase<TChannel>. Essa classe possui uma propriedade chamada InnerChannel do tipo IClientChannel. A finalidade desta propriedade é expor a funcionalidade básica de comunicação e informações contextuais. Entre os membros fornecidas pela interface IClientChannel, temos o método GetProperty<T>. Este método genérico, recebe um objeto tipado que o método utilizará para efetuar a busca dentro da channel stack. Caso o objeto seja encontrado, ele é retornado; caso contrário, ele encaminha a busca para a próxima layer.

Utilizaremos este método para extrair uma classe que implementa a interface IContextManager. Como o próprio nome diz, ela representa o gerenciador do contexto do canal atual (contexto relacionado aos serviços duráveis), permitindo você ler ou definir um contexto, com informações específicas. Para isso, ela fornece dois simples métodos: GetContext e SetContext. O primeiro retorna uma cópia do contexto atual, representado por um dicionário de dados (onde a chave e o valor são do tipo string) com os itens que foram enviados pelo serviço. Já o segundo método, SetContext, recebe um dicionário de dados (do mesmo tipo anterior), com as informações que devem ser enviadas do cliente para o serviço. Para facilitar, criei uma classe chamada “GerenciadorDeEstado”, que tem como finalidade gerir o ID que é informado pelo serviço, e reenviado para ele. Por questões de espaço, alguns membros foram omitidos:

internal static class GerenciadorDeEstado
{
    private const string ARQUIVO_COM_CHAVE = "InstanceId.bin";

    public static void Salvar(IClientChannel channel)
    {
        IContextManager context = channel.GetProperty<IContextManager>();
        if (context != null)
            using (FileStream fs = File.Create(ARQUIVO_COM_CHAVE))
                new BinaryFormatter().Serialize(fs, context.GetContext());
    }

    public static void Carregar(IClientChannel channel)
    {
        if (JaExisteArquivo)
        {
            IContextManager context = channel.GetProperty<IContextManager>();
            if (context != null)
                using (FileStream fs = File.Open(ARQUIVO_COM_CHAVE, FileMode.Open))
                    context.SetContext((IDictionary<string, string>)new BinaryFormatter().Deserialize(fs));
        }
    }

    //Outros Membros
}

Note que no método “Salvar” invocamos o método GetContext, enquanto no método “Carregar” utilizamos o método SetContext. No nosso exemplo, esse dicionário irá conter apenas uma única entrada, chamada de “instanceId”, que é justamente o GUID gerado pela inserção do registro no SQL Server.

O que irá determinar se existe ou não uma instância em aberto para esse cliente, é a existência do arquivo com o respectivo ID. Se notarmos o código abaixo, ele irá verificar a existência do arquivo. Caso não exista, então ele invoca o método “CriarCarrinho” e salvará o contexto atual (ID); caso contrário, ele apenas carregará o contexto (ID) existente, para que as chamadas para as operações sejam encaminhadas para a instância previamente criada. Na sequência, você inclui itens dentro do carrinho e, finalmente, será perguntado se deseja ou não finalizar a compra. Se disser não, então você poderá reabrir a aplicação cliente, que os produtos adicionados ainda estarão disponíveis. Se optar por finalizar a compra, então você deve invocar a operação “FinalizarCompra”, que como já sabemos, é responsável por remover o registro da base de dados.

using (ComercioEletronioClient proxy = new ComercioEletronioClient())
{
    if (!GerenciadorDeEstado.JaExisteArquivo)
    {
        proxy.CriarCarrinho("Israel Aece");
        GerenciadorDeEstado.Salvar(proxy.InnerChannel);
    }
    else
    {
        GerenciadorDeEstado.Carregar(proxy.InnerChannel);
    }

    //Incluir Itens

    Console.WriteLine("\nDeseja finalizar a compra? (S)im ou (N)ão");
    if (Console.ReadLine() == "S")
    {
        //Finaliza a Compra e remove o registro da base de dados.
        proxy.FinalizarCompra();
        GerenciadorDeEstado.Excluir();

        Console.WriteLine("A compra foi finalizada com sucesso.");
    }
}

Durante a execução, ou depois do término da aplicação cliente sem finalizar a compra, ao analisarmos a tabela InstaceData no SQL Server, notaremos um registro adicionado, onde a coluna “id” representa o ID que foi propagado para o cliente e está armazenado no arquivo “InstanceId.bin”, e a coluna "instanceXml” com os membros privados devidamente serializados em formato Xml. Abaixo temos informação formatada que está nesta coluna:

<ServicoDeComercioEletronico xmlns="http://schemas.datacontract.org/2004/07/Host"
                             xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                             xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
                             z:Id="1"
                             z:Type="Host.ServicoDeComercioEletronico"
                             z:Assembly="Host, Version=1.0.0.0">
  <_produtos z:Id="2">
    <_items z:Id="3"
            z:Size="4">
      <ItemDaCompra z:Id="4">
        <NomeDoProduto z:Id="5">Mouse Microsoft</NomeDoProduto>
        <Quantidade>10</Quantidade>
        <Valor>100.00</Valor>
      </ItemDaCompra>
      <ItemDaCompra z:Id="6">
        <NomeDoProduto z:Id="7">Celular Motorola</NomeDoProduto>
        <Quantidade>10</Quantidade>
        <Valor>40.0</Valor>
      </ItemDaCompra>
      <ItemDaCompra i:nil="true" />
      <ItemDaCompra i:nil="true" />
    </_items>
    <_size>2</_size>
    <_version>2</_version>
  </_produtos>
  <_usuario z:Id="8">Israel Aece</_usuario>
</ServicoDeComercioEletronico>

Conclusão: Através deste artigo podemos compreender a finalidade e como implementar serviços duráveis. Notamos que não há nenhuma mudança muito radical em relação ao que conhecemos para a construção de serviços em WCF, mas há alguns detalhes importantes, que se não nos atentarmos, este recurso não funcionará como o esperado. Esse tipo de serviço permite enriquecer ainda mais a experiência com o usuário, não obrigando o mesmo a finalizar a tarefa naquele momento, podendo persistir e restaurar mais tarde.

WCFDurableServices.zip (73.37 kb)

Tags: , ,

CSD | WCF

Binary Encoding no Silverlight 3.0

by Israel Aece 18. June 2009 22:02

Até a versão 2.0 do Silverlight, o binding BasicHttpBinding apenas codificava a mensagem em formato texto, enviando-a através de HTTP. Como já sabemos, esse binding com esta codificação pode ser facilmente interoperável com serviços baseados no protocolo SOAP 1.1, como é o caso dos ASP.NET Web Services (ASMX). Esse tipo de codificação também pode ser facilmente interceptado por qualquer ferramenta de monitoramento de requisições HTTP, como é o caso do Fiddler.

Uma inovação que está presente a partir da versão 3.0 do Silverlight, é o suporte a codificação binária do envelope SOAP que é transmitido entre o serviço e o cliente, e vice-versa. Apesar de, em alguns casos, diminuir o tamanho da mensagem trafegada, a grande finalidade deste tipo de codificação é ter uma velocidade maior entre a transferência das informações. Com esse benefício, esse tipo de codificação passa a ser o padrão quando criar um projeto baseando-se na template Silverlight-enabled WCF Service.

Para que isso funcione, temos um novo binding element chamado BinaryMessageEncodingBindingElement. Com a criação de um binding customizado, já podemos fazer a utilização do mesmo e, consequentemente, de seus benefícios. O código abaixo ilustra a sua utilização, configurando o binding através do arquivo de configuração:

<bindings>
   <customBinding>
      <binding name="binaryHttpBinding">
         <binaryMessageEncoding />
         <httpTransport />
      </binding>
   </customBinding>
</bindings>

Apenas fique atento para utilizar o mesmo codificador dos dois lados. Caso isso não aconteça, não será possível fazer a comunicação. Para finalizar, é importante dizer que ao utilizar esse codificador, não iremos mais conseguir monitorar as requisições e analisar o seu conteúdo. Enquanto estiver em ambiente de desenvolvimento, talvez seja melhor manter a codificação baseada em formato texto, para conseguir depurar facilmente eventuais problemas que venham a acontecer.

Tags: , ,

WCF

Polling Duplex

by Israel Aece 16. June 2009 18:09

Como já falei há algum tempo, um dos tipos de mensagens suportados pelo WCF é o padrão Duplex. A finalidade desta técnica é permitir uma comunicação bidirecional, ou seja, o cliente invocar método de um serviço, bem como um serviço invocar um método do cliente. Esse tipo de comunicação permite, na maioria das vezes, um determinado serviço notificar o cliente de que algum evento ocorreu, dando a ele, uma chance de conseguir interagir com um cliente específico ou até mesmo vários clientes, através de um sistema publicador-assinante.

Aplicações Silverlight também podem precisar deste tipo de recurso, onde um serviço irá se comunicar com o cliente (que está rodando no navegador) para notificar sobre algum resultado, ou até mesmo para reportar a execução de alguma tarefa mais complexa. Felizmente, a partir da versão 2.0 do Silverlight, ele prove esta funcionalidade, e a partir a versão mais recente, Silverlight 3.0 (ainda em Beta), tornou esse trabalho mais simples. Para quem já está habituado a trabalhar com WCF, utilizar a API do mesmo em uma aplicação Silverlight (independentemente do tipo de comunicação) traz algumas limitações, quais falaremos neste artigo, principalmente dando mais foco no formato Duplex.

Ao utilizar um binding que suporte o tipo de comunicação Duplex, ele criará internamente um endpoint para que o serviço consiga invocar o callback do lado do cliente. Como a aplicação cliente está rodando dentro do navegador, ela não poderá criar um "listener" para receber os eventuais callbacks que o serviço possa disparar, impedindo a utilização dos bindings tradicionais para este cenário. Para resolver essa deficiência, o Silverlight utiliza o recurso conhecido como "poll". Com esta técnica, o cliente ficará interrogando o serviço para determinar se há alguma mensagem que deve ser recebida por ele.

Antes de falarmos sobre as classes que dão suporte a esta técnica, vamos analisar os assemblies necessários. O Silverlight 2.0 e 3.0 traz dois assemblies com o nome de System.ServiceModel.PollingDuplex.dll, que estão em diretórios diferentes. Um deles contido no diretório %ProgramFiles%\Microsoft SDKs\Silverlight\VERSAO\Libraries\Server e outro no diretório %ProgramFiles%\Microsoft SDKs\Silverlight\VERSAO\Libraries\Client. Como podemos perceber, um dos assemblies será utilizado pelo lado do serviço, onde trará os tipos necessários para a criação de serviços Duplex, enquanto o segundo deverá ser utilizado por aplicações cliente, para o consumo de serviços Duplex.

O binding responsável por fornecer toda a infraestrutura necessária para a comunicação Duplex no Silverlight é chamado de PollingDuplexHttpBinding. Este binding herda diretamente da classe BasicHttpBinding, e customiza o mesmo para suportar o "poll" através do protocolo HTTP. Este binding utiliza os protocolos abertos Net Duplex e WS-Make Connection (WS-MC). O protocolo Net Duplex é utilizado para estabelecer uma sessão entre o cliente e o serviço, para que seja possível correlacionar as mensagens que serão trocadas. Já o protocolo WS-MC, faz o trabalho necessário para a criação de um canal dentro da sessão previamente criada, e que será utilizado pelo serviço para criar a mensagem que deve ser entregue ao cliente ou para o cliente interrogar o serviço.

Em termos de implementação do serviço nada muda, ou seja, a criação de uma Interface que representará o callback continuará sendo necessária. Além disso, a forma de invocarmos o callback continua sendo através do método GetCallbackChannel<T> da classe OperationContext. Para entender mais detalhes de como implementar um serviço que suporte callbacks, consulte este artigo. Se você já sabe construir serviços que utilizam callbacks, você notará que não há nenhuma diferença na criação destes mesmos serviços para Silverlight.

Depois de criado o contrato do serviço, de callback e da classe que representará o serviço, precisamos configurar o host para expor o serviço através do novo binding que mencionamos acima. Infelizmente, até a versão atual (Silverlight 3.0 Beta), ainda não existe suporte para a configuração deste tipo de serviços através de arquivos de configuração (como o Web.config). Na maioria das vezes o serviço estará hospedado no IIS (mas poderá utilizar outros hosts) e a deficiência na configuração declarativa, nos obrigará a customizar o ServiceHost e o ServiceHostFactoryBase (responsável por criar as instâncias da classe ServiceHost). Aqui não há diferenças em relação a forma que já conhecemos para a configuração de um endpoint, ou seja, continuamos necessitando o endereço, binding e contrato. A única atenção que temos que ter é especificar um binding que suporte este tipo de comunicação.

Utilizaremos o IIS como hosting (via arquivo *.svc), e como mencionado acima, será necessário criarmos uma especialização da classe ServiceHost e também da ServiceHostFactoryBase. Isso é mostrado através do código abaixo, e note que dentro do método InitializeRuntime adicionamos os endpoints necessários (através do método AddServiceEndpoint), incluindo um com um binding customizado, e entre os elementos que irão compor ele, temos a instância da classe PollingDuplexBindingElement, que habilita a comunicação Duplex entre o serviço e o cliente.

public class PollingDuplexServiceHostFactory : ServiceHostFactoryBase
{
    public override ServiceHostBase CreateServiceHost(
        string constructorString, Uri[] baseAddresses)
    {
        return new PollingDuplexServiceHost(baseAddresses);
    }

    private class PollingDuplexServiceHost : ServiceHost
    {
        public PollingDuplexServiceHost(params Uri[] addresses)
            : base(typeof(Servico), addresses)
        {
            this.Description.Behaviors.Add(
                new ServiceMetadataBehavior() { HttpGetEnabled = true });
        }

        protected override void InitializeRuntime()
        {
            this.AddServiceEndpoint(
                typeof(IContrato),
                new CustomBinding(
                    new PollingDuplexBindingElement(),
                    new TextMessageEncodingBindingElement(),
                    new HttpTransportBindingElement()),
                "");

            this.AddServiceEndpoint(
                typeof(IMetadataExchange),
                MetadataExchangeBindings.CreateMexHttpBinding(),
                "mex");

            base.InitializeRuntime();
        }
    }
}

É importante dizer ao runtime do WCF que criamos uma factory própria, e para vinculá-la ao serviço, utilizamos a diretiva @ServiceHost no arquivo *.svc. Essa diretiva possui um atributo chamado Factory, onde podemos especificar o tipo de uma classe que implemente a classe base ServiceHostFactoryBase.

<%@ ServiceHost
    Language="C#"
    Debug="true"
    Factory="Web.UI.Host.Network.PollingDuplexServiceHostFactory" %>

Com isso finalizamos tudo o que é necessário por parte do serviço. Com o endereço do mesmo em mãos, devemos referenciá-lo no cliente através da IDE do Visual Studio .NET ou através do utilitário de linha de comando chamado slsvcutil.exe. Ao fazer isso, uma classe que corresponderá ao proxy será criada mas o arquivo de configuração não terá nenhuma informação. Isso se deve ao fato de que a versão atual (Silverlight 3.0 Beta) ainda não traz suporte a este tipo de funcionalidade. Um outro detalhe importante que você notará ao explorar a classe que representa o proxy, é que não haverá a versão síncrona do método, pois o Silverlight não possui isso nativamente. Assim, todos os métodos expostos pelo contrato, o cliente somente terá a versão assíncrona dos mesmos, que é composta por um par de métodos, BeginOperacao e EndOperacao. Apesar destes dois métodos existirem, eles são privados e não estão acessíveis além do proxy, que disponibiliza o modelo baseado em eventos para o consumo. Para maiores detalhes de como o processo assíncrono funciona, consulte este artigo.

Da mesma forma que utilizamos um binding customizado do lado do serviço, temos que fazer o mesmo do lado do cliente. Temos apenas que tomar os devidos cuidados para utilizar o assembly System.ServiceModel.PollingDuplex.dll que está no diretório %ProgramFiles%\Microsoft SDKs\Silverlight\VERSAO\Libraries\Client. Veja que estamos montando o binding customizado com os mesmos elementos (e na mesma ordem) que utilizamos na configuração do serviço, apenas nos atentando que a classe PollingDuplexBindingElement está no assembly que faz parte do cliente. O código abaixo mostra como devemos proceder para utilizar o proxy que foi gerado:

private Servico.ContratoClient _proxy;

public MainPage()
{
    InitializeComponent();

    EndpointAddress ea = new EndpointAddress("http://127.0.0.1.:51116/Servico.svc");
    CustomBinding cb =
        new CustomBinding(
            new PollingDuplexBindingElement(),
            new TextMessageEncodingBindingElement(),
            new HttpTransportBindingElement());

    _proxy = new ContratoClient(cb, ea);
    _proxy.OnCallbackReceived += (o, e) => MessageBox.Show(e.msg);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    _proxy.PingAsync("Teste");
}

Antes de invocar o método Ping, é necessário nos vincularmos ao evento OnCallbackReceived, que será disparado quando a operação finalizar. Note que as operações são sufixadas com a palavra "Async", e haverá sempre um evento que corresponde ao término da execução da respectiva operação.

Limites e Timeouts

As classes que vimos acima possuem algumas propriedades que podemos utilizar para ter um maior controle em alguns timeouts, e também na quantidade máxima de sessões e de mensagens que podem ser criadas. Para iniciar vamos falar sobre as propriedades fornecidas pela classe PollingDuplexBindingElement do serviço. As propriedades que gerenciam as quantidades máximas são: MaxPendingSessions e MaxPendingMessagesPerSession. A primeira determina a quantidade máxima de sessões suportadas, enquanto a segunda determina a quantidade máxima de mensagens para cada sessão criada. Com relação aos timeouts, temos as propriedades InactivityTimeout e ServerPollTimeout. A primeira propriedade determina um intervalo de tempo que o serviço pode passar sem nenhuma atividade antes do canal se tornar inválido. Já a segunda propriedade, especifica o tempo em que o serviço irá monitorar o cliente anter de retornar.

Do lado do cliente apenas temos duas propriedades, também fornecidas pela classe PollingDuplexBindingElement: InactivityTimeout e ClientPollTimeout. A primeira propriedade tem a mesma finalidade da propriedade InactivityTimeout do lado do serviço, enquanto a propriedade ClientPollTimeout determina o intervalo de tempo que o cliente ficará interrogando o serviço, para verificar a existência de novas mensagens. Todas essas propriedades devem ser configuradas antes da conexão ser estabelecida entre o cliente e o serviço.

Conclusão: Felizmente o Silverlight já traz suporte à comunicação Duplex. Como vimos no decorrer deste artigo, o único binding que possibilita isso em aplicações Silverlight é o PollingDuplexHttpBinding. Como estamos atualmente na versão Beta, ainda há algumas deficiências, como é o caso da ausência do suporte a configuração declarativa deste binding (via arquivo Web.config), que torna o processo de desenvolvimento um pouco mais trabalhoso e propício a erros. De qualquer forma, conseguimos atingir o nosso objetivo, que é proporcionar uma boa interatividade, já que podemos inicar uma tarefa e o serviço nos notificar quando a mesma finalizar.

SLComWCF.zip (92.46 kb)

Tags: , ,

WCF

Extraindo informações a partir do WSDL

by Israel Aece 14. June 2009 12:52

O WCF fornece um conjunto de classes que nos permite extrair informações a respeito de um determinado serviço. Como já sabemos, o serviço pode ou não expor as informações (binding, contrato, endereço) através de um documento, que é conhecido como WSDL. Ao extrair essas informações, podemos ter acesso aos contratos, o endereço onde o serviço está publicado e também ao respectivo binding, que define como será o canal de comunicação. Com essas informações, podemos tornar o nosso código mais dinâmico, e menos dependente de uma configuração que, muitas vezes, está em hard-code.

A primeira classe que nos ajudará nisso é a MetadataExchangeClient. Com o endereço até o documento WSDL, ela é capaz de fazer o download do mesmo através do protocolo WS-MetadataExchange. Como podemos notar no exemplo abaixo, ainda em seu construtor especificamos se o modo de requisição será através de uma requisição WS-Transfer GET ou uma requisição HTTP GET. Para capturar o resultado, utilizamos o método GetMetadata, que retorna uma instância da classe MetadataSet, contendo uma coleção de seções (representadas pela classe MetadataSection), que serão utilizadas a seguir, pela classe WsdlImporter.

A classe WsdlImporter converte as informações expostas pelo WSDL, em classes que representam os endpoints, e disponibiliza vários métodos, onde podemos extrair esses endpoints individualmente, ou retornar todos os endpoints encontrados no serviço. O método ImportAllEndpoints retorna uma coleção, onde cada elemento é representado pela classe ServiceEndpoint, que por sua vez, descreve através de várias propriedades, todas as informações que estão relacionadas à um determinado endpoint, ou seja, o famoso ABC.

MetadataExchangeClient proxy =
    new MetadataExchangeClient(
        new Uri("net.tcp://localhost:9292/mex"),
        MetadataExchangeClientMode.MetadataExchange);

ServiceEndpointCollection endpoints = new WsdlImporter(proxy.GetMetadata()).ImportAllEndpoints();

foreach (ServiceEndpoint se in endpoints)
{
    Console.WriteLine("Address: {0}", se.Address);
    Console.WriteLine("Binding: {0}", se.Binding);
    Console.WriteLine("Contract: {0}", se.Contract.Name);

    foreach (OperationDescription od in se.Contract.Operations)
        Console.WriteLine("\tOperation: {0} - IsOneWay: {1}", od.Name, od.IsOneWay);
}

Todas as classes utilizadas aqui estão debaixo do namespace System.ServiceModel.Description. Além disso, é importante dizer que isso não substitui o protocolo WS-Discovery, pois conhecemos qual é o endereço até o documento WSDL.

Tags: , ,

WCF

Mudança na API de Globalização

by Israel Aece 9. June 2009 19:19

Uma novidade que estará presente no .NET Framework 4.0 é a mudança no mecanismo de recuperação das culturas. Até a versão anterior, utilizamos o método estático GetCultures da classe CultureInfo. Esse método retorna um array de objetos do tipo CultureInfo, onde cada elemento representa uma cultura específica. Todas as informações são retornadas a partir de um repositório que está contido no .NET Framework.

A partir do .NET Framework 4.0, a Microsoft efetuou uma mudança, onde não haverá mais essa “tabela interna”, que era responsável por armazenar essas informações. Quando você estiver rodando uma aplicação em cima do Windows 7, as culturas serão extraídas diretamente do sistema operacional. Já quando rodar em versões anteriores ao Windows 7, as informações carregadas serão as mesmas do repositório interno do .NET Framework.

Com essa nova mudança, as opções WindowsOnlyCultures e FrameworkCultures do enumerador CultureTypes estão obsoletas. Utilizar o WindowsOnlyCultures não retornará nenhuma informação, enquanto o FrameworkCultures retornará os mesmos resultados do .NET Framework 2.0.

Tags:

.NET Framework

TimeoutException em serviços WCF

by Israel Aece 8. June 2009 15:58

Uma das exceções mais comuns quando se utiliza WCF é a TimeoutException, com a seguinte mensagem:

“The request channel timed out while waiting for a reply after 00:01:00. Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. The time allotted to this operation may have been a portion of a longer timeout.”

Muitas vezes, quando se consome um serviço WCF, muitas pessoas esquecem de fechar o proxy, que é o responsável pela comunicação entre o cliente e o serviço. Por padrão, o modo de gerenciamento de instâncias do serviço é o PerSession. Isso quer dizer que haverá uma instância do serviço para atender cada instância do proxy que é criada pelas aplicações clientes.

O problema que mencionei acima começará acontecer quando a quantidade máxima de conexões for atingida. Por mais que você aumente o throttle, a limitação é imposta pelo sistema operacional. Windows Vista Home e Premium tem um limite de 3 conexões; já o Vista Ultimate e Professional (e acredito que o XP Professional também), estão limitados à 10 conexões; e, por fim, as versões de servidor, possuem um número ilimitado. Somente em ambientes de servidores que o throttle poderá, de forma mais clara, ter uma maior interferência/utilidade.

Ao manter o proxy aberto, fará com que esse “contador” nunca decremente, e facilmente chegará ao limite estabelecido pelo sistema operacional ou pelo throttle, e além desse limite atingido, há sempre recursos que estão sendo presos de forma desnecessária. Sendo assim, sempre feche explicitamente o proxy através do método Close, ou se preferir, basta envolvê-lo em um bloco using (com os devidos cuidados), que implicitamente o método Dispose será chamado, que internamente invocará o método Close.

Tags: ,

WCF

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host