ASP.NET Core - Usando a classe ProblemDetails - II


Hoje continuar a apresentar a classe ProblemDetails e mostrar como usá-la na ASP .NET Core.

Continuando o artigo anterior vamos ver agora uma implementação de Problem Details usando o pacote Hellang.Middleware.ProblemDetails criado por Kristian Hellang que é um middleware que mapeia exceções para o Problem Details.

Usando Problem Details na ASP .NET Core

O middleware ProblemDetails de Kristian Hellang trata as exceções que ocorrem no pipeline de processamento do request e converte os detalhes da exceção para o formato definido para o Problem Details na RFC 8707.

Para trabalhar com ProblemDetails, vamos instalar o pacote Nuget Hellang.Middleware.ProblemDetails em nosso projeto.

Vamos criar um novo projeto usando o template ASP .NET Core Web API no VS 2022 chamado API_ProblemDetails segundo as configurações definidas a seguir:

Observe que vamos criar uma Web API usando controladores, sem usar as Minimal APIs, para focar na implementação do ProblemDetails.

Ao final teremos na janela Solution Explorer o projeto padrão criado com a estrutura a seguir:

Vamos agora instalar o pacote Hellang.Middleware.ProblemDetails no projeto usando o comando abaixo na janela do Package Manager Console do Visual Studio

Install-Package Hellang.Middleware.ProblemDetails

A seguir vamos configurar o middleware que acabamos de instalar. Geralmente isso era feito na classe Startup mas como no novo template essa classe não é mais criada então vamos realizar a configuração no arquivo Program.

1- Vamos registrar os serviços do middleware no contêiner DI usando o método AddProblemDetails();
2- Vamos incluir o middleware no pipeline usando o método UseProblemDetails();

Abaixo temos o código configurando o Middleware:

using Hellang.Middleware.ProblemDetails;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseProblemDetails();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();  

Com isso agora temos que as respostas de não exceção são convertidas em instâncias de ProblemDetails quando qualquer uma das seguintes condições for verdadeira:

Testando a implementação

Para testar a nossa implementação vamos incluir um novo método Get() no controlador WeatherForecast do projeto onde vamos lançar uma exceção retornando um BadRequest();

[HttpGet("{x}")]
public ActionResult Get(int x)
{
     int i = 0;
     try
     {
          i = 10 / x;
      }
      catch (Exception ex)
      {
            return BadRequest();
       }
       return Ok(i);
}

Neste código, se o método Get receber um valor igual a 0 será lançada uma exceção capturada no bloco try/catch onde retornamos um BadRequest(). Executando projeto teremos o seguinte resultado:

Informando o valor 0 como parâmetro para o método Get teremos o resultado a seguir:



Podemos também criar uma instância da classe Microsoft.AspNetCore.Mvc.ProblemDetails e passá-la de volta ao cliente. Para isso teremos que alterar o código do método Get conforme o código mostrado abaixo:

        [HttpGet("{x}")]
        public ActionResult Get(int x)
        {
            int i = 0;
            try
            {
                i = 10 / x;
            }
            catch (Exception ex)
            {
                var problemDetails = new ProblemDetails
                {
                    Status = StatusCodes.Status400BadRequest,
                    Type = "https://example.com/divisionbyzero",
                    Title = "Divisão por zero...",
                    Detail = ex.StackTrace,
                    Instance = HttpContext.Request.Path
                };
                return BadRequest(problemDetails);

            }
            return Ok(i);
        }

 

O resultado da execução será o seguinte:

Além disso, podemos personalizar o comportamento do middleware ProblemDetails usando ProblemDetailsOptions na configuração do serviço :

...
builder.Services.AddProblemDetails(opts => {
    opts.IncludeExceptionDetails = (context, ex) =>
    {
        var environment = context.RequestServices.GetRequiredService<IHostEnvironment>();
        return environment.IsDevelopment();

    };
});
...

Nesta opção de configuração estamos garantindo que os detalhes da exceção sejam incluídos apenas se você estiver no ambiente de desenvolvimento.

Se você desejar poderá também estender a classe ProblemDetails criando uma classe de exceções personalizada, veja abaixo um exemplo para uma exceção chamada CategoriaCustomException que herda de Exception e estende ProblemDetails:

public class CategoriaCustomException : Exception
{
    public string AdditionalInfo { get; set; }
    public string Type { get; set; }
    public string Detail { get; set; }
    public string Title { get; set; }
    public string Instance { get; set; }

    public CategoriaCustomException(string instance)
    {
        Type = "categoria-custom-exception";
        Detail = "Ocorreu um erro ao tratar a categoria";
        Title = "Categoria Exception";
        AdditionalInfo = "Tente acessar o serviço mais tarde...";
        Instance = instance;
    }
}

Dessa forma vimos que é importante padronizar o tratamento de erros das exceções em nossas APIs e podemos fazer isso usando a classe ProblemDetails e temos também a alternativa de usar o middleware que implementa os mesmos recursos.

Em outro artigo iremos tratar com mais profundidade este importante recurso.

E estamos conversados...

"Pois estou convencido de que nem morte nem vida, nem anjos nem demônios, nem o presente nem o futuro, nem quaisquer poderes,nem altura nem profundidade, nem qualquer outra coisa na criação será capaz de nos separar do amor de Deus que está em Cristo Jesus, nosso Senhor."
Romanos 8:38,39


Referências:


José Carlos Macoratti