Desenvolvimento - WCF

Tratando erros com jQuery e WCF

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.

por Israel Aéce



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 classJsonErrorHandler : IEndpointBehavior, IErrorHandler
{
publicbool 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 classJsonErrorElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get
{
return typeof(JsonErrorHandler);
}
}

public 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 usuario\u000d\u000aParameter 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",
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.
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.