WIF – Tracing

Há algum tempo eu comentei aqui em como habilitar e manipular o tracing em serviços WCF. Utilizando essa mesma estrutura, a Microsoft também incorporou este recurso no WIF, para possibilitar o diagnóstico de eventuais problemas ou comportamentos estranhos que possam acontecer durante o desenvolvimento ou mesmo durante o ambiente de produção.

Assim como serviços WCF, o WIF também é baseado em mensagens sendo trafegadas entre as partes envolvidas, que determinam as requisições de autenticação, a resposta informando se o usuário é válido ou não, processo de logout, etc. O tracing do WIF também é bastante completo, e nos permite visualizar as mensagens de forma gráfica, utilizando o utilitário Service Trace Viewer.

O uso deste tracing está disponível tanto para o autenticador (STS) quanto pela aplicação cliente (RP), e isso se dá através de trace sources, que foram criados pela Microsoft durante a construção do WIF. Esse trace source é chamado de Microsoft.IdentityModel, e podemos utilizá-lo em ambos os lados (STS e RP), para capturar os procedimentos que estão acontecendo nas duas partes. Abaixo temos o código para habilitar o tracing do lado do autenticador, e se quiser utilizar pelo cliente (RP), tudo o que precisa ser feito é colocar esta mesma configuração, alterando somente o nome do arquivo para que um não sobrescreva o do outro.

<system.diagnostics>
  <sources>
    <source name=”Microsoft.IdentityModel” switchValue=”All”>
      <listeners>
        <add name=”xml”
             type=”System.Diagnostics.XmlWriterTraceListener”
             initializeData=”C:TempAutenticador.xml” />
      </listeners>
    </source>
  </sources>
  <trace autoflush=”true” />
</system.diagnostics>

Dentro do arquivo gerado, os logs estarão distribuídos entre várias categorias, onde cada uma delas representa um estágio ou processo diferente. Essas categorias estão listadas abaixo, com uma descrição superficial sobre cada uma delas:

  • ChunkedCookieHandler: Nome, tamanho, domínio e data de expieração do cookie gerado para o usuário.
  • ClaimsPrincipal: Informações sobre a IPrincipal que foi criada, contendo o nome e o conjunto de claims.
  • DeflateCookie: Basicamente traz informações sobre o processo de compressão do cookie, incluindo o tamanho original e o comprimido.
  • HashTrace: Informações sobre tamanho e valor das assinaturas digitais do conteúdo Xml.
  • PassiveMessage: Contém um conjunto de informações permitinentes ao ambiente passivo, que são representadas pela requisição HTTP (wa, wresult e wctx).
  • Reference: Cataloga informações sobre o processo de cálculo dos digests (hash).
  • Token: Como o próprio nome diz, temos aqui informações sobre o token, trazendo informações a cada token específico (SessionTokens, Saml11 e Saml2).
  • WsFedMessage: Armazena informações que chegam até a aplicação consumidora, que cataloga cada estágio do processamento do token.
  • Exceptions: Problemas que ocorrem durante o processo de autenticação, incluindo toda a stack até o erro.
  • AppDomain Unloading: Informações sobre o descarregamento do AppDomain da aplicação.

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);
}