ASP .NET Core  - Tratando erros de forma global


  Neste artigo veremos como realizar o tratamento de erros de forma global na ASP .NET Core 5.0 criando um middleware customizado.

A ASP.NET Core oferece suporte a middlewares e dentre eles temos o recurso que permite tratar exceções de forma simples.

Lembrando que os  Middlewares são componentes que definem o pipeline de execução dos requests HTTP, ou seja, todos os passos que seu request faz dentro da aplicação desde a sua recepção até a resposta.

Os middlewares são encadeados e podem executar outro através de delegates repassando o mesmo contexto da requisição. Quando uma resposta é gerada em algum passo dentro do pipeline, a execução volta para o passo anterior e assim em diante.

Além disso, você pode criar trechos de código que executam antes ou depois do próximo passo do pipeline. Eles são configurados em ordem no método Configure da classe Startup, através da interface IApplicationBuilder.

O beneficio principal do tratamento global de erros é que você pode fazer a implementação em um único local facilitando a manutenção e assim não ter que usar o bloco try...catch nos métodos Action deixando o seu controlador mais limpo e legível.

Eu já mostrei como usar os middlewares nativos da ASP .NET Core para realizar o tratamento de erros de forma global neste artigo :  ASP.NET Core API - Tratamento de erros com UseExceptionHandler

Hoje veremos como criar um middleware customizado para realizar o tratamento de erros.

Vejamos isso na prática.

Criando um projeto API

Abra o VS 2019 e clique em New Project e selecione o template ASP .NET Core Web API e clique em Next;

Informe o nome ApiGlobalError e clique em Next;

A seguir selecione o Target Framework, Authentication Type e demais configurações conforme mostrada na figura:

Remova os arquivos WeatherForecastController.cs e WeatherForecast.cs do projeto criado.

A seguir vamos criar na pasta Controllers o controlador TestesController com o código a seguir:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace ApiGlobalError.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestesController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get() => throw new NullReferenceException("Ocorreu um erro :
 NullReferenceException");

    }
}

Agora vamos criar na pasta GlobalExceptionHandler no projeto e nesta pasta vamos definir os seguintes recursos:

Vamos iniciar criando a classe ApiResponse:

public class ApiResponse<T>
{
    public T Data { get; set; }
    public bool Succeeded { get; set; }
    public string Message { get; set; }

    public static ApiResponse<T> Fail(string errorMessage)
    {
        return new ApiResponse<T> { Succeeded = false, Message = errorMessage };
    }

    public static ApiResponse<T> Success(T data)
    {
        return new ApiResponse<T> { Succeeded = true, Data = data };
    }
}

Nesta classe definimos :

A seguir vamos criar a classe ApiException que define uma exceção customizada:

using System;
using System.Globalization;

namespace ApiGlobalError.GlobalExceptionHandler
{
    public class ApiException : Exception
    {
        public ApiException() : base()
        {
        }
        public ApiException(string message) : base(message)
        {
        }
        public ApiException(string message, params object[] args)
            : base(String.Format(CultureInfo.CurrentCulture, message, args))
        {
        }
    }
}

Esta classe herda de Exception e define 3 construtores para o tratamento de exceções.

Finalmente vamos criar o middleware customizado ApiExceptionHandlerMiddleware :

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;

namespace ApiGlobalError.GlobalExceptionHandler
{
    public class ApiExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        public ApiExceptionHandlerMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception error)
            {
                var response = context.Response;
                response.ContentType = "application/json";

                var responseModel = ApiResponse<string>.Fail(error.Message);

                switch (error)
                {
                    case ApiException e:
                        //  erro customizado
                        response.StatusCode = (int)HttpStatusCode.BadRequest;
                        break;
                    case KeyNotFoundException e:
                        // erro not found
                        response.StatusCode = (int)HttpStatusCode.NotFound;
                        break;
                    default:
                        // erro não tratado
                        response.StatusCode = (int)HttpStatusCode.InternalServerError;
                        break;
                }
                var result = JsonSerializer.Serialize(responseModel);
                await response.WriteAsync(result);
            }
        }
    }
}

O componente de middleware customizado é como qualquer outra classe .NET com o método Invoke(), e para executar o próximo middleware em uma sequência, ele deve ter o parâmetro do tipo RequestDelegate no construtor.

No código implementado temos que:

- O RequestDelegate denota uma conclusão de solicitação HTTP.

- Um bloco try-catch simples sobre o delegado de solicitação indica que sempre que houver uma exceção de qualquer tipo no pipeline para a solicitação atual, o controle vai para o bloco catch. Nesse middleware, o bloco Catch tem todas as vantagens.

- O bloco catch captura todas as exceções onde as nossas exceções personalizadas são derivadas da classe base Exception.

- Criamos um modelo ApiResponse a partir da mensagem de erro usando o método Fail que criado.

- Caso a exceção detectada seja do tipo ApiException, o código de status é definido como BadRequest. As outras exceções também são tratadas de maneira semelhante.

- Ao final o modelo de resposta API criado é serializado(JsonSerializer) e enviado como uma response.

Para poder usar o middleware criado temos que incluir o mesmo no pipeline de request definindo no método Configure da classe Startup a linha de código em destaque :

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
            if (env.IsDevelopment())
            {
                //app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiGlobalError v1"));
            }

            app.UseExceptionHandler("/errors");
            app.UseHttpsRedirection(); 

            app.UseRouting();

            app.UseAuthorization();

            app.UseMiddleware<GlobalExceptionHandler.ApiExceptionHandlerMiddleware>();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
   }

Agora podemos executar o projeto e verificar o middleware customizado em ação :

Pegue o código do projeto aqui : ApiGlobalError.zip (sem as referências)

"Louvai ao SENHOR, porque ele é bom, porque a sua benignidade dura para sempre."
Salmos 118:1

Referências:


José Carlos Macoratti