ASP.NET Core Web API - Tratando erros globalmente


Neste artigo veremos como realizar o tratamento de erros de forma global em aplicações ASP .NET Core Web API.

Os recursos de tratamento de erros nos ajudam a lidar com os erros imprevistos que podem aparecer em nosso código. Para lidar com exceções, podemos usar o bloco try-catch, e a palavra-chave finally para limpar os recursos posteriormente.

Embora não haja nada de errado em usar blocos try-catch no métodos Action das nossas Web APIs, podemos extrair toda a lógica de tratamento de exceções em um único local. Ao fazer isso, tornamos nossas Actions mais legíveis e o processo de tratamento de erros mais sustentável. Se quisermos tornar nossas Actions ainda mais legíveis e fáceis de manter, poderemos implementar Action Filters(Filtros de Ação).

Neste artigo, vamos iniciar tratando os erros via bloco try-catch e depois vamos reescrever nosso código usando o middleware integrado e a seguir vamos criar nosso middleware personalizado para o tratamento global de erros, a fim de demonstrar os benefícios dessa abordagem.

Criando o projeto API no VS 2019 Community

Vamos criar uma aplicação ASP .NET Core Web Application usando o template API no Visual Studio 2019 Community chamada Api_GlobalErros;

Abra o VS 2019 Community e selecione : Create a new Project

Selecionando em Language : C#, Platform: All Platforms e em Project Type: web podemos selecionar o template ASP .NET Core Web Application:

A seguir vamos informar o nome do projeto, sua localização e nome da solução conforme abaixo:

Finalmente seleciona a plataforma .NET Core e ASP .NET Core 2.2 e o template API.

Marque a opção para configurar HTTP e não vamos usar Autenticação:

Ao final teremos um projeto Web API contendo o controlador ValuesController na pasta Controllers que iremos ajustar para usar neste artigo.

Para tornar o exemplo mais simples e focarmos apenas na questão do tratamento de erros vamos ajustar o controlador ValuesController alterando o seu código para tratar apenas um request Get retornando dados de alunos que iremos implementar em uma classe chamada Repositorio.

No código vamos usar um bloco try-catch para tratar exceções.

A seguir temos o código ajustado do controlador ValuesController:

using Api_GlobalErros.Models;
using Microsoft.AspNetCore.Mvc;
using System;
namespace Api_GlobalErros.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public IActionResult Get()
        {
            try
            {
                var alunos = Repositorio.GetAlunos(); 
                return Ok(alunos);
            }
            catch (Exception)
            {
                return StatusCode(500, "Internal server error");
            }
        }
    }
}

Vamos criar uma pasta Models no projeto e nesta pasta criar uma classe Repositorio com o seguinte código:

using System.Collections.Generic;
namespace Api_GlobalErros.Models
{
    public class Repositorio
    {
        public static List<Aluno> GetAlunos()
        {
            return new List<Aluno>
            {
                new Aluno { Nome="Jose", Email="jose@email.com"},
                new Aluno { Nome="Janete", Email="janjan@email.com"},
                new Aluno { Nome="Miriam", Email="mimi@email.com"},
                new Aluno { Nome="Sandra", Email="sansan@email.com"},
            };
        }
    }
}

Agora vamos criar a classe Aluno na pasta Models com o código abaixo:

namespace Api_GlobalErros.Models
{
    public class Aluno
    {
        public string Nome { get; set; }
        public string Email { get; set; }
    }
}

Agora nosso projeto Web API esta pronto. Vamos iniciar o tratamento de erros usando o bloco try-cacth.

Tratando erros com blocos try-catch

Vamos iniciar tratando os erros usando apenas o bloco try-catch, e, para poder enviar requisições para nossa web api vamos usar o Postman.

Para instalar o Postman acesse esse link : https://www.getpostman.com/apps ou abra o Google Chrome e digite postam e a seguir clique no link:  Postman - Chrome Web Store

Na janela que será aberta clique no botão - Usar no Chrome:

