Tratando erros com jQuery e WCF

Neste artigo eu mostrei como construir serviços WCF para serem consumidos através do jQuery. Nele falamos sobre os cuidados que devemos ter para criar e expor serviços WCF, mas não chegamos a falar sobre alguns detalhes, não menos importante, como é o caso do tratamento de erros, que é algo tão comum em qualquer tipo de aplicação.

Ao executar alguma operação, uma exceção pode ser disparada por algum motivo. Como já falamos, ao utilizar a biblioteca do jQuery para invocar algum serviço, podemos através de um callback, especificarmos um código para ser disparado quando algum problema ocorrer do lado do serviço. Para configurar isso, podemos utilizar o parâmetro error da função $.ajax. O código abaixo ilustra como podemos configurar o parâmetro error, exibindo uma mensagem informando ao usuário que algum problema ocorreu:

function RecuperarUsuario() {
    $.ajax(
    {
        type: “POST”,
        url: “http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario”,
        contentType: “application/json”,
        data: ‘{ “nome”: “”, “email”: “ia@israelaece.com” }’, //Nome vazio causa o erro
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                alert(‘Algum Problema Ocorreu!’);
            }
        });
    }

Esse tipo de código funciona normalmente, até que você precise capturar a mensagem exata do erro que ocorreu. Quando executamos uma operação que está disparando alguma exceção, o WCF acaba retornando a seguinte mensagem: “Request server encountered an error processing the request. See server logs for more details”. O problema é que o erro não será retornado em um formato que é facilmente entendido/preferido pelo jQuery, que é o JSON.

Ao configurar o contrato para expor via AJAX, podemos definir através do atributo WebInvokeAttribute, que a requisição (RequestFormat) e a resposta (ResponseFormat) sejam formatadas em JSON, mas isso não inclui eventuais exceções que sejam disparadas pelas respectivas operações, mesmo que esse problema esteja definido como um contrato de fault (FaultContractAttribute).

Para tentar resolver isso, podemos recorrer a alguns pontos de estensibilidade do WCF, para conseguirmos interceptar o erro que ocorreu. Ao interceptar, deveremos transformar a mensagem em algum objeto que deverá ser entendido pelo jQuery, entregando para o cliente todas as informações necessárias para que o mesmo consiga tratar o erro ocorrido.

Para descrever o problema, vamos criar uma classe que possui características que detalham o erro ocorrido. Essa classe será chamada de GenericErrorDescription, que possuirá apenas uma única propriedade (para manter a simplicidade), chamada de Message, do tipo string. Como a finalidade será transformar a exceção em formato JSON para facilitar o consumo pelo jQuery, criaremos uma classe chamada de JsonErrorHandler, e nela implementaremos a interface IErrorHandler, que é uma espécie de interceptador (mais detalhes neste artigo). O principal método fornecido por esta interface, é chamado de ProvideFault, que passa como parâmetro a instância da exceção que ocorreu e uma instância da classe Message, que corresponde a mensagem que será devolvida para o cliente.

Na implementação deste método, utilizaremos o método estático CreateMessage da classe Message, que criará a mensagem de retorno, utilizando o serializador JSON para que a mesma será formatada neste padrão, e além disso, esse serializador deverá serializar a instância da classe que representa o erro, que no nosso exemplo é a GenericErrorDescription. Na sequência configuramos a instância da classe HttpResponseMessageProperty, para customizarmos a mensagem, informando que o tipo a ser retornado será application/json, para que o jQuery consiga, facilmente, entender e interpretar esse conteúdo. Note também que mantemos o StatusCode do HTTP como 500, ou seja, um erro interno, e como sua descrição, definimos a mesma mensagem gerada pela exceção disparada pela operação.

A classe JsonErrorHandler ainda implementa a interface IEndpointBehavior, que nos permite acoplar a instância desta classe em um endpoint específico, do lado do serviço (dispatcher), na coleção de tratadores de erros. O código abaixo ilustra a classe JsonErrorHandler já devidamente implementada:

public class JsonErrorHandler : IEndpointBehavior, IErrorHandler
{
    public bool HandleError(Exception error)
    {
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        GenericErrorDescription desc = 
            new GenericErrorDescription() { Message = error.Message };

        fault = Message.CreateMessage(version, “”, desc, 
            new DataContractJsonSerializer(typeof(GenericErrorDescription)));
        fault.Properties.Add(WebBodyFormatMessageProperty.Name, 
            new WebBodyFormatMessageProperty(WebContentFormat.Json));

        var msgp = new HttpResponseMessageProperty();
        msgp.Headers[HttpResponseHeader.ContentType] = “application/json”;
        msgp.StatusCode = HttpStatusCode.InternalServerError;
        msgp.StatusDescription = desc.Message;

        fault.Properties.Add(HttpResponseMessageProperty.Name, msgp);
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bp) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime cr) { }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher ed)
    {
        ed.ChannelDispatcher.ErrorHandlers.Add(this);
    }

    public void Validate(ServiceEndpoint endpoint) { }
}

Só que essa classe por si só não funciona. Precisamos efetivamente acoplar ao pipeline do WCF, e para isso, podemos criar um extension element, que nos dará a oportunidade de efetuar essa configuração de modo declarativo, ou seja, através do arquivo Web.config. Basicamente, essa classe será responsável por criar instâncias da classe que criamos acima, ou seja, do tratador de erros:

