最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - I can't make ProblemDetails pattern working every time - Stack Overflow

programmeradmin4浏览0评论

Trying to investigate ProblemDetails and met some issues.

My code:

using Microsoft.AspNetCore.Builder;
using WebApplication3;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseStatusCodePages();
app.UseExceptionHandler();

app.MapGet("/hello", () =>
{
    throw new Exception("Oulah. Hello API has problems!");
});

app.MapGet("/bad", () => Results.BadRequest(new
{
    message = "Very bad, you have a problem."
}));

app.Run();

As you can see there are 2 API methods - hello and bad.

My issue: I do get the expected response body with hello:

But with bad, it is a different story:

ProblemDetail is not activated.

I can't see what is different in my code than in other examples I can see.

What I need:

  1. To understand what is happening
  2. What I can do

Thank you

Trying to investigate ProblemDetails and met some issues.

My code:

using Microsoft.AspNetCore.Builder;
using WebApplication3;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseStatusCodePages();
app.UseExceptionHandler();

app.MapGet("/hello", () =>
{
    throw new Exception("Oulah. Hello API has problems!");
});

app.MapGet("/bad", () => Results.BadRequest(new
{
    message = "Very bad, you have a problem."
}));

app.Run();

As you can see there are 2 API methods - hello and bad.

My issue: I do get the expected response body with hello:

But with bad, it is a different story:

ProblemDetail is not activated.

I can't see what is different in my code than in other examples I can see.

What I need:

  1. To understand what is happening
  2. What I can do

Thank you

Share Improve this question edited Jan 19 at 11:24 marc_s 754k184 gold badges1.4k silver badges1.5k bronze badges asked Jan 19 at 11:11 Frédéric De Lène MirouzeFrédéric De Lène Mirouze 6191 gold badge7 silver badges36 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 1

It's not auto-"activated", because your current setup converts ONLY Exceptions to Problems.

If you had added:

app.UseStatusCodePages()

and removed the message

app.MapGet("/bad", () => Results.BadRequest());

you'd get the extra "activation" of requests 4xx-5xx with no body being converted to ProblemDetails.

If you want to return ProblemDetails with a message you need to do it yourself:

app.MapGet("/bad", () => 
   TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, 
   title: "Bad"));

When you use Results.BadRequest with an anonymous object, you are simply telling the controller to return a 400 status code with the json-ified object passed as an argument.

If you want the controller to return a representation of the ProblemDetails class, you can use Results.Problem (i.e. Results.Problem("This is a problem", statusCode: 400)).

When an exception is thrown inside a controller, it is automatically caught and a ProblemDetails is returned with a standard error message. Otherwise, you have to handle it manually.

Returning a bad request from a minimal endpoint does not automatically return ProblemDetails, as compared to controller-based endpoints. This is because minimal APIs don't automatically use the IProblemDetailsService for exception handling unless explicitly configured. This means that, by default, you won't get ProblemDetails returned on exceptions or when using methods like Results.BadRequest.

It's not a good practice to throw an exception from your endpoint. Instead, you can return a Problem.

return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest);

The above line of code will return a default ProblemDetails response. You can further configure ProblemDetails in your IoC Container (pipeline). For example:

builder.Services.AddProblemDetails (options =>
{
    options.CustomizeProblemDetails = context =>
    {
        context.ProblemDetails.Instance =
            $"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}";
        
        context.ProblemDetails.Extensions.TryAdd (
            "requestId",
            context.HttpContext.TraceIdentifier);
        
        var activity = context.HttpContext.Features.Get<IHttpActivityFeature>()!.Activity;
        context.ProblemDetails.Extensions.TryAdd ("traceId", activity.Id);
    };
});

If you still intend to throw an exception or wish to configure exceptions to automatically return a ProblemDetails. Please refer below code:

// Controllers are required to access ProblemDetailsFactory.
builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = context =>
    {
        context.ProblemDetails.Instance =
            $"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}";

        context.ProblemDetails.Extensions.TryAdd(
            "requestId",
            context.HttpContext.TraceIdentifier);

        var activity = context.HttpContext.Features.Get<IHttpActivityFeature>()!.Activity;
        context.ProblemDetails.Extensions.TryAdd("traceId", activity.Id);
    };
});

builder.Services.AddExceptionHandler<ProblemExceptionHandler>();

My minimal endpoint:

app.MapGet ("/exception", (HttpContext httpContext) =>  
{
    // Remember to AddControllers() to IoC Container.
    var problemDetailsFactory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
    
    // Fetch pre-configured Problem Details from IoC Container.
    var problem = problemDetailsFactory.CreateProblemDetails (
        httpContext: httpContext,
        statusCode: StatusCodes.Status400BadRequest,
        title: "An exception occurred.");

    throw new MyCustomException("An exception occured", "Hello, Exception!", problem);
});

My custom exception and global exception handler class:

public class ProblemException : Exception
{
    public string Error { get; set; }
    public new string Message { get; set; }
    public ProblemDetails Problem { get; set; }

    public ProblemException(string error, string message, ProblemDetails problem) : base(message)
    {
        Error = error;
        Message = message;
        Problem = problem;
    }
}

public class ProblemExceptionHandler : IExceptionHandler
{
    private readonly IProblemDetailsService _problemDetailsService;

    public ProblemExceptionHandler(IProblemDetailsService problemDetailsService)
    {
        _problemDetailsService = problemDetailsService;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        if (exception is not ProblemException problemException) return true;

        var problemDetails = problemException.Problem;

        Dictionary<string, object> extensions = new();
        extensions.Add ("Error", problemException.Error);
        extensions.TryAdd ("Message", problemException.Message);

        problemDetails.Extensions.TryAdd ("Details", extensions);
        
        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;

        return await _problemDetailsService.TryWriteAsync(
            new ProblemDetailsContext()
            {
                HttpContext = httpContext,
                ProblemDetails = problemDetails
            });
    }
}

I hope you find this helpful.

发布评论

评论列表(0)

  1. 暂无评论