O tratamento global de exceções é um aspecto essencial no desenvolvimento de APIs REST robustas. Neste artigo, veremos como implementar essa abordagem em aplicações ASP.NET, garantindo que erros não tratados sejam capturados e gerenciados de forma centralizada. Esse método permite oferecer respostas padronizadas aos usuários, evitar a exposição de detalhes sensíveis e manter a aplicação funcionando de maneira controlada, mesmo diante de falhas inesperadas.
Caso queira saber como tratar exceções em partes específicas do código, você pode acessar nosso artigo sobre Tratamento de exceções em C#.
O que é o tratamento global de exceções?
O tratamento global de exceções é a prática de capturar exceções não tratadas em um único ponto da aplicação, garantindo que:
- O usuário receba respostas amigáveis e consistentes.
- O sistema registre logs para diagnóstico de erros.
- Exceções críticas sejam tratadas sem comprometer a estabilidade da aplicação.
No ASP.NET Core, isso é feito por meio de middlewares, que interceptam requisições HTTP no pipeline, permitindo a captura de erros de forma centralizada.
Criando um middleware personalizado
Os middlewares são componentes no pipeline de requisições do ASP.NET Core que processam solicitações e respostas HTTP. Cada middleware pode executar lógica antes ou depois de passar o controle para o próximo middleware na cadeia. Agora, vamos criar um middleware para capturar exceções, retornar mensagens amigáveis e garantir que os códigos HTTP sejam apropriados.
Classe base do middleware
Todo middleware no ASP.NET Core precisa de um “RequestDelegate”, que representa o próximo passo no pipeline de execução. Isso garante que, após o middleware executar sua lógica, ele possa passar a solicitação para o próximo middleware registrado, mantendo a cadeia de execução fluida. Para isso, crie uma classe que podemos dar o nome de “ExceptionHandlingMiddleware”:
using Microsoft.AspNetCore.Http;
using System.Net;
using System.Text.Json;
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
//Continua aqui
}
Aqui, estamos recebendo o próximo middleware via injeção no construtor e armazenando sua referência na variável “_next”.
Agora vamos implementar o método “InvokeAsync” que é obrigatório em um middleware e define o comportamento do middleware ao processar a requisição HTTP.
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var errorResponse = new { Message = "Ocorreu um erro inesperado." };
await context.Response.WriteAsJsonAsync(errorResponse); }
}
No código acima, a execução do próximo middleware é envolvida em um bloco “try-catch” para capturar qualquer exceção que ocorra durante o processamento da requisição. Caso uma exceção seja gerada, o middleware define o código de status HTTP da resposta como “500”, indicando que ocorreu um erro interno no servidor.
Em seguida, define o tipo de conteúdo da resposta como JSON, já que a mensagem de erro será retornada nesse formato. A linha “var errorResponse = new { Message = “Ocorreu um erro inesperado.” };” cria um objeto anônimo com uma mensagem simples informando sobre o erro.
Por fim, envia a resposta ao cliente no formato JSON, garantindo que ele receba uma mensagem de erro controlada e compreensível.
Registrando o middleware no pipeline de requisições
Depois de criar o middleware personalizado, o próximo passo é registrá-lo no pipeline de requisições do ASP.NET Core. Isso pode ser feito no arquivo “Program.cs”:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMiddleware();
app.MapControllers();
app.Run();
No código acima, registramos o middleware personalizado no pipeline de requisições do ASP.NET Core, garantindo que ele seja executado durante o processamento das requisições HTTP. Com isso, qualquer erro que ocorrer durante a execução da aplicação será automaticamente tratado pelo nosso middleware personalizado.
Exemplo:
Personalizando respostas de erro
A resposta padrão criada no exemplo anterior é simples e atende casos genéricos. No entanto, em uma aplicação real, é importante fornecer mais informações estruturadas sem comprometer a segurança. Por exemplo, você pode incluir o código de erro específico para identificar facilmente a natureza do problema. Uma mensagens específicas para diferentes tipos de exceções, como “NotFoundException”, “ValidationException” etc.
Vamos criar um exemplo aprimorado que inclui tratamento específico para exceções personalizadas:
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
context.Response.ContentType = "application/json";
var statusCode = ex switch
{
ArgumentNullException => (int)HttpStatusCode.BadRequest,
KeyNotFoundException => (int)HttpStatusCode.NotFound,
_ => (int)HttpStatusCode.InternalServerError
};
context.Response.StatusCode = statusCode;
var errorResponse = new
{
Message = ex.Message,
Details = _env.IsDevelopment() ? ex.StackTrace : ""
};
await context.Response.WriteAsJsonAsync(errorResponse);
}
}
Tratando exceções com o IExceptionHandler
Uma alternativa ao método ensinado acima é a utilização do “IExceptionHandler” que foi introduzido no ASP.NET Core 8 trazendo melhorias no tratamento global de exceções. Esta abordagem oferece uma alternativa simples e flexível de configurar o gerenciamento de erros em um middleware dedicado, permitindo respostas consistentes e centralizadas.
O método TryHandleAsync
Ao implementar um middleware que herda da interface “IExceptionHandler”, é necessário sobrescrever o método “TryHandleAsync”. Este método tenta lidar com a exceção fornecida e determina se a exceção foi manipulada retornando “true” que indica que a exceção foi tratada e nenhum outro manipulador deve ser chamado ou “false” que permite que outro manipulador seja chamado para lidar com a exceção.
A assinatura do método é a seguinte:com cenários de cancelamento assíncrono.
ValueTask TryHandleAsync(HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken);
- httpContext: permite acessar o contexto HTTP atual para configurar a resposta.
- exception: a exceção que está sendo manipulada.
- cancellationToken: um token para lidar
Implementação de um manipulador global de exceções
Para entendermos melhor o seu funcionamento, vamos criar um middleware que vai tratar as exceções de forma global.
internal sealed class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger _logger;
public GlobalExceptionHandler(ILogger logger)
{
_logger = logger;
}
public async ValueTask TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "Erro interno: {Message}", exception.Message);
var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "Erro interno no servidor",
Detail = "Um erro inesperado ocorreu. Por favor, tente novamente mais tarde."
};
httpContext.Response.StatusCode = problemDetails.Status.Value;
await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
return true;
}
}
O código acima implementa o método “TryHandleAsync”, que é responsável por gerenciar exceções de forma centralizada. Ele registra a exceção no log usando “_logger.LogError” para facilitar o diagnóstico e cria um objeto “ProblemDetails” com informações estruturadas, incluindo o status HTTP 500, um título descritivo e uma mensagem detalhada para o cliente. Em seguida, o método configura o código de status da resposta e utiliza “WriteAsJsonAsync” para enviar o objeto “ProblemDetails” como resposta JSON. Por fim, retorna “true” para indicar que a exceção foi tratada com sucesso, prevenindo a execução de outros manipuladores.
Configurando o IExceptionHandler
Para usar “IExceptionHandler”, é necessário realizar algumas configurações no “Program.cs”, como:
Adicionar suporte ao uso do formato “ProblemDetails” para respostas de erro:
builder.Services.AddProblemDetails();
Registrar os serviços no contêiner de dependência:
builder.Services.AddExceptionHandler();
Adicionar o middleware ao pipeline de requisição:
app.UseExceptionHandler();
Com isso, o tratamento global de exceções utilizando o “IExceptionHandler” estará devidamente configurado e integrado ao pipeline de requisições da aplicação
Benefícios do tratamento global de exceções
Implementar um tratamento global de exceções oferece várias vantagens:
Centralização: todo o tratamento de erros não previstos é gerenciado em um único local.
Consistência: as respostas de erro são padronizadas, facilitando a experiência do cliente.
Manutenção: qualquer modificação no tratamento de exceções pode ser feita centralmente, reduzindo o esforço de manutenção.
Acelere a sua carreira conosco!
Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui
Conclusão
O tratamento global de exceções no ASP.NET Core é crucial para melhorar a resiliência e a segurança da aplicação. Ao utilizar middlewares personalizados, você garante respostas consistentes e a proteção contra a exposição de dados sensíveis. Essa prática não só melhora a experiência do usuário, mas também facilita o diagnóstico de erros e a manutenção do sistema. Implemente este modelo e ajuste-o conforme as necessidades do seu projeto.