C# ASP .NET

Tratando erros em uma API ASP.NET Core com Middleware

Tratamento de erros é uma parte importante de qualquer aplicação. No ASP.NET Core, isso pode ser facilitado com o uso de Middlewares.

4 meses atrás

Toda aplicação pode gerar uma exceção, mesmo com vários testes, é impossível prever todos os possíveis cenários. Assim é imprescindível que a aplicação esteja preparada para comportamentos inesperados. Isso é obtido com a implementação de tratamento de erros.

Cada aplicação possui uma melhor forma de tratar os erros, estratégias adotadas em uma aplicação Desktop, pode não ser a ideal para uma aplicação web. E quando estamos falando de uma API ASP.NET Core, o ideal é obter isso com o uso de middlewares.

F# (F Sharp) - Fundamentos
Curso F# (F Sharp) - Fundamentos
Conhecer o curso

Tratando erros apenas com try..catch

Geralmente quando uma aplicação C# necessita tratar erros ela implementa um bloco try..catch, como abaixo:

public ActionResult<IEnumerable<Pessoa>> Get()
{
    try{
        var pessoas = _repository.GetAll();
        return Ok(pessoas);
    }
    catch (Exception)
    {
        return StatusCode(500, "Internal Server Error");
    }
}

Mas quando estamos falando de uma API ASP.NET Core, definir blocos try..catch em todas as actions gera códigos duplicados, dificultando futuras manutenções. Além de que a não implementação do bloco try..catch fará a aplicação gerar uma mensagem de erro não amigável e insegura, já que irá expor aspectos internos do código.

Para evitar este cenários podemos definir um tratamento de erro global com o uso de middleware.

Tratando erros com o middleware nativo error handling

No meu artigo sobre o pipeline do ASP.NET Core e o funcionamento dos middlewares cito a existência de um middleware fornecido pelo framework para tratamento de erros. Em alguns projetos do ASP.NET Core este middleware já vem ativado por padrão, algo que não ocorre em uma API ASP.NET Core.

Assim, neste tipo de projeto caso queira fazer uso deste middleware é necessário configurá-lo. Isso pode ser feito diretamente no método Configure da classe Startup, mas o ideal é criar um método de extensão:

public static class ErrorHandlerExtensions
{
    public static IApplicationBuilder UseErrorHandler(
      								this IApplicationBuilder appBuilder, 
      								ILoggerFactory loggerFactory)
    {
        return appBuilder.UseExceptionHandler(builder =>
        {
            builder.Run(async context =>
            {
                var exceptionHandlerFeature = context
												.Features
												.Get<IExceptionHandlerFeature>();

                if (exceptionHandlerFeature != null)
                {

                    var logger = loggerFactory.CreateLogger("ErrorHandler");
                    logger.LogError($"Error: {exceptionHandlerFeature.Error}");

                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.ContentType = "application/json";

                    var json = new
                    {
                        context.Response.StatusCode,
                        Message = "Internal Server Error",
                    };

                    await context.Response.WriteAsync(JsonConvert.SerializeObject(json));
                }
            });
        });
    }
}

E chamá-lo no método Configure:

public void Configure(IApplicationBuilder app, 
                      IWebHostEnvironment env, 
                      ILoggerFactory logger)
{
    app.UseErrorHandler(logger);

    //..
}

É importante que o error handling seja definido no início do pipeline dos middlewares, assim ele irá tratar todos os erros da aplicação.

Caso necessite/queira ter um controle maior sobre o tratamento das exceções, também é possível definir um middleware customizado.

Tratando erros com um middleware customizado

Um middleware customizado é uma classe que recebe por parâmetro um objeto RequestDelegate e define um método Invoke:

public class ErrorHandler {
    private readonly RequestDelegate _next;
    private readonly ILogger _log;

    public ErrorHandler(RequestDelegate next, ILoggerFactory log){
        this._next = next;
        this._log = log.CreateLogger("MyErrorHandler");
    }

    public async Task Invoke(HttpContext httpContext)
    {
        try{
            await _next(httpContext);
        }
        catch(Exception ex)
        {
            await HandleErrorAsync(httpContext, ex);
        }
    }

    private async Task HandleErrorAsync(HttpContext context, Exception exception)
    {
        var errorResponse = new ErrorResponse();

        errorResponse.StatusCode = HttpStatusCode.InternalServerError;
        errorResponse.Message = exception.Message;

        _log.LogError($"Error: {exception.Message}");
        _log.LogError($"Stack: {exception.StackTrace}");

        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int) errorResponse.StatusCode;
        await context.Response.WriteAsync(JsonConvert.SerializeObject(errorResponse));
    }
}

Note que nesta implementação é utilizado um bloco try..catch para interceptar qualquer exceção gerada no pipeline dos middlewares. Quando isso ocorrer, a exceção é registrada no log e uma mensagem de erro é retornada ao usuário.

Assim, como o middleware nativo, este também precisa ser registrado na classe Startup e isso será feito com um método de extensão:

public static class ErrorHandlerExtensions
{
    public static IApplicationBuilder UseMyErrorHandler(this IApplicationBuilder appBuilder)
    {
        return appBuilder.UseMiddleware<ErrorHandler>();
    }
}

Que deve ser aplicado no início do pipeline:

public void Configure(IApplicationBuilder app, 
                      IWebHostEnvironment env, 
                      ILoggerFactory logger)
{
    app.UseMyErrorHandler();

    //..
}

F# (F Sharp) - Fundamentos
Curso F# (F Sharp) - Fundamentos
Conhecer o curso

Conclusão

Devido a imprevisibilidade das exceções, elas não podem ser menosprezadas. Nenhuma aplicação está imune e ignorá-las não deve ser uma opção, mesmo que isso seja realidade em alguns projetos.

A implementação de um procedimento global com middlewares, customizado ou não, facilita (e muito) o tratamento e log das exceções da aplicação. Assim, não tenha medo os erros, importando o escopo do projeto, procure tratá-los e forneça repostas amigáveis para seus usuários.

Autor(a) do artigo

Wladimilson M. Nascimento
Wladimilson M. Nascimento

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.

Todos os artigos

Artigos relacionados Ver todos