A seguir registre uma conta e faça o login.

Pronto ! Já podemos usar o Postman.

Abrindo o Postman e enviando uma requisição GET para o endpoint api/values em https://localhost:44390 iremos obter:

Tudo ok;. Essa era a resposta esperada, o método GET irá retornar a lista de alunos conforme mostrado na figura.

Vamos agora alterar o código do controlador ValuesController para lançar uma exceção incluindo a linha destacada em vermelho abaixo no código:

using Api_GlobalErros.Models;
using Microsoft.AspNetCore.Mvc;
using System;
namespace Api_GlobalErros.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public IActionResult Get()
        {
            try
            {
                var alunos = Repositorio.GetAlunos(); 
                throw new Exception("Exceção ocorrida ao obter os alunos."); 
                return Ok(alunos);
            }
            catch (Exception)
            {
                return StatusCode(500, "Internal server error");
            }
        }
    }
}

Repetindo a requisição GET para o endereço :  https://localhost:44390/api/values iremos obter:

Agora o bloco try-catch capturou a exceção e exibiu a mensagem Status Code 500 - Internal Server error.

Assim, a utilização do bloco try-catch funciona bem, mas a desvantagem desta abordagem é que precisamos repetir os blocos try-catch em todas as Actions onde desejamos capturar as exceções não tratadas.

Vamos então usar uma abordagem melhor que é usar o middleware interno para tratamento de erros globais.

Tratamento de erros globais com o Middleware interno

O pipeline de processamento de  requests da ASP.Net Core inclui uma cadeia de componentes de middleware. Esse pipeline, por sua vez, contém uma série de delegates de request que são invocados um após o outro.

Enquanto as solicitações recebidas fluem através de cada um dos componentes de middleware no pipeline, cada um desses componentes pode processar a solicitação ou passar a solicitação para o próximo componente no pipeline.

O middleware integrado para o tratamento de exceções chama-se UseExceptionHandler e podemos usar esse middleware integrado para manipular exceções.

Como podemos usar esse middleware ?

A forma mais simples de usar este recurso e configurar o middleware no método Configure para redirecionar o usuário para uma página de erros quando uma exceção ocorrer.

Veja um exemplo de código para fazer isso abaixo:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
               app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseExceptionHandler("/Home/Error");
            app.UseMvc();
        }

Esse exemplo pode ser usado em uma aplicação ASP .NET Core MVC, mas nossa Web API não usa views.

Neste caso, e para o nosso exemplo, podemos definir detalhes na captura da exceção conforme mostra o código destacado em azul abaixo no método Configure:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            //app.UseExceptionHandler("/Home/Error");
            app.UseExceptionHandler(
                options =>
                {
                    options.Run(
                        async context =>
                        {
                            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                            context.Response.ContentType = "text/html";
                            var exceptionObject = context.Features.Get<IExceptionHandlerFeature>();
                            if (null != exceptionObject)
                            {
                         var errorMessage = $"<b>Error: {exceptionObject.Error.Message}
</b> {exceptionObject.Error.StackTrace}";
                                await context.Response.WriteAsync(errorMessage).ConfigureAwait(false);
                            }
                        });
                }
            );
            app.UseMvc();
        }

Antes de executar remova o bloco try-catch do método Action Get do controlador ValuesController:

        [HttpGet]
        public IActionResult Get()
        {
                var alunos = Repositorio.GetAlunos();
                throw new Exception("Exceção ocorrida ao obter os alunos.");
                return Ok(alunos);
        }

Executando novamente e enviando uma requisição GET iremos obter o resultado abaixo:

Vemos agora o stack trace sendo exibido no Postman conforme o esperado e agora estamos usando o middleware de tratamento de erros interno.

Na próxima parte do artigo vamos criar o nosso tratamento de erro customizado global.

"A luz semeia-se para o justo, e a alegria para os retos de coração."
Salmos 97:11

Referências:


José Carlos Macoratti