Skip to content

ProblemDetailsServiceEndpointFilter stripping HttpValidationProblemDetails #24

Description

@kingdamo

Hi @DamianEdwards,

I'm trying to use the ProblemDetailsServiceEndpointFilter inside of this repository and it mostly works great.

When I run the API, calling /test it returns a response of:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Value1": [
      "Problem 1",
      "Problem 2"
    ]
  }
}

However if I use the /testfiltered if returns a response of:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400
}

While debugging I can see that it gets to the ProblemDetailsServiceAwareResult.ExecuteAsync method as the Microsoft.AspNetCore.Http.HttpValidationProblemDetails but then the extra validation properties are lost.

Even explicitly using that type as the Value type, it doesn't solve the problem.

Any help would be appreciated.

Regards,
Damien.

Program.cs:

using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerDocument();

builder.Services.AddProblemDetails(
);

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi3();
}

app.MapGet("/test", () =>
{
    var dict = new Dictionary<string, string[]>();
    dict.Add("Value1", new[] { "Problem 1", "Problem 2" });

    return Results.ValidationProblem(dict);
});

var general = app.MapGroup("").AddEndpointFilter(new ProblemDetailsServiceEndpointFilter());

general.MapGet("/testfiltered", () =>
{
    var dict = new Dictionary<string, string[]>();
    dict.Add("Value1", new[] { "Problem 1", "Problem 2" });

    return Results.ValidationProblem(dict);
});

app.Run();

public class ProblemDetailsServiceEndpointFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
        => await next(context) switch
        {
            ProblemHttpResult problemHttpResult => new ProblemDetailsServiceAwareResult(problemHttpResult.StatusCode, problemHttpResult.ProblemDetails),
            ProblemDetails problemDetails => new ProblemDetailsServiceAwareResult(null, problemDetails),
            { } result => result,
            null => null
        };

    private class ProblemDetailsServiceAwareResult : IResult, IValueHttpResult, IValueHttpResult<ProblemDetails>
    {
        private readonly int? _statusCode;

        public ProblemDetailsServiceAwareResult(int? statusCode, ProblemDetails problemDetails)
        {
            _statusCode = statusCode ?? problemDetails.Status;
            Value = problemDetails;
        }

        public ProblemDetails Value { get; }

        object? IValueHttpResult.Value => Value;

        public async Task ExecuteAsync(HttpContext httpContext)
        {
            if (httpContext.RequestServices.GetService<IProblemDetailsService>() is IProblemDetailsService problemDetailsService)
            {
                if (_statusCode is { } statusCode)
                {
                    httpContext.Response.StatusCode = statusCode;
                }
                await problemDetailsService.WriteAsync(new()
                {
                    HttpContext = httpContext,
                    ProblemDetails = Value
                });
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions