ASP.NET Core API - Tratamento de erros com UseExceptionHandler


Neste artigo vamos rever o tratamento de erros em uma Web API usando o middleware UseExceptionHandler.

Tratar erros é um aspecto importante no processo de desenvolvimento de aplicações e hoje vamos apresentar o comportamento do middleware UseExceptionHandler no tratamento de erros em uma Web API.

Criando a Web API ASP .NET Core

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

Informe o nome TrataErrosAPI e clique em Next;

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

A seguir vamos remover os arquivos WeatherForecastController.cs e WeatherForecast.cs do projeto criado.

Agora, Inclua um novo Controller na pasta Controllers chamado TrataErrosController com apenas um endpoint definido pelo método Get():

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
namespace TrataErrosAPI.Controllers
{
    [Route("api/[controller]")]    
    [ApiController]
    public class TrataErrosController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get() => throw new NullReferenceException("Null Reference Exception");
    }
}

Vale lembrar que estamos no ambiente de desenvolvimento e que neste momento temos definido no arquivo Startup, no método Configure o código abaixo que indica que o middleware UseDeveloperExceptionPage() irá tratar os erros neste ambiente:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TrataErrosAPI v1"));
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
 }

Executando o projeto teremos a interface do swagger exibindo a pagina abaixo:

Vamos fazer o teste para acessar o endpoint da API clicando no botão Get.

Isso vai apresentar o resultado abaixo:

Como lançamos uma NullReferenceException, a resposta obtida é um System.NullReferenceException e com isso obtemos detalhes da exceção graças ao método de extensão UseDeveloperExceptionPage que adiciona o middleware ao pipeline de solicitação que exibe a página de detalhes da exceção amigável ao desenvolvedor.

Isso ajuda os desenvolvedores a rastrear erros que ocorrem durante a fase de desenvolvimento. É aconselhável adicioná-lo apenas durante a fase de desenvolvimento, pois o middleware exibe informações confidenciais.

Vamos agora comentar a linha de código que usa o middleware UseDeveloperExceptionPage :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
            if (env.IsDevelopment())
            {
                //app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TrataErrosAPI v1"));
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
 }

E executar novamente o projeto. Fazendo isso iremos obter o resultado a seguir:

Agora temos apenas a indicação de erro :  500 Error

Realizando o tratamento de erros com UseExceptionHandler

Vamos melhorar o tratamento de erros usando o método de extensão UseExceptionHandler que adiciona um middleware ao pipeline que captura exceções, as registra e reexecuta a requisição em um pipeline alternativo.

Este middleware permite configurar uma página de tratamento de erros personalizada para o ambiente de produção realizando as seguintes tarefas:

Assim vamos simular o ambiente de produção e usar uma sobrecarga para o método UseExceptionHandler incluindo no método Configure da classe Startup a linha de código destacada :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
            if (env.IsDevelopment())
            {
                //app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TrataErrosAPI v1"));
            }
            app.UseExceptionHandler("/errors");
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
 }

No código estamos usando o argumento /errors do tipo string que vai atuar da seguinte forma: Nos bastidores, quando ocorrer uma exceção não tratada, ela será redirecionada para um método Action do controlador decorado com o atributo Route [“/ errors”].

Agora vamos criar um novo método Action no controlador TrataErrosController para tratar os erros chamado HandleErrors:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
namespace TrataErrosAPI.Controllers
{
    [Route("api/[controller]")]    
    [ApiController]
    public class TrataErrosController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get() => throw new NullReferenceException("Null Reference Exception");
        [HttpGet]
        [Route("/errors")]
        [ApiExplorerSettings(IgnoreApi = true)]
        public IActionResult HandleErrors()
        {
            return Problem("Alguma coisa errada aconteceu !!! - Acione o suporte.");
        }
    }
}

Observe que decoramos o método Action com o atributo [ApiExplorerSettings(IgnoreApi = true)] e com isso este método será ignorado pela interface do swagger pois ele vai atuar apenas para tratar os erros que serão lançados pela nossa API.

Poderíamos ter criado outro controlador que ficaria responsável por fazer o tratamento dos erros e assim teríamos um tratamento global de erros, mas para mostrar o comportamento vamos fazer isso usando apenas o método Action.

Agora ao executar o projeto novamente e acessar o método Get teremos o resultado a seguir:

Temos agora a exibição de um erro customizado onde é exibido o status e a mensagem personalizada do nosso erro.

Podemos incrementar esse tratamento de erros e capturar a exceção real usando a interface interna chamada IExceptionHandlerFeature para acessar a exceção e o caminho de requisição original em um manipulador de erro.

Vamos então alterar o código do método Action HandleErrors conforme abaixo:

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Net;
namespace TrataErrosAPI.Controllers
{
    [Route("api/[controller]")]    
    [ApiController]
    public class TrataErrosController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get() => throw new NullReferenceException("NullReferenceException");
        [HttpGet]
        [Route("/errors")]
        [ApiExplorerSettings(IgnoreApi = true)]
        public IActionResult HandleErrors()
        {
            var contextException = HttpContext.Features.Get<IExceptionHandlerFeature>();

            var responseStatusCode = contextException.Error.GetType().Name switch
            {
                "NullReferenceException" => HttpStatusCode.BadRequest, 
                _ => HttpStatusCode.ServiceUnavailable
            };
            return Problem(detail: contextException.Error.Message, statusCode: (int)responseStatusCode);
        }
    }
}

Para capturar os detalhes reais da exceção, invocamos o método get de HttpContext.Features.

Com base no tipo de erro, o código de status de resposta é personalizado. Em nosso caso, se o erro for do tipo NullReferenceException, retornaremos um BadRequest. Para todos os outros erros, o serviço estará indisponível.

Agora executando o aplicativo novamente teremos a seguinte resposta:

Note que agora o erro esta mais customizado.

Portanto, aprendemos como processar exceções não tratadas usando o middleware UseExceptionHandler. Outra maneira de lidar com exceções é criar nosso próprio middleware de tratamento de erros personalizado e usá-lo no pipeline.

Em outro artigo veremos como criar um middleware customizado para realizar o tratamento de erros.

Pegue o código do projeto aqui :  TrataErrosAPI.zip

"Não estejais inquietos por coisa alguma; antes as vossas petições sejam em tudo conhecidas diante de Deus pela oração e súplica, com ação de graças."
Filipenses 4:6

Referências:


José Carlos Macoratti