public class JsonErrorElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(JsonErrorHandler);
        }
    }

    protected override object CreateBehavior()
    {
        return new JsonErrorHandler();
    }
}

Depois das classes devidamente criadas, tudo o que precisamos fazer agora é configurarmos o arquivo Web.config do serviço, acoplando o extension element ao endpoint de acesso ao serviço. A configuração final do Web.config deve ficar da seguinte forma:

<system.serviceModel>
  <services>
    <service name=”ServicoDeUsuarios” behaviorConfiguration=”config“>
      <endpoint
        binding=”webHttpBinding”
        contract=”IUsuarios”
        behaviorConfiguration=”edpConfig” />
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name=”config“>
        <serviceDebug includeExceptionDetailInFaults=”false” />
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name=”edpConfig“>
        <jsonErrorHandler />
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
      <add
        name=”jsonErrorHandler
        type=”JsonErrorElement, WCFExtensions, Version=1.0.0.0, …” />
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

Já do lado do cliente, as mudanças são bastante ligeiras. Tudo o que precisamos fazer é uma pequena mudança no código que é disparado quando o erro ocorre. Uma vez que o interceptador de erros está acoplado do WCF, a mensagem com o erro de retorno será formatada em JSON, e se analisarmos o retorno, veremos o seguinte resultado:

{“Message”: “Informe o nome do usuariou000du000aParameter name: nome”}

Como a mensagem de retorno está em formato JSON, utilizaremos o método parse da classe JSON, exposta pelo JSON2. Esse método é responsável por transformar o conteúdo JSON em um objeto, respeitando as mesmas propriedades serializadas pela classe GenericErrorDescription. Com isso, o nosso código de consumo ao serviço será alterado para:

function RecuperarUsuario() {
    $.ajax(
    {
        type: “POST”,
        url: “http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario&#8221;,
        contentType: “application/json”,
        data: ‘{ “nome”: “”, “email”: “ia@israelaece.com” }’, //Nome vazio causa o erro
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                var erro = JSON.parse(xhr.responseText);
                alert(erro.Message);
            }
        });
    }

Se quiser levar mais detalhes do erro, você ainda tem algumas outras alternativas, mas não tão interessantes como essa. Você poderia optar por definir o atributo includeExceptionDetailInFaults do elemento serviceDebug para True, mas isso iria expor mais informações do que realmente deveria, como por exemplo a Stack Trace, algo que não deveria ultrapassar o serviço. Além dela, ainda poderia envolver toda a operação em um bloco try/catch, e caso algum problema ocorra, utilizamos a classe WebOperationContext para customizar o StatusCode e o ContentType através dela, mas isso torna a classe que representa o serviço dependente do protocolo HTTP, o que não é uma boa opção.

Conclusão: Graças a grande flexibilidade fornecida pelo WCF, podemos contornar alguma de suas “limitações” de uma forma bastante elegante, sem precisar misturar em minhas operações, códigos que estão relacionados puramente à infraestrutura.

AppFabric para desenvolvedores WCF

Temos dentro do sistema operacional uma ferramenta administrativa conhecida como Component Services. É através desta ferramenta que controlamos os componentes que utilizam a tecnologia COM+, permitindo visualizar aqueles componentes que estão hospedados em um determinado servidor, configurar suas características, entre várias outras funcionalidades.

Em muitas situações, o Component Services é encarado como sendo o servidor de aplicações, pois é nele que estarão todos os componentes que são utilizados pela companhia. Utilizamos o Component Services para centralizar tais componentes, fazendo uso de suas funcionalidades, tais como: suporte à transações, pool de objetos, sincronização, etc. As aplicações clientes, sejam elas Windows ou Web, recorrem a estes componentes hospedados neste servidor para executar as tarefas.

Com o surgimento do WCF, o COM+ está perdendo espaço, e a partir de agora, quando falamos em sistemas distribuídos, utilizamos o WCF como tecnologia para suportar isso. Uma das principais características do WCF, é a independência de hosting, ou seja, podemos utilizar qualquer tipo de aplicação para hospedar um serviço WCF.

No ambiente do WCF, um dos hosts que mais se popularizou foi o IIS, que até então, possibilitava apenas a publicação do serviço em cima do protocolo HTTP/HTTPS. A partir da versão 7.0 do IIS, ele já traz suporte à todos os protocolos, incluindo o TCP (WAS). Ao incorporar essa funcionalidade, isso o tornou muito mais do que um simples servidor para sites da web, dando a ele características de um servidor de aplicação, onde podemos hospedar componentes, que através do WCF, vamos publicar para os consumidores, estejam eles dentro do fora dos limites da empresa.

O IIS ganhou todas essas capacidades, mas as ferramentas que ajudam a monitorar esses serviços não evoluíram na mesma proporção. Por mais que eu consiga hospedar meus componentes no IIS, fazendo uso dos diversos protocolos, não há como, por exemplo, saber quais são os serviços WCF que eu tenho hospedado naquele servidor; quais são os endpoints publicados para um determinado serviço, entre várias outras informações úteis tanto para os desenvolvedores quanto para aqueles que administram o servidor.

Pensando nisso, a Microsoft criou uma ferramenta chamada de Windows Server AppFabric. Quando instalada, essa ferramenta adiciona ao sistema operacional, mais precisamente dentro do IIS, um conjunto de funcionalidades que permitem diagnosticar e gerenciar os serviços que rodam dentro de um determinado servidor. Além disso, o AppFabric ainda fornece recursos adicionais, como uma estrutura para caching distribuído, que pode ser utilizado para qualquer tipo de aplicação.

Entre os vários recursos oferecidos pelo AppFabric, temos a possibilidade de gerenciar e monitorar serviços WCF. Entre as funcionalidades exclusivas ao WCF, temos a capacidade de visualizar quais são os serviços que rodam naquele servidor, quais são os endpoints que cada serviço publica (incluindo aqueles de metadados). As imagens abaixo ilustram essas funcionalidades:

Ainda há uma opção chamada de Dashboard, que exibe uma visão sintética de quantas chamadas para serviços WCF ocorreram, quantas falharam, as exceções que foram disparadas, etc. Como trata-se de uma visão agrupada, essa tela possui vários links, que ao clicar, levará para uma segunda tela, com os filtros relacionados àquela opção clicada, fornecendo a visão analítica, com informações mais completas a respeito daquele serviço. A imagem abaixo ilustra este dashboard:

Ao instalar o AppFabric, um assistente é inicializado para configurá-lo. Basicamente, ele solicitará um local para armazenar as informações geradas por ele, para que mais tarde, possamos visualizá-las. Essas configurações exigem que você defina, através de uma espécie de provider, um banco de de dados SQL Server. É importante que você crie um banco exclusivo para isso, já que este assistente adicionará vários objetos dentro dele (tabelas, views, jobs, etc.), que são de uso exclusivo, e misturar com a base de dados da aplicação não é uma boa prática.

Além dessas informações macros, ainda temos um detalhamento delas. Quando utilizamos o tracing, uma série de informações são geradas, catalogando todos os estágios por onde a requisição está passando. No AppFabric temos uma seção chamada de Tracked Events, que nos permite visualizar justamente esse tipo de informação. Se analisarmos a imagem abaixo, podemos perceber os diversos eventos (estágios) do processamento, que ocorreram do lado do servidor, e também é possível visualizar a operação que foi efetivamente disparada.

Algumas poucas configurações do serviço estão disponíveis a partir do AppFabric, como é o caso de algumas cotas e throttling, mas como sabemos, as configurações do WCF são extensas e complexas, e neste caso, o uso da ferramenta WCF Service Configuration Editor performa melhor.

Para finalizar, um outro recurso bastante interessante, que também é fornecido com o AppFabric, é a possibilidade de exportar todas as configurações de um servidor, incluindo os serviços que nele rodam. Esse recurso utiliza em seus bastidores, uma ferramenta conhecida como Web Deployment Tool, e que permite configurar um outro servidor com as mesmas características deste servidor de origem, sem a necessidade de lembrar cada uma das configurações que você já realizou anteriormente.

Conclusão: O AppFabric não visa apenas serviços WCF. Como o Windows Workflow (WF) tem uma forte integração com o WCF, é possível gerenciarmos esses serviços a partir do AppFabric, com recursos para interagir com workflows de longa duração. Além disso, ainda há recursos extras que vão além do escopo definido para este artigo, mas que não são menos importantes. Depois de ter criado toda a infraestrutura necessária para suportar serviços WCF dentro do IIS, a Microsoft tem destinado grande parte de seus esforços no desenvolvimento desse tipo de ferramenta, que ajudam a monitorar e diagnosticar problemas, algo que muitas vezes são realizadas por outras pessoas, e que não são necessariamente desenvolvedores.

multipleSiteBindingsEnabled

Há algum tempo eu comentei aqui sobre uma dificuldade que existe quando hospedamos serviços WCF no IIS. Neste mesmo post, eu mostrei a solução que foi incorporada na versão 3.5 do WCF.

Para facilitar isso, a partir da versão 4.0 do WCF temos uma opção chamada multipleSiteBindingsEnabled, disponível através do elemento serviceHostingEnvironment, que quando definido como True, permite que o serviço fique disponível a partir dos múltiplos endereços de acesso configurados no IIS. Abaixo temos o arquivo de configuração com esta opção habilitada:

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

Consumindo serviços WCF com jQuery

O jQuery é uma biblioteca com várias funcionalidades que tornam o desenvolvimento de código JavaScript muito mais simples. Tarefas que demandavam várias linhas de código para chegar a um determinado resultado, atualmente com o jQuery o código necessário para atingir esse mesmo objetivo é muito menos complexo. Inclusive a Microsoft reconheceu a facilidade e a larga adoção pelos desenvolvedores, e decidiu incorporá-lo às templates de projeto do ASP.NET.

Entre as mais variadas funcionalidades que o jQuery oferece, uma delas é o suporte ao AJAX, que nos permite invocar métodos que estão do lado do servidor. Mas além disso, uma das possibilidades que temos é a requisição para serviços, incluindo serviços construídos com WCF. A finalidade deste artigo é mostrar como proceder para criar serviços WCF que possam ser consumidos pelo AJAX, e depois disso, vamos analisar o que o jQuery oferece para o consumo do mesmo.

Primeiramente vamos nos atentar a construção de um serviço para que possamos consumí-lo através do AJAX/jQuery. O que precisamos nos atentar é que para expor esse serviço para clientes AJAX, é necessário decorarmos as operações não somente com o atributo OperationContractAttribute, mas também com os atributos WebGetAttribute ou WebInvokeAttribute, que são disponibilizados a partir da versão 3.5 do .NET Framework, e que estão contidos debaixo do namespace System.ServiceModel.Web (assembly System.ServiceModel.Web.dll).

Como exemplo, o contrato irá manipular instância da classe Usuario, que nada mais é do que um objeto simples, que possui três propriedades autoexplicativas: Codigo, Nome e Email. O contrato fornecerá três operações: RecuperarUsuario, que retorna a instância da classe Usuario devidamente configurada com os parâmetros de entrada, uma outra operação chamada RecuperarUsuarios que retorna uma coleção de usuários e, finalmente, a operação Adicionar, que dado a instância do usuário, irá adicioná-lo em algum repositório. Abaixo podemos visualizar a interface que servirá como o contrato para o serviço:

[ServiceContract]
public interface IUsuarios
{
    [OperationContract]
    [WebInvoke(
        BodyStyle = WebMessageBodyStyle.Wrapped,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    Usuario RecuperarUsuario(string nome, string email);

    [OperationContract]
    [WebGet(
        BodyStyle = WebMessageBodyStyle.Bare,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    Usuario[] RecuperarUsuarios();

    [OperationContract]
    [WebInvoke(
        BodyStyle = WebMessageBodyStyle.Bare,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    void Adicionar(Usuario usuario);
}

Note que as operações que serão expostas também determinam algumas configurações específicas para quando forem consumidas a partir do AJAX. É importante perceber também que em todas elas, estamos optando por trabalhar com JSON ao invés do XML para formular a mensagem de requisição e de resposta, já que o JSON é mais simples de se trabalhar com Javascript. A propriedade BodyStyle controla como o corpo da requisição/resposta deve ser formatado, determinando se os parâmetros ou o resultado dos métodos devem ou não estar dentro de elementos adicionais. Para maiores detalhes sobre esses parâmetros, consulte este artigo.

Ao interceptar a requisição/resposta que é feita para a primeira operação, então teremos as informações em JSON sendo trafegadas, e as duas pontas (WCF e o jQuery) se encarregam de fazer toda a tradução necessária, aliviando assim nosso trabalho. Antes de analisarmos o código necessário para consumir serviços WCF no jQuery, vamos analisar o conteúdo que está sendo trafegado para as mensagens (requisição e resposta respectivamente) do contrato que criamos acima:

[ Operação RecuperarUsuario ]

POST http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario HTTP/1.1
Content-Type: application/json
— OUTROS PARAMETROS OMITIDOS —

{ “nome”: “Israel Aece”, “email”: “ia@israelaece.com” }

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
— OUTROS PARAMETROS OMITIDOS —

{“RecuperarUsuarioResult”:{“Codigo”:123,”Email”:”ia@israelaece.com”,”Nome”:”Israel Aece”}}

[ Operação RecuperarUsuarios ]

GET http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuarios HTTP/1.1
— OUTROS PARAMETROS OMITIDOS —

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
— OUTROS PARAMETROS OMITIDOS —

[{“Codigo”:1,”Email”:”usuario1@servidor.com.br”,”Nome”:”Nome do Usuario 1″},{“Codigo”:2,”Email”:”usuario2@servidor.com.br”,”Nome”:”Nome do Usuario 2″},{“Codigo”:3,”Email”:”usuario3@servidor.com.br”,”Nome”:”Nome do Usuario 3″},{“Codigo”:4,”Email”:”usuario4@servidor.com.br”,”Nome”:”Nome do Usuario 4″}]

[ Operação Adicionar ]

POST http://localhost/Services/ServicoDeUsuarios.svc/Adicionar HTTP/1.1
Content-Type: application/json
— OUTROS PARAMETROS OMITIDOS —

{“Codigo”:123,”Nome”:”Israel”,”Email”:”ia@israelaece.com”}

Note que a URL de requisição também contempla o método a ser disparado do lado do serviço. Os parâmetros de entrada são serializados em JSON, e o resultado idem. Na operação RecuperarUsuario, configuramos ela para que o resultado seja Wrapped, e com isso, o corpo da mensagem de retorno veio envolvida em um objeto com o nome de RecuperarUsuarioResult, ao contrário daquelas definidas como Bare, onde a requisição/resposta não estão envolvidas neste elemento adicional.

Além dos detalhes que são necessários em nível de contrato, precisamos nos atentar em como hospedar esse serviço. Você precisa configurar o endpoint do WCF para utilizar um binding exclusivo para este cenário, que é o WebHttpBinding. Se você estiver hospedando no IIS (como é o caso do exemplo acima), então provavelmente haverá um arquivo com extensão *.svc que representa o serviço. É dentro deste arquivo que precisamos trocar o “fábrica” de hosts, que deve apontar para o WebServiceHostFactory, assim como já mostrei neste artigo.

Depois de todo o serviço WCF já devidamente criado e rodando, precisamos entender a API do jQuery para consumir esse serviço de exemplo. Nos dois tipos de projetos ASP.NET (WebForms e MVC), ambos já trazem o JQuery referenciado, o que nos permite acessar o que ele fornece para consumir serviços, escritos em qualquer tecnologia. Sendo assim, o primeiro passo importante para acessar qualquer um dos recursos fornecidos pelo jQuery, devemos adicionar uma referência para o arquivo *.js correspondente ao mesmo, e para isso, podemos utilizar o seguinte elemento:

http://Scripts/jquery-1.4.1.js

Depois deste arquivo referenciado, então já podemos utilizar a API do jQuery para consumir serviços WCF. Vamos utilizar neste exemplo a função $.ajax, que é uma função de baixo nível do jQuery, e que nos permite customizar, através de uma série de parâmetros, as configurações para controlar como a mensagem será enviada e como a resposta será processada.

Entre os vários parâmetros, temos o type, que determina com a requisição será enviada ao serviço, podendo ser através de GET ou POST. A propriedade url determina o endereço até o serviço, incluindo o nome da operação a ser invocada. contentType é onde definimos como o corpo da mensagem está formatada, e apesar do jQuery utilizar um tipo genérico, que pode ser aplicado em grande parte dos casos, é sempre melhor deixar isso explícito com o tipo que você está operando. A propriedade data determina o que vai ser enviado para a operação. Essa propriedade trabalha em conjunto com uma outra propriedade chamada processData. Quando você define os dados a serem enviados para o serviço através do método GET, então os dados serão convertidos em query strings, e a propriedade processData previne esse comportamento.

Além dessas propriedades, temos algumas opções para a configuração de callbacks para alguns cenários. beforeSend permite efetuar algum processamento momentos antes da requisição ser enviada ao serviço. A opção success permite especificarmos um callback apontando para um método que deverá ser disparado quando o resultado voltar com sucesso. Já error também permite especificarmos um método a ser disparado quando algum problema acontecer no serviço. É importante dizer aqui que o tratamento de erro não será tão simples, pois o WCF não traduz automaticamente a exceção em um erro no formato JSON, o que dificulta para mostrar a mensagem de erro. Finalmente, temos um callback chamado de complete, que como o próprio nome diz, é disparado quando a requisição é finalizada, independemtente se ocorreu ou não alguma exceção durante o seu processamento.

Abaixo temos como podemos configurar a função $.ajax para executarmos a primeira operação fornecida pelo serviço WCF que criamos anteriormente, que é a RecuperarUsuario. Como o acesso deve ser feito através do método POST, então os parâmetros serão enviados no corpo da mensagem, em formato JSON. Do lado do serviço, o WCF consegue interpretar o objeto JSON que foi enviado, extrair cada propriedade e preencher cada um dos parâmetros exigidos pela operação.

function RecuperarUsuario() {
    $.ajax(
    {
        type: “POST”,
        url: “http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuario&#8221;,
        contentType: “application/json”,
        data: ‘{ “nome”: “Israel”, “email”: “ia@israelaece.com” }’,
        processData: false,
        success:
            function (resultado) {
                alert(resultado.RecuperarUsuarioResult.Nome);
                alert(resultado.RecuperarUsuarioResult.Email);
            },
        error:
            function (xhr, textStatus, errorThrown) {
                alert(‘Algum Problema Ocorreu!’);
            }
        });
    }

É importante perceber que configuramos (no contrato) o corpo da mensagem para o método RecuperarUsuario como Wrapped, e que isso nos obrigará a acessar o resultado através do objeto que envolve as propriedades do usuário, e que é criada automaticamente pelo JSON, que neste caso é chamada de RecuperarUsuarioResult.

Já a segunda operação exposta pelo serviço WCF, que retorna uma coleção de usuários, vamos acessá-la através do método GET, e ao receber o resultado, vamos iterar pela coleção, acessando elemento a elemento mostrando as propriedades Nome e Email de cada usuário retornado pelo serviço. É importante perceber aqui que estamos acessando diretamente as propriedades, sem passar por aquele elemento que é gerado pelo JSON. Isso se deve ao fato de termos configurado a respectiva operação como Bare, que evita envolver o resultado neste membro extra.

function RecuperarUsuarios() {
    $.ajax(
    {
        type: “GET”,
        url: “http://localhost/Services/ServicoDeUsuarios.svc/RecuperarUsuarios&#8221;,
        success:
            function (usuarios) {
                $.each(usuarios, function (indice, usuario) {
                    alert(usuario.Nome + “: ” + usuario.Email);
                })
            }
        });
    }

Finalmente temos o método AdicionarUsuario, que recebe como parâmetro a instância da classe Usuario e o adiciona em algum repositório. O usuário é criado utilizando a sintaxe JSON, onde configuramos cada propriedade e seu respectivo valor através de uma espécie de dicionário. Só que o jQuery não traz nativamente funções para representar o objeto em formato de string, algo que é necessário para enviá-lo até o serviço. Ao invés de fazer todo o trabalho árduo para essa transformação, podemos recorrer à uma biblioteca fornecida através do site oficial do JSON, chamada de JSON2.js. Essa biblioteca fornece dois métodos para a manipulação do JSON, sendo eles: parse e stringify. O método parse retorna o objeto devidamente reconstruído a partir de uma estrutura JSON, enquanto o método stringify retorna uma string contendo representação JSON de um determinado objeto. É justamente o resultado deste segundo método que estamos enviando ao serviço:

function AdicionarUsuario() {
    var usuario = { “Codigo”: 123, “Nome”: “Israel”, “Email”: “ia@israelaece.com” };

    $.ajax(
    {
        type: “POST”,
        url: “http://localhost/Services/ServicoDeUsuarios.svc/Adicionar&#8221;,
        contentType: “application/json”,
        data: JSON.stringify(usuario),
        processData: false,
        success:
            function (resultado) {
                alert(‘Usuário adicionado com sucesso.’);
            }
        });
    }

Apenas atente-se que para este código funcionar, precisamos fazer o download do arquivo json2.js e referenciá-lo na página, como vemos abaixo:

http://Scripts/json2.js

Conclusão: Consumir serviços a partir de AJAX pode tornar a experiência do usuário muito melhor, já que evita a necessidade de ter efetuar a atualização completa da página. Isso já era uma necessidade, mas o jQuery torna isso muito mais simples, onde mesmo utilizando funções de baixo nível como vimos aqui, a tarefa acaba sendo muito mais simples de se realizar.

Community Launch em Piracicaba

No próximo dia 17 de abril acontecerá um evento chamado de Community Launch. Esse evento será espalhado por todo o Brasil, onde cada região criará o evento e abordará os principais produtos da Microsoft que estarão sendo lançados neste ano de 2010, a saber: Visual Studio 2010, Windows Server 2008 R2 e SQL Server 2008 R2.

Para quem é de Piracicaba e região, este evento acontecerá na Faculdade Salesiana Dom Bosco, à partir das 08:30 da manhã. Para maiores detalhes, consulte o site do evento. Entre as várias palestras, eu sou o responsável por falar sobre o WCF – Windows Communication Foundation. É importante dizer que a palestra será de nível 100, abordando superficialmente algumas das principais funcionalidades e mudanças que aconteceram nesta nova versão da tecnologia. Abaixo temos a descrição da palestra:

Título: Overview do WCF
Palestrante: Israel Aece
Descritivo: O WCF é o pilar de comunicação da plataforma .NET, e que está presente desde a versão 3.0. Através dele podemos construir serviços que podem ser consumidos pela própria empresa ou por parceiros de negócios, para que seja possível criar aplicações inteligentes e conectadas. A finalidade desta palestra é mostrar um overview sobre o WCF, abordando superficialmente as funcionalidades incluídas na versão 4.0, com exemplos práticos de sua utilização.

IMPORTANTE: Apesar do evento ser gratuito, para poder participar é necessário efetuar o cadastro aqui.

Expect100Continue em serviços WCF

Por padrão, todas as requisições HTTP que são relizadas pelas classes que estão contidas no namespace System.Net, adicionam um header chamado “Expect: 100-continue”. A finalidade deste header, criado a partir da versão 1.1 do HTTP, quando é implementado corretamente pelos servidores Web, servem para que o servidor, baseando-se apenas nos headers da requisição, determine se a mensagem será ou não aceitada por ele, sem a necessidade de enviar toda a mensagem para depois rejeitá-la.

Quando você tenta consumir um serviço WCF exposto através do protocolo HTTP, eventualmente poderá receber a seguinte mensagem de erro: The remote server returned an unexpected response: (417) Expectation failed. Provavelmente isso pode ser pela má implementação deste recurso do protocolo, que evita conseguirmos consumir o serviço. Para resolver este problema precisamos desativar o envio deste header, que acaba provocando este comportamento. Para isso, podemos recorrer à propriedade estática Expect100Continue da classe ServicePointManager, que recebe um valor boleano (que por padrão é True), indicando se este recurso está ou não habilitado. No nosso caso, devemos definí-la como False antes da chamada da operação do serviço:

System.Net.ServicePointManager.Expect100Continue = false;

Ou, para ficar mais flexível, utilizando o arquivo de configuração:

<system.net>
  <settings>
    <servicePointManager expect100Continue=”false”/>
  </settings>
</system.net>

Compressão em Serviços WCF

Em certas situações, quando um cliente executa uma operação de um serviço, o resultado pode ser uma grande massa de dados. Essa massa de dados pode ser qualquer coisa, desde os bytes de um arquivo até mesmo objetos que foram extraídos de um banco de dados.

Como todos sabem, o problema disso é a quantidade de informações que irá trafegar na rede, podendo causar uma grande lentidão, caso isso seja uma operação comum. A principal solução que geralmente utilizamos quando queremos diminuir o tráfego, é a compactação das informações. Isso fará com que a quantidade total de dados seja bem menor, aliviando consideravelmente a quantidade de informações que são trafegadas. Obviamente que a compactação tem um overhead quando você compacta ou descompacta, e isso deve ser levado em consideração para decidir se deve ou não optar por ela, mas na maioria das vezes, acaba compensando.

Mas infelizmente o WCF não traz a possibilidade de compactar e/ou descompactar as mensagens que são enviadas. Sendo assim, para conseguirmos efetuar a compactação, temos que recorrer aos pontos de estensibilidade de WCF, para conseguir interceptar o envio e recebimento das mensagens, para assim conseguir diminuir o conteúdo a ser trafegado. A Microsoft se preocupou em deixar a disposição de todos, um exemplo que estende o WCF, e utiliza a classe GZipStream (System.IO.Compression) para compactar as mensagens; além deste exemplo, há um projeto chamado WCF Extensions, que possui essa funcionalidade de compactação. É importante dizer que em ambos os lados (cliente e serviço) precisam acoplar o código para efetuar a compactação e descompactação, caso contrário, a mensagem não poderá ser lida.

Além disso, para aqueles que hospedam o serviço no IIS, podem tirar proveito da compactação que ele faz. O IIS fornece um serviço que permite efetuar a compactação de conteúdo dinâmico, que é o caso de serviços WCF. Quando habilitado, este serviço irá compactar a resposta que será enviada ao cliente. O IIS compacta a resposta dependendo de um header que vem na requisição, indicando se o cliente consegue ou não interpretar o conteúdo compactado. Como os bindings do WCF não fornecem isso, você tem que explicitamente adicionar um header para encaminhar ao servidor (IIS) que você consegue compreender o algoritmo GZIP ou o Deflate. Esse header é o Accept-Encoding, e para fazer isso podemos recorrer ao código abaixo:

using (ServiceClient p = new ServiceClient())
{
    using (new OperationContextScope(p.InnerChannel))
    {
        var props = new HttpRequestMessageProperty();
        props.Headers.Add(HttpRequestHeader.AcceptEncoding, “gzip,deflate”);
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = props;

        Console.WriteLine(p.RetornaUmTextoMuitoGrande());
    }
}

Ao monitorar a requisição através do Fiddler, você poderá perceber que essas informações são encaminhadas ao IIS, incluindo o header necessário:

POST /MeuServico/Service.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip,deflate
SOAPAction: “http://tempuri.org/IService/RetornaUmTextoMuitoGrande&#8221;
Host: israelaecenb1
Content-Length: 151
Expect: 100-continue
Connection: Keep-Alive

Com isso, a mensagem de retorno que antes tinha um total de 2.000.176 bytes, passa a ter apenas 17.494 bytes usando o GZIP. Mas apesar de conseguirmos visualizar o conteúdo compactado, isso não quer dizer o proxy, ou melhor, o binding que está do lado do cliente, irá conseguir interpretar o mesmo. Como comentado acima, pelo fato do WCF não suportar nativamente a compactação, uma exceção do tipo ProtocolException será disparada, indicando exatamente isso.

Na versão 4.0 do WCF, uma nova propriedade foi adicionada na classe HttpTransportElement, chamada de DecompressionEnabled. Essa propriedade recebe um valor boleano indicando se o WCF pode ou não compactar as mensagens de resposta que são enviadas ao cliente. Por padrão, essa propriedade é definida como True, mas para conseguir acessá-la, é necessário a criação de um binding customizado. Um outro ponto importante é com relação à mensagem de requisição, que ao contrário das versões anteriores, a versão 4.0 do WCF já embuti o header necessário (Accept-Encoding) para que o serviço saiba que o cliente consegue descompactar a resposta.

Auditando serviços WCF

Como já comentei antes, o WCF fornece uma série de formas de autenticação e autorização, e mais recentemente, também já dá suporte ao WIF, assunto qual abordarei no futuro. Uma vez que a segurança esteja habilitada, uma necessidade que se tem é justamente como auditar os processos de autenticação que são realizados, independentemente do modelo de credencial que esteja sendo fornecido pelo cliente.

O WCF fornece vários pontos de estensibilidade, mas utilizá-lo há dois problemas, onde o primeiro é a necessidade de escrever o código necessário para isso, e o segundo, e mais complicado, é que a maioria (talvez todos) os pontos para acoplar algum código customizado, sempre acontece depois que o processo de autenticação já tenha sido efetuado. Já quando você possuir um modelo de autenticação customizado, você pode interceptar a validação do usuário, e caso ele não seja válido, você cataloga isso em algum lugar. Mas e quando o modelo de autenticação é Windows ou qualquer outra forma que o WCF já entende?

Apesar de informações bem simplistas, o WCF já traz nativamente um behavior que podemos utilizar em nível de serviço para auditar a autenticação. Como tudo no WCF, este behavior pode ser configurado de forma imperativa ou declarativa, e nos fornece quatro propriedades para a sua configuração, sendo elas:

  • AuditLogLocation: Todas as informações geradas pela auditoria são armazenadas no log do Windows. Esta opção permite especificar o local onde essas informações serão colocadas. Temos três possíveis opções, fornecidas pelo enumerador AuditLogLocation:
    • Default: Utiliza o log padrão do sistema operacional.
    • Application: Armazena as informações no event log Application.
    • Security: Armazena as informações no event log Security.
  • SuppressAuditFailure: Propriedade do tipo boleana, que permite especificar se eventuais falhas que aconteçam no momento da gravação do log sejam enviadas para a aplicação. Se a aplicação não se preocupa com as falhas que possam acontecer durante este momento, então é importante que se defina esta campo para True, caso contrário, as exceções comprometerão o funcionamento dela. Mas definir isso como True, faz com que possíveis exceções que seriam disparadas não sejam propagadas para a aplicação, e você nunca saberá se há algum problema, e quando realmente precisar recorrer aos logs de auditoria para saber se alguém acessou em um determinado dia e hora, as informações não estarão lá.
  • ServiceAuthorizationAuditLevel: Este nível de auditoria consiste em catalogar as informações de autorização que são realizadas em nível de serviço, utilizando a mesma política de segurança para todos os métodos.
  • MessageAuthenticationAuditLevel: Neste nível, a auditoria monitora os eventos que são gerados durante uma mensagem específica, que por sua vez, executará uma única operação.

As propriedades ServiceAuthorizationAuditLevel e MessageAuthenticationAuditLevel recebem uma das opções expostas pelo enumerador AuditLevel: None, Success, Failure ou SuccessOrFailure. A ideia aqui é permitir ao desenvolvedor catalogar somente aqueles eventos importantes para ele, pois talvez ele esteja somente interessado nas falhas que ocorreram. É importante dizer também que isso influencia no tamanho do log, e se você especificou um limite máximo, dependendo do volume de requisições que chegam para este serviço, em pouco tempo podemos exceder esse tamanho, e a partir daí, exceções podem começar a acontecer. Eis aqui um dos motivos para a existência da propriedade SuppressAuditFailure.

O código abaixo exibe a utilização deste behavior utilizando o modelo declarativo, através de um arquivo de configuração (App.config ou Web.config):

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name=”Host.Servico” behaviorConfiguration=”srvBehaviorConfig”>
        <!– endpoints –>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name=”srvBehaviorConfig”>
          <serviceSecurityAudit
            auditLogLocation=”Application”
            messageAuthenticationAuditLevel=”SuccessOrFailure”
            serviceAuthorizationAuditLevel=”SuccessOrFailure”
            suppressAuditFailure=”false” />
          <!– configurações de segurança –>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Abaixo temos o resultado que foi colocado no Event Log:

———————————————————————————
Service authorization succeeded.
Service: http://localhost:8778/srv
Action: http://www.israelaece.com/IContrato/RecuperarDados
ClientIdentity: IsraelAece
AuthorizationContext: uuid-019ae4a4-2cb5-4414-9806-1a4e22a4bf79-2
ActivityId: <null>
ServiceAuthorizationManager: XmlAuthorizationManager
———————————————————————————

MaxItemsInObjectGraph

Há algum tempo, eu comentei aqui sobre os limites e cotas que o WCF possui, que se não se atentar em ajustar de acordo com a sua necessidade, exceções começam a ser disparadas se você excede os valores que lá estão.

Além daquelas configurações, ainda há uma outra configuração/cota que interfere na quantidade de informações que trafegam entre as partes, que é a MaxItemsInObjectGraph. Essa propriedade recebe um número inteiro (que por padrão é 65.536 (64KB)), que especifica a quantidade máxima de objetos que podem ser serializados ou deserializados pelo DataContractSerializer. Se tentar enviar e/ou receber uma quantidade de objetos maior que o valor especificado por essa cota, você pode se deparar com a seguinte exceção do tipo SerializationException, como é mostrado abaixo:

Unhandled Exception: System.ServiceModel.CommunicationException: There was an error while trying to serialize parameter http://tempuri.org/:clientes. The InnerException message was ‘Maximum number of items that can be serialized or deserialized in an object graph is ‘200’. Change the object graph or increase the MaxItemsInObjectGraph quota. ‘.  Please see InnerException for more details. —> Systm.Runtime.Serialization.SerializationException: Maximum number of items that can be serialized or deserialized in an object graph is ‘200’. Change the object graph or increase the MaxItemsInObjectGraph quota.

A configuração desta opção não está em nível de binding, ao contrário do que acontece com as outras cotas, pois ela está relacionada ao serializador que o serviço utiliza para uma operação em particular. É importante dizer que essa configuração deve estar sincronizada entre o cliente e serviço, caso contrário, o erro persistirá. Há diversas formas para configurarmos ela, e a primeira delas é através da propriedade MaxItemsInObjectGraph exposta pelo atributo ServiceBehaviorAttribute:

[ServiceBehavior(MaxItemsInObjectGraph = 1000)]
public class Servico : IContrato
{
    public Cliente[] Ping(Cliente[] clientes)
    {
        //…
    }
}

A outra forma de configuração é acessando diretamente as descrições do serviço, de forma imperativa, onde poderá customizar essa informação para uma operação específica. O código abaixo mostra como proceder para alterá-la do lado do serviço e do lado do cliente, respectivamente:

serviceHost
    .Description
    .Endpoints[0]
    .Contract
    .Operations[0]
    .Behaviors
    .Find<DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = 1000;

proxy
    .Endpoint
    .Contract
    .Operations[0]
    .Behaviors
    .Find<DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = 1000;

Além disso, ainda podemos optar por configurá-la através do respectivo arquivo de configuração da aplicação (cliente ou serviço). Para isso, utilizamos o elemento dataContractSerializer, que é um behavior que será aplicado no endpoint do serviço, como podemos reparar abaixo:

<configuration>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name=”config”>
                    <dataContractSerializer maxItemsInObjectGraph=”1000″ />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <client>
            <endpoint address=””
                      behaviorConfiguration=”config”
                      binding=”basicHttpBinding”
                      contract=”IContrato” />
        </client>
    </system.serviceModel>
</configuration>

Impacto de Timeouts no Proxy WCF

Como comentei neste post, as exeções influenciam diretamente na vida útil do proxy de um serviço WCF. Da mesma forma, quando eventuais timeouts acontecem no serviço, isso também faz com que o proxy seja movido para um estado falho, mas não diretamente.

Isso acontece para aqueles bindings que suportam sessão, onde depois de um tempo de inatividade, a sessão que é mantida do lado do serviço para aquele cliente, será expirada, fazendo com que o canal de comunicação do serviço entre em um estado falho, não podendo mais receber requisições.

Neste momento, o proxy correspondente ainda continua aberto e íntegro, até que ele envie uma próxima requisição até o serviço que está falho, e que por sua vez, irá disparar uma exceção e retornará uma fault para o cliente, que ao recebê-la, irá mover o proxy para um estado falho e, consequentemente, nenhuma outra requisição poderá ser realizada a partir daquele proxy, a menos que você o reconstrua, podendo utilizar a mesma técnica mostrada no post anterior.