No último artigo falamos de tratamento de exceções usando um Middeware, que é um recurso muito útil e versátil.
Agora no .NET 8 temos um recurso mais simples e poderoso chamado Global Error Handler, que faz basicamente o que o Middleware de Exception do nosso último artigo, só que de maneira um tanto mais simples e eficiente.
Como usar o Global Error Handler
Eiste muita maneiras de usar o recurso, mas basicamente você implementa uma classe que herda da interface IExceptionHandler :
public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
// seu código aqui
return true;
}
}
Depois adiciona esta classe no program.cs usando o método AddExceptionHandler() e depois o UseExceptionHandler(). Estou adicionando também o ProblemDetails(), que usamos mais adiante.
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSerilog(s => s.WriteTo.Console());
builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); // aqui
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
app.UseExceptionHandler(); // e aqui
app.Run();
}
Adicionando o tratamento de Erros completo
Pegando a rotina de montagem de Erros do nosso último artigo, o código ficaria assim:
using CapturaErros.DTO;
using Microsoft.AspNetCore.Diagnostics;
using System.Net;
using static System.Net.Mime.MediaTypeNames;
namespace CapturaErros.ExceptionHandler
{
public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var _modelErros = new List<ModelErrors>();
MontaMensagemErro(_modelErros, exception);
var errorJson = System.Text.Json.JsonSerializer.Serialize(_modelErros);
logger.LogError(errorJson);
httpContext.Response.ContentType = Application.Json;
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await httpContext.Response.WriteAsJsonAsync(errorJson, cancellationToken).ConfigureAwait(false);
return true;
}
private void MontaMensagemErro(List<ModelErrors> _modelErros, Exception ex)
{
_modelErros.Add(new ModelErrors { Mensagem = ex.Message, CodeTrace = ex.StackTrace });
if (ex.InnerException != null)
{
MontaMensagemErro(_modelErros, ex.InnerException);
}
}
}
}
Veja que agora não temos mais o try..catch do Middeware, pois o erro já vem para nós no método TryHandleAsync na propriedade Exception, e podemos fazer os devidos tratamentos, escrevendo ao final o retorno para o usuário.
Adicionando o ProblemDetails
Agora vamos a uma pequena variação deste mesmo código adicionando um novo elemento chamado ProblemDetails. Esta nova classe serve para devolvermos os erros para o usuário de uma maneira mais padronizada. Para isto no program.cs você deve ter reparado o método:
builder.Services.AddProblemDetails();
Este método adiciona os dados necessários para trabalharmos com o ProblemDetails. Veja agora um novo método para tratamento de exceção:
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using static System.Net.Mime.MediaTypeNames;
using System.Net;
namespace CapturaErros.ExceptionHandler
{
public class GlobalExceptionHandlerProblemDetail(ILogger<GlobalExceptionHandlerProblemDetail> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var _pd = new List<ProblemDetails>();
MontaMensagemErroPD(_pd, httpContext.Request.Path.ToString(), exception);
var errorJson = System.Text.Json.JsonSerializer.Serialize(_pd);
logger.LogError("{ProblemDetails}", errorJson);
httpContext.Response.ContentType = Application.Json;
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await httpContext.Response.WriteAsJsonAsync(errorJson, cancellationToken).ConfigureAwait(false);
return true;
}
private void MontaMensagemErroPD(List<ProblemDetails> _pd, string pathString, Exception ex)
{
// aqui montamos o ProblemDetails
_pd.Add(new ProblemDetails{ Instance = pathString, Title = ex.Message, Detail = ex.StackTrace });
if (ex.InnerException != null)
{
MontaMensagemErroPD(_pd, pathString, ex.InnerException);
}
}
}
}
Você deve notar que estamos preenchendo a classe ProblemDetails com 3 informações:
- Instance = local onde ocorreu o erro
- Title = título do erro, no nosso caso, a exceção
- Details = detalhes, aqui eu coloquei o stackTrace do código, mas você pode colocar qualquer outra informação.
Por fim, o resultado final não muda muito, pois continuamos tratando as exceções no método MontaMensagemErroPD para pegar toda a árvore de exceções.
Para usar esta nova classe de erros, no nosso program.cs, precisamos modificar o Exception Handler para este novo:
builder.Services.AddControllers();
builder.Services.AddSerilog(s => s.WriteTo.Console());
builder.Services.AddExceptionHandler<GlobalExceptionHandlerProblemDetail>(); // aqui
builder.Services.AddProblemDetails();
PS: Aqui eu usei o ProblemDetails da mesma maneira que a classe ModelErrors, pois eu quero pegar o StackTrace, mas você pode simplificar o código, eliminando esta última parte (StackTrace), deixando o retorno mais parecido com o informado na documentação.
Considerações
Este novo modelo de tratamento de erros deixa o código mais simples e permite vários outras maneiras de gerenciar os erros da nossa aplicação!
Vale a pena ler cuidadosamente a documentação oficial da Microsoft.
O código fonte completo da aplicaçao está no meu GitHub.
Abraços e até a próxima!