ASP.NET Core - Apresentando a classe ProblemDetails


 Hoje vou apresentar a classe ProblemDetails da ASP.NET Core e mostrar como usá-la na ASP .NET Core.

Em uma Web API para comunicar os erros e exceções aos clientes devemos especificar um formato de resposta. E muitas vezes desejamos informar detalhes da ocorrência ao invés de apenas dizer que ocorreu um erro 400 ou 500.

Como existem milhares de APIs e cada uma define um formato de resposta diferente acaba que não temos um formato padronizado e isso podemos constatar verificando APIs muito consumidas como o Facebook e o Twitter.

Para resolver este problema foi criada em 2016 um documento chamado Problem Details for HTTP APIs definido  pela IETF (Internet Enginnering Task Force), instituição que especifica os padrões que serão implementados e utilizados em toda a internet., na RFC 7807 que cria um formato padronizado para os formatos de mensagens de erros em APIs HTTP e define o objeto Problem Detail. O objetivo é evitar que novos formatos sejam criados e ela especifica o seguinte:

  1. Os códigos de status HTTP entre os intervalos 400 e 500 devem ser usados para exibir mensagens de erro;
  2. O header Content-Type deve ser to tipo application/problem, incluindo o formato de serialização da mensagem, quer seja json ou xml;
     
     application/problem+json    
     application/problem+xml

     
  3. O payload das respostas de erro devem conter as seguintes propriedades :
     
        Title -> um breve resumo do tipo de problema. Não deve mudar para ocorrências do mesmo tipo, 
        exceto para fins de localização;
        Detail -> Uma descrição detalhada do problema;
        Type -> Uma URL para um documento que descreva o tipo do problema;
        Status -> O status HTTP gerado pelo servidor de origem. Normalmente deve ser o mesmo status 
                      HTTP da resposta, e pode servir de referência para casos onde um servidor proxy altera 
                      o status da resposta;    
        Instance -> Propriedade opcional, com um URI exclusivo para o erro específico, que geralmente 
                         aponta para um log de erros para essa resposta.

Vejamos um exemplo de uma resposta formatada usando o objeto ProblemDetails:

Aqui estamos tentando acessar um produto que não existe mais, e, como podemos ver nos cabeçalhos de resposta, o objeto JSON Problem Details é do tipo “appalication/problem+json”, conforme especifica a documentação, e, ele contém os seguintes membros:

O membro “Type” é considerado vazio quando não for especificado. Quando existe, ele fornece uma referência legível que descreve o tipo de problema em geral. Aqui, na resposta de exemplo, isso leva a uma página de erro personalizada. Essa página explicaria ao usuário que o produto que ele estava procurando não existe. Em outros casos, pode, por exemplo, vincular-se à descrição do código de status HTTP.

O membro “Status” é apenas consultivo. Pode ser útil em alguns casos em que queremos confirmar que nosso cabeçalho de resposta não foi adulterado. Deve sempre corresponder ao código de status gerado pelo servidor especificado nos cabeçalhos de resposta. Isso é para garantir que o cliente se comporte corretamente, mesmo que não entenda o formato ProblemDetails.

O membro “Instance” identifica a ocorrência específica do problema e pode ou não fazer referência a informações mais detalhadas.

Além do formato padrão especificado do objeto Problem Details, podemos estendê-lo se quisermos adicionar informações personalizadas.  É importante notar que este objeto não se destina a ser uma ferramenta de depuração. Devemos sempre ter cuidado ao expor nossos detalhes de implementação nessas respostas.

A implementação ASP .NET Core

Desde a versão 2.1 do .NET Core, o objeto Problem Details é representado pela classe ProblemDetails que é responsável por definir um formato legível para especificar erros nas respostas das APIs HTTP com base na RFC 7807.

Assim, a partir da versão versão 2.2 da ASP .NET Core, quando usamos os métodos internos da classe ControllerBase para retornar as respostas do código de status HTTP, como Ok() ou BadRequest(), o framework formata automaticamente a resposta como base na classe ProblemDetails seguindo assim o que recomenda a RFC 7807.

Então na ASP.NET Core,  podemos usar o recurso da classe ProblemDetails nos controladores simplesmente chamando o método Problem() de ControllerBase que retorna um IActionResult conforme mostra o exemplo a seguir:

        [HttpGet("{id}", Name = "ObterCategoria")]
        public ActionResult<Categoria> Get(int id)
        {
            var categoria = _context.Categorias.AsNoTracking()
                .FirstOrDefault(p => p.CategoriaId == id);

            if (categoria == null)
            {
                return Problem(
               "A categoria não existe.",
                $"/categoria//{id}",
                404,
               "Não foi possível encontrar a categoria.",
               "http://exemplo.com/problemas/nao-encontrada");

            }

           return categoria;
        }

O resultado obtido no navegador ao tentar localizar uma categoria inexistente pode ser visto a abaixo:

Como nada é perfeito, o objeto Problem Details não tem uma maneira explícita de definir vários problemas em uma única resposta. Você pode conseguir isso definindo um tipo específico, o que indica que haverá um membro de problemas que será uma matriz dos membros de detalhes de problemas normais.

Para realizar o tratamento de exceções o framework não nos dá muitas opções e temos que fazer todo o trabalho via código. Como alternativa você pode usar o pacote Hellang.Middleware.ProblemDetails de Kristian Hellang, que é um middleware que mapeia exceções para ProblemDetais e que veremos na  próxima parte do artigo.

E estamos conversados...

"Aguardo ansiosamente e espero que em nada serei envergonhado. Pelo contrário, com toda a determinação de sempre, também agora Cristo será engrandecido em meu corpo, quer pela vida quer pela morte;
porque para mim o viver é Cristo e o morrer é lucro "
Filipenses 1:20-21


Referências:


José Carlos Macoratti