Tratando erros com jQuery e WCF

by Israel Aece 18. May 2010 22:03

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

    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.

Tags: , , , ,

WCF

Impacto de Timeouts no Proxy WCF

by Israel Aece 5. February 2010 08:32

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.

Tags: , ,

WCF

Impacto de Exceções no Proxy WCF

by Israel Aece 4. February 2010 11:17

Uma das preocupações que devemos ter em qualquer tipo de desenvolvimento é com o tratamento de erros. Eu já comentei neste artigo e vídeo as possibilidades que temos para fazer isso dentro do WCF, utilizando suas próprias características de interceptação, transformação (promoção) e propagação das exceções para faults.

Já sabemos que se conhecermos todos os eventuais problemas que podem acontecer, podemos já definí-los no contrato do serviço (através do atributo FaultContractAttribute), para que assim o cliente possa ser notificado do que realmente aconteceu. Só que ainda há uma questão que não foi tratada, que é justamente como fica o estado do proxy depois que a falha acontece no serviço?

A resposta para essa pergunta dependerá de qual tipo de exceção que está sendo disparada, e também de qual binding está sendo utilizado por aquele endpoint. Em grande parte dos cenários, vemos que o desenvolvedor utilizar a instância de um mesmo proxy para efetuar várias chamadas para o mesmo serviço, não importando se é ou não para uma mesma operação.

Envolver as chamadas para as operações dentro de um bloco try/catch, evita apenas que a aplicação cliente saiba se comportar quando um determinado erro ocorre. Mas dependendo do que aconteceu no serviço, você não conseguirá reutilizar a mesma instância do proxy. Depois da falha, qualquer tentativa de comunicacão não chegará mais até o serviço, resultando assim naquela famosa exceção do tipo CommunicationObjectFaultedException, com a seguinte mensagem:

System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state

Digamos que você não está preocupado com as exceções que ocorrem dentro do seu serviço. Dentro de uma determinada operação, uma exceção CLR (ArgumentNullException, IndexOutOfRangeException, etc.) é disparada. No exemplo abaixo, estou efetuando o teste para saber se o parâmetro é ou não nulo, e sendo, uma exceção do tipo ArgumentNullException está sendo disparada:

public string Ping(string value)
{
    if (value == null)
        throw new ArgumentNullException("value");

    return value;
}

Como exceções não tratadas do lado do serviço são consideradas um risco, o canal de comunicação (proxy) não ficará mais disponível. Para os bindings que suportam sessão (NetTcpBinding, WSHttpBinding, etc.), isso quer dizer que se você tentar invocar uma segunda requisição, ele estará em um estado inválido (Faulted), o que obrigará a você recriar o proxy (instanciar novamente) para depois utilizá-lo. A solução para este caso, já foi comentada nos artigos que referencie acima, que é trabalhar explicitamente com a classe FaultException (ou até mesmo FaultException<T>):

public string Ping(string value)
{
    if (value == null)
        throw new FaultException("value");

    return value;
}

Desta forma, o canal de comunicação não será afetado, e seguramente você poderá utilizá-lo para efetuar novas requisições. Já aqueles bindings que não suportam sessão, como é o caso do BasicHttpBinding ou do WebHttpBinding (utilizado para REST/AJAX), não serão danificados, mesmo se você não estiver se preocupando com o uso da classe FaultException.

Como muitas vezes as operações recorrem a outras classes, e que muitas você não tem acesso à elas, então você dificilmente conseguirá mapear todas as exceções que podem acontecer. Para garantir que o proxy consiga se "restaurar" de um estado falho, você pode recorrer ao evento Faulted, exposto pela interface ICommunicationObject e também pela classe ClientBase<TChannel>. Este evento é disparado quando o proxy entre em estado falho, e assinando este evento, você poderia reconstruir o proxy no exato momento, como eu mostro no código abaixo:

ChannelFactory<IContrato> factory = 
    new ChannelFactory<IContrato>(new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:3732/srv"));

IContrato proxy = factory.CreateChannel();
((ICommunicationObject)proxy).Faulted += (o, e) => proxy = factory.CreateChannel();

Tags: , ,

WCF

Propagando exceções para o Silverlight

by Israel Aece 23. September 2009 09:56

O WCF é uma das formas que temos para estabelecer a comunicação entre uma aplicação Silverlight e a aplicação (ASP.NET) que a hospeda. Com o WCF podemos permitir que o Silverlight se conecte e envie e/ou receba informações, pois como sabemos, ele roda dentro do navegador do usuário. Apesar de você não ter acesso a maior parte dos recursos do WCF, o básico você consegue fazer.

Antes da versão 3 do Silverlight, há uma grande dificuldade em propagar as exceções que esse serviço, eventualmente, disparasse. Qualquer exceção que ocorra dentro do WCF, a resposta é enviada ao cliente definindo o código de resposta do HTTP (Http Status Code) como 500, o que indica um erro interno do servidor e impede o navegador de ler qualquer resposta. Esse problema pode ser facilmente contornado, criando um message inspector, verificando antes de enviar a resposta para o cliente, se a mensagem é ou não uma fault. Caso seja, você altera o status do HTTP para 200 (OK) ao invés de 500. O código abaixo ilustra como criar esse behavior customizado:

public class SLFaultMessageInspector : IDispatchMessageInspector
{
    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        if (reply.IsFault)
            reply.Properties[HttpResponseMessageProperty.Name] =
                new HttpResponseMessageProperty() { StatusCode = HttpStatusCode.OK };
    }

    //....
}

Isso ainda poderia ser feito na versão 2 do Silverlight, mas o cliente não conseguia interpretar a fault, ou melhor, traduzí-la para uma exceção, e permitir que o desenvolvedor trate o erro da forma tradicional. Já a versão 3, incorpora a capacidade ao Silverlight de processar as faults, podendo especificar os contratos de faults ou optar por não especificá-los, para assim propagar ao cliente os objetos que representam e trazem maiores informações sobre o problema que ocorreu do outro lado.

Tags: , , ,

WCF

Utilizando Exceptions como Faults

by Israel Aece 15. September 2009 08:39

Quando construímos um serviço WCF, podemos utilizar o atributo FaultContractAttribute um tipo, que representará o problema que ocorreu durante o processamento daquela mensagem. O tipo especificado ali vai estar disponível para o cliente através de uma FaultException<T>, onde T representará os detalhes do erro. O parâmetro genérico T não tem nenhuma constraint, o que permite colocar qualquer tipo (desde que ele seja serializável). Para maiores detalhes, consulte essas fontes.

Como o tipo informado através do atributo FaultContractAttribute está aberto, nada impede de colocarmos ali um tipo que herde direta ou indiretamente da classe Exception. Definindo exceções que foram criadas pelo .NET Framework, não haverá problemas, já que elas existem do outro lado. As dificuldades começam quando é preciso propagar exceções customizadas, aquelas que são herdadas a partir da classe Exception. O grande problema aqui é que o serializador padrão do WCF, que é o DataContractSerializer, não serializa tipos complexos por questões de interoperabilidade. Com essa "limitação", a classe Exception (ou uma de suas derivadas) sofrerá com isso, já que ela expõe uma propriedade chamada Data, que retorna a instância de um objeto que implementa a Interface IDictionary.

Para resolvermos isso, a boa prática é que sempre criar Faults. Dessa forma, você criará uma classe para detalhar o problema para os clientes, sem a necessidade de manipular exceções. Com isso, ao invés de disparar uma exceção customizada, você dispara uma FaultException<T>, onde T será essa classe que detalhará o problema ocorrido. Agora, se você tiver a possibilidade de compartilhar os tipos, então a exceção customizada funcionará, já que os clientes a conhecem, mas você pagará o preço da interoperabilidade.

Tags: , ,

WCF

WCF Vídeo - Tratamento de Erros

by Israel Aece 8. September 2009 07:38
Tratamento de Erros Tratamento de Erros

Um dos pontos mais importantes na construção/consumo de um serviço, é como lidar com os eventuais erros que podem ser disparados durante a execução. Este vídeo detalhará quais as alternativas que temos para isso. Para maiores detalhes, consulte este artigo.

Formato: WMV - Duração: 00:33:30 - Tamanho: 48MB

Tags: , ,

WCF

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

CSE - Corrupted State Exceptions

by Israel Aece 21. February 2009 14:46

A classe Exception é a base para todas as exceções do .NET Framework. Tanto exceções do próprio .NET quanto aquelas que criamos, herdam direta ou indiretamente dela, independentemente de sua severidade.

Tendo isso em mente, nunca foi tão perigoso utilizar a instrução catch(Exception). Mesmo em "aplicações finais", que são aquelas que devem ter um handler para tratar as possíveis exceções, utilizar esta técnica pode ser muito perigoso. Existem algumas exceções que são consideradas "exceções de sistema", como é o caso da AccessViolationException, e como também derivam da classe Exception, serão capturadas e "tratadas" pelo handler catch(Exception). Mas isso não quer dizer que voce pode/deve continuar utilizando a aplicação, já que essas exceções comprometem o estado do respectivo processo.

Para resolver este tipo de problema, a Microsoft está disponibilizando junto ao .NET 4.0 o conceito de CSE (Corrupted State Exceptions). Com este novo recurso, a CLR não entregará exceções que podem danificar o processo para o handler catch(Exception), a menos que voce faça isso explicitamente (apesar de não ser uma boa prática), decorando o método que pode causar a falha com o atributo HandleProcessCorruptedStateExceptionsAttribute. Para maiores informações sobre essa nova funcionalidade, consulte este artigo.

Tags:

.NET Framework

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