ASP.NET Core Web API - Tratamento de erros - I


 Neste artigo vamos apresentar uma abordagem de tratamento de erros que pode ser usada em aplicações ASP .NET Core Web API.

No .NET Core as aplicações MVC e WEB API agora possuem os mesmos controladores para as Actions; no entanto, apesar da semelhança, o tratamento de erros é feito de forma distinta nas respectivas aplicações.

Como as Actions das aplicações MVC são executadas como resultado de uma ação do usuário no navegador, retornar uma página de erro ao navegador é uma abordagem correta. Nas aplicações Web API a abordagem já diferente.

Como as chamadas de uma API são em geral feitas pelo código do backend ou javascript e, o procedimento padrão é verificar o código de status e analizar a resposta para determinar se a ação foi executada com sucesso exibindo dados ao usuário se necessário. Gerar uma página de erro HTML é inútil nessas situações pois o retorno esperado é em geral JSON ou XML.

Apesar desse comportamento diferente, o fluxo do tratamento de erro usado , é praticamente o mesmo nas aplicações MVC e Web API.

Vejamos algumas abordagens usadas para o tratamento de erros em uma Web API.

Nos exemplos do artigo estou usando o Visual Studio 2019 Community e a ASP .NET Core 2.2.

Usando a abordagem padrão

Em uma aplicação WEB API, quando ocorre uma requisição inválida, o retorno de um response body vazio, embora não seja o ideal pode ser suficiente para indicar o cliente do processamento inválido.

Retornar um código de status 404 (sem corpo de resposta) para uma rota inexistente na API já pode ser suficiente para informar o cliente que corrija a sua requisição.

Essa geralmente é a abordagem padrão com configuração zero, e é o que o framework ASP.NET Core nos fornece.

Agora, quando um cliente passa dados inválidos, retornar um código de status 400, por exemplo, pode não ser suficiente para o cliente idenficar o problema. O correto é informar quais campos estão incorretos e retornar uma mensagem específica para cada campo inválido.

Isso é muito simples de fazer na ASP .NET Core usando os atributos de validação internos pré-existentes e disponíveis no namespace System.ComponentModel.DataAnnotations sendo muito usados para aplicações ASP .NET Core MVC.

Para aplicações WEB API podemos usar a abordagem usar o método Validate() da interface IValidatableObject retornando a informação de validação para o cliente no formato JSON.

Nota: Outra abordagem é criar uma classe que herda de ValidationAttribute e sobrescrever o método IsValid(). Para detalhes veja este artigo.

Como exemplo para um modelo de domínio Produto :

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ProdutosApi.Model
{
    public class Produto : IValidatableObject
    {
        public int ProdutoId { get; set; }
        [Required]
        public string Nome { get; set; }
        public string Descricao { get; set; }
        public decimal Preco { get; set; }
        public bool Ativo { get; set; }
        public string ImagemUrl { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
           if(string.IsNullOrEmpty(this.Nome))
           {
                yield return new ValidationResult("O nome do produto é obrigatório", new[] { nameof(Nome) });
           }
           if (this.Nome.Length < 5)
           {
             yield return new ValidationResult("O nome do produto deve ter no mínimo 5 caracteres", new[] { nameof(Nome) });
           }

        }

    }
}

Implementamos a interface IValidatableObject e o método Validate() definindo regras de validação para nome.

Usando o seguinte método Action HttpPost no Controlador:

        [HttpPost]
        public async Task<ActionResult<Produto>> PostProduto(Produto item)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            _ctx.Produtos.Add(item);
            await _ctx.SaveChangesAsync();
            return CreatedAtAction(nameof(GetProduto), new { id = item.ProdutoId }, item);
        }

Nota:  O código destacado em azul na versão 2.2 da ASP .NET Core é chamado implicitamente, não sendo necessário ser escrito. Veja detalhes aqui.

Fazendo uma requisição POST usando o Postman, para criar um novo produto com dados do nome do produto inválido (usamos apenas 3 caracteres), temos o resultado abaixo:

 

Vemos o retorno no formato JSON indicando claramente o problema de campo inválido relativo ao nome.

Retornando informação adicional para erros específicos

Geralmente podemos retornar o código de status para um erro ocorrido em um request, mas existem casos que isso não é suficiente para identificar a falha.

Veja por exemplo o código de status 404 que pode ter os seguintes significados:

- O domínio está correto, mas a URL não corresponde a uma rota existente;
- A URL é mapeada corretamente para uma rota, mas o recurso não existe;
- O request esta sendo feito para um endereço incorreto;


Se pudéssemos fornecer informações para distinguir entre esses casos, isso poderia ser muito útil para um cliente.

Vejamos o caso do recurso não encontrado que pode acontecer quando informamos um id inexistente para localizar um produto:

O código abaixo mostra uma forma de resolver esse problema:

        [HttpGet("{id}")]
        public async Task<ActionResult<Produto>> GetProduto(int id)
        {
            var produto = await _ctx.Produtos.FindAsync(id);
            if (produto == null)
            {
                return NotFound($"Produto não encontrado com o id ={id} informado");
            }
            return produto;
        }

Veja no Postman o resultado de um request para um id inexistente:

Agora estamos retornando uma mensagem mais clara, o problema é que ela esta no formato texto sem formatação e não no formato JSON o que pode levar a inconsistências no tratamento da resposta.

Para resolver esse problema podemos usar o código a seguir no lugar do anterior:

        [HttpGet("{id}")]
        public async Task<ActionResult<Produto>> GetProduto(int id)
        {
            var produto = await _ctx.Produtos.FindAsync(id);
            if (produto == null)
            {
               return NotFound(new { message = $"Produto de id={id} não encontrado" });
            }
            return produto;
        }

Testando novamente no Postman para um id inexistente teremos agora o resultado abaixo:

Observe que agora a resposta esta no formato JSON.

Na próxima parte do artigo veremos como customizar o tratamento da resposta de forma mais estruturada.

"Disse-lhes, pois, Jesus: Quando levantardes o Filho do homem, então conhecereis que EU SOU, e que nada faço por mim mesmo; mas isto falo como meu Pai me ensinou."
João 8:28

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

 

 Referências:


José Carlos Macoratti