Skip to content

Backport/pr 42384 to release/7.0 preview7 #42741

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d860878
MVC Changes
brunolins16 Jun 1, 2022
983e64e
ExceptionHandler changes
brunolins16 Jun 1, 2022
3638665
Routing changes
brunolins16 Jun 1, 2022
23eaced
Http.Extensions changes
brunolins16 Jun 1, 2022
12ad2df
Minimal APi draft changes
brunolins16 Jun 1, 2022
6e426cd
HttpResults changes
brunolins16 Jun 1, 2022
968ca33
New ProblemDetails project
brunolins16 Jun 1, 2022
ae73d9b
Using ProblemDetailsOptions in mvc
brunolins16 Jun 2, 2022
4dba616
ProblemDetails project simplification
brunolins16 Jun 2, 2022
a1bfd24
Using metadata
brunolins16 Jun 7, 2022
e53df4a
Using metadata
brunolins16 Jun 7, 2022
7b7cf46
Latest version
brunolins16 Jun 10, 2022
86da603
Removing changes
brunolins16 Jun 10, 2022
ad22115
Initial cleanup
brunolins16 Jun 10, 2022
262187d
Clean up
brunolins16 Jun 16, 2022
4de248a
Public api clean up
brunolins16 Jun 17, 2022
d81f4d9
Updating public API
brunolins16 Jun 17, 2022
e6aa8bb
More clean ups
brunolins16 Jun 17, 2022
d587e14
Adding initial unit tests
brunolins16 Jun 20, 2022
1d10344
Updating unit tests
brunolins16 Jun 21, 2022
b838b1a
Adding more unit tests
brunolins16 Jun 23, 2022
2f810e0
Cleanup
brunolins16 Jun 23, 2022
074e05c
Clean up
brunolins16 Jun 23, 2022
2a48e80
clean up
brunolins16 Jun 23, 2022
46f9ba5
clean up
brunolins16 Jun 23, 2022
6879069
Removing nullable
brunolins16 Jun 23, 2022
c4e0cc6
Simplifying public api
brunolins16 Jun 25, 2022
4ee1f40
API Review feedback
brunolins16 Jun 27, 2022
33b5131
API review feedback
brunolins16 Jun 28, 2022
7c32d0f
Clean up
brunolins16 Jun 28, 2022
b739686
Clean up
brunolins16 Jun 28, 2022
e63dce5
clean up
brunolins16 Jun 28, 2022
1b855b5
Reusing Endpoint & EndpointMetadataCollection
brunolins16 Jun 29, 2022
48a986d
Fix build issues
brunolins16 Jun 29, 2022
9280d61
Updates based on docs/Trimming.md
brunolins16 Jun 29, 2022
284ab71
Adding Functional tests
brunolins16 Jun 30, 2022
d9db491
Fix unittest
brunolins16 Jun 30, 2022
4d60630
Seal context
brunolins16 Jun 30, 2022
2626cee
Seal ProblemMetadata
brunolins16 Jun 30, 2022
aa3df02
PR Feeback
brunolins16 Jul 6, 2022
2ce4880
API Review
brunolins16 Jul 8, 2022
6bae1fa
Clean up
brunolins16 Jul 8, 2022
9402004
Fixing publicapi warnings
brunolins16 Jul 8, 2022
32b6026
Clean up
brunolins16 Jul 11, 2022
82ef5c3
PR Feedback
brunolins16 Jul 11, 2022
caa5c59
Fixing build
brunolins16 Jul 11, 2022
79f789a
Fix unit test
brunolins16 Jul 11, 2022
97046e3
Fix unit tests
brunolins16 Jul 11, 2022
6ea164c
PR review
brunolins16 Jul 11, 2022
543212b
Fixing JsonSerializationContext issues
brunolins16 Jul 11, 2022
d51af76
Adding statuscode 405
brunolins16 Jul 12, 2022
bdc889e
Update src/Http/Http.Extensions/src/ProblemDetailsDefaultWriter.cs
brunolins16 Jul 12, 2022
eaaa702
PR review
brunolins16 Jul 12, 2022
09e2183
Apply suggestions from code review
brunolins16 Jul 12, 2022
e76a277
Fixing bad merge
brunolins16 Jul 13, 2022
b9df68c
Adding analysis.NextMiddlewareName
brunolins16 Jul 13, 2022
63b400a
PR review
brunolins16 Jul 13, 2022
448d3dd
Changing to CanWrite/WriteAsync
brunolins16 Jul 13, 2022
6fd5836
Fix bad merge
brunolins16 Jul 15, 2022
6f44131
Fixing bad merge
brunolins16 Jul 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Microsoft.AspNetCore.Http.HttpResponse</Description>
<Compile Include="$(SharedSourceRoot)PropertyHelper\**\*.cs" />
<Compile Include="$(SharedSourceRoot)\UrlDecoder\UrlDecoder.cs" Link="UrlDecoder.cs" />
<Compile Include="$(SharedSourceRoot)ValueTaskExtensions\**\*.cs" />
<Compile Include="$(SharedSourceRoot)ProblemDetails\HttpValidationProblemDetailsJsonConverter.cs" LinkBase="ProblemDetails\Converters" />
<Compile Include="$(SharedSourceRoot)ProblemDetails\ProblemDetailsJsonConverter.cs" LinkBase="ProblemDetails\Converters" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Http;

/// <summary>
/// Defines a type that provide functionality to
/// create a <see cref="Mvc.ProblemDetails"/> response.
/// </summary>
public interface IProblemDetailsService
{
/// <summary>
/// Try to write a <see cref="Mvc.ProblemDetails"/> response to the current context,
/// using the registered <see cref="IProblemDetailsWriter"/> services.
/// </summary>
/// <param name="context">The <see cref="ProblemDetailsContext"/> associated with the current request/response.</param>
/// <remarks>The <see cref="IProblemDetailsWriter"/> registered services
/// are processed in sequence and the processing is completed when:
/// <list type="bullet">One of them reports that the response was written successfully, or.</list>
/// <list type="bullet">All <see cref="IProblemDetailsWriter"/> were executed and none of them was able to write the response successfully.</list>
/// </remarks>
ValueTask WriteAsync(ProblemDetailsContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Http;

/// <summary>
/// Defines a type that write a <see cref="Mvc.ProblemDetails"/>
/// payload to the current <see cref="HttpContext.Response"/>.
/// </summary>
public interface IProblemDetailsWriter
{
/// <summary>
/// Write a <see cref="Mvc.ProblemDetails"/> response to the current context
/// </summary>
/// <param name="context">The <see cref="ProblemDetailsContext"/> associated with the current request/response.</param>
ValueTask WriteAsync(ProblemDetailsContext context);

/// <summary>
/// Determines whether this instance can write a <see cref="Mvc.ProblemDetails"/> to the current context.
/// </summary>
/// <param name="context">The <see cref="ProblemDetailsContext"/> associated with the current request/response.</param>
/// <returns>Flag that indicates if that the writer can write to the current <see cref="ProblemDetailsContext"/>.</returns>
bool CanWrite(ProblemDetailsContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Http;

/// <summary>
/// Represent the current problem details context for the request.
/// </summary>
public sealed class ProblemDetailsContext
{
private ProblemDetails? _problemDetails;

/// <summary>
/// The <see cref="HttpContext"/> associated with the current request being processed by the filter.
/// </summary>
public required HttpContext HttpContext { get; init; }

/// <summary>
/// A collection of additional arbitrary metadata associated with the current request endpoint.
/// </summary>
public EndpointMetadataCollection? AdditionalMetadata { get; init; }

/// <summary>
/// An instance of <see cref="ProblemDetails"/> that will be
/// used during the response payload generation.
/// </summary>
public ProblemDetails ProblemDetails
{
get => _problemDetails ??= new ProblemDetails();
init => _problemDetails = value;
}
}
30 changes: 30 additions & 0 deletions src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Microsoft.AspNetCore.Http.EndpointFilterInvocationContext
Microsoft.AspNetCore.Http.EndpointFilterInvocationContext.EndpointFilterInvocationContext() -> void
Microsoft.AspNetCore.Http.EndpointMetadataCollection.Enumerator.Current.get -> object!
Microsoft.AspNetCore.Http.EndpointMetadataCollection.GetRequiredMetadata<T>() -> T!
Microsoft.AspNetCore.Http.HttpValidationProblemDetails
Microsoft.AspNetCore.Http.HttpValidationProblemDetails.Errors.get -> System.Collections.Generic.IDictionary<string!, string![]!>!
Microsoft.AspNetCore.Http.HttpValidationProblemDetails.HttpValidationProblemDetails() -> void
Microsoft.AspNetCore.Http.HttpValidationProblemDetails.HttpValidationProblemDetails(System.Collections.Generic.IDictionary<string!, string![]!>! errors) -> void
Microsoft.AspNetCore.Http.IBindableFromHttpContext<TSelf>
Microsoft.AspNetCore.Http.IBindableFromHttpContext<TSelf>.BindAsync(Microsoft.AspNetCore.Http.HttpContext! context, System.Reflection.ParameterInfo! parameter) -> System.Threading.Tasks.ValueTask<TSelf?>
Microsoft.AspNetCore.Http.IContentTypeHttpResult
Expand All @@ -30,8 +34,13 @@ Microsoft.AspNetCore.Http.IEndpointFilter.InvokeAsync(Microsoft.AspNetCore.Http.
Microsoft.AspNetCore.Http.IFileHttpResult
Microsoft.AspNetCore.Http.IFileHttpResult.ContentType.get -> string?
Microsoft.AspNetCore.Http.IFileHttpResult.FileDownloadName.get -> string?
Microsoft.AspNetCore.Http.IProblemDetailsService
Microsoft.AspNetCore.Http.IProblemDetailsService.WriteAsync(Microsoft.AspNetCore.Http.ProblemDetailsContext! context) -> System.Threading.Tasks.ValueTask
Microsoft.AspNetCore.Http.IProblemDetailsWriter
Microsoft.AspNetCore.Http.INestedHttpResult
Microsoft.AspNetCore.Http.INestedHttpResult.Result.get -> Microsoft.AspNetCore.Http.IResult!
Microsoft.AspNetCore.Http.IProblemDetailsWriter.CanWrite(Microsoft.AspNetCore.Http.ProblemDetailsContext! context) -> bool
Microsoft.AspNetCore.Http.IProblemDetailsWriter.WriteAsync(Microsoft.AspNetCore.Http.ProblemDetailsContext! context) -> System.Threading.Tasks.ValueTask
Microsoft.AspNetCore.Http.IStatusCodeHttpResult
Microsoft.AspNetCore.Http.IStatusCodeHttpResult.StatusCode.get -> int?
Microsoft.AspNetCore.Http.IValueHttpResult
Expand All @@ -42,6 +51,14 @@ Microsoft.AspNetCore.Http.Metadata.IFromFormMetadata
Microsoft.AspNetCore.Http.Metadata.IFromFormMetadata.Name.get -> string?
Microsoft.AspNetCore.Http.Metadata.IRequestSizeLimitMetadata
Microsoft.AspNetCore.Http.Metadata.IRequestSizeLimitMetadata.MaxRequestBodySize.get -> long?
Microsoft.AspNetCore.Http.ProblemDetailsContext
Microsoft.AspNetCore.Http.ProblemDetailsContext.AdditionalMetadata.get -> Microsoft.AspNetCore.Http.EndpointMetadataCollection?
Microsoft.AspNetCore.Http.ProblemDetailsContext.AdditionalMetadata.init -> void
Microsoft.AspNetCore.Http.ProblemDetailsContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext!
Microsoft.AspNetCore.Http.ProblemDetailsContext.HttpContext.init -> void
Microsoft.AspNetCore.Http.ProblemDetailsContext.ProblemDetails.get -> Microsoft.AspNetCore.Mvc.ProblemDetails!
Microsoft.AspNetCore.Http.ProblemDetailsContext.ProblemDetails.init -> void
Microsoft.AspNetCore.Http.ProblemDetailsContext.ProblemDetailsContext() -> void
Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(Microsoft.AspNetCore.Routing.RouteValueDictionary? dictionary) -> void
Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, object?>>? values) -> void
Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, string?>>? values) -> void
Expand All @@ -51,6 +68,19 @@ Microsoft.AspNetCore.Http.Metadata.IEndpointDescriptionMetadata
Microsoft.AspNetCore.Http.Metadata.IEndpointDescriptionMetadata.Description.get -> string!
Microsoft.AspNetCore.Http.Metadata.IEndpointSummaryMetadata
Microsoft.AspNetCore.Http.Metadata.IEndpointSummaryMetadata.Summary.get -> string!
Microsoft.AspNetCore.Mvc.ProblemDetails
Microsoft.AspNetCore.Mvc.ProblemDetails.Detail.get -> string?
Microsoft.AspNetCore.Mvc.ProblemDetails.Detail.set -> void
Microsoft.AspNetCore.Mvc.ProblemDetails.Extensions.get -> System.Collections.Generic.IDictionary<string!, object?>!
Microsoft.AspNetCore.Mvc.ProblemDetails.Instance.get -> string?
Microsoft.AspNetCore.Mvc.ProblemDetails.Instance.set -> void
Microsoft.AspNetCore.Mvc.ProblemDetails.ProblemDetails() -> void
Microsoft.AspNetCore.Mvc.ProblemDetails.Status.get -> int?
Microsoft.AspNetCore.Mvc.ProblemDetails.Status.set -> void
Microsoft.AspNetCore.Mvc.ProblemDetails.Title.get -> string?
Microsoft.AspNetCore.Mvc.ProblemDetails.Title.set -> void
Microsoft.AspNetCore.Mvc.ProblemDetails.Type.get -> string?
Microsoft.AspNetCore.Mvc.ProblemDetails.Type.set -> void
override Microsoft.AspNetCore.Http.DefaultEndpointFilterInvocationContext.Arguments.get -> System.Collections.Generic.IList<object?>!
override Microsoft.AspNetCore.Http.DefaultEndpointFilterInvocationContext.GetArgument<T>(int index) -> T
override Microsoft.AspNetCore.Http.DefaultEndpointFilterInvocationContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext!
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Text.Json;
using Microsoft.AspNetCore.Http.Json;

namespace Microsoft.AspNetCore.Http.Extensions;
namespace Microsoft.AspNetCore.Http.Abstractions.Tests;

public class HttpValidationProblemDetailsJsonConverterTest
{
Expand Down Expand Up @@ -40,7 +40,7 @@ public void Read_Works()
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
Assert.Equal(traceId, kvp.Value?.ToString());
});
Assert.Collection(
problemDetails.Errors.OrderBy(kvp => kvp.Key),
Expand Down Expand Up @@ -81,7 +81,7 @@ public void Read_WithSomeMissingValues_Works()
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
Assert.Equal(traceId, kvp.Value?.ToString());
});
Assert.Collection(
problemDetails.Errors.OrderBy(kvp => kvp.Key),
Expand Down Expand Up @@ -111,15 +111,16 @@ public void ReadUsingJsonSerializerWorks()
// Act
var problemDetails = JsonSerializer.Deserialize<HttpValidationProblemDetails>(json, JsonSerializerOptions);

Assert.Equal(type, problemDetails.Type);
Assert.NotNull(problemDetails);
Assert.Equal(type, problemDetails!.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Assert.Collection(
problemDetails.Extensions,
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
Assert.Equal(traceId, kvp.Value?.ToString());
});
Assert.Collection(
problemDetails.Errors.OrderBy(kvp => kvp.Key),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Http.Extensions;
namespace Microsoft.AspNetCore.Http.Abstractions.Tests;

public class ProblemDetailsJsonConverterTest
{
Expand Down Expand Up @@ -46,6 +46,7 @@ public void Read_Works()
// Act
var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);

//Assert
Assert.Equal(type, problemDetails.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Expand All @@ -56,7 +57,7 @@ public void Read_Works()
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
Assert.Equal(traceId, kvp.Value?.ToString());
});
}

Expand All @@ -75,7 +76,9 @@ public void Read_UsingJsonSerializerWorks()
// Act
var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(json, JsonSerializerOptions);

Assert.Equal(type, problemDetails.Type);
// Assert
Assert.NotNull(problemDetails);
Assert.Equal(type, problemDetails!.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Assert.Equal(instance, problemDetails.Instance);
Expand All @@ -85,7 +88,7 @@ public void Read_UsingJsonSerializerWorks()
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
Assert.Equal(traceId, kvp.Value?.ToString());
});
}

Expand All @@ -105,6 +108,7 @@ public void Read_WithSomeMissingValues_Works()
// Act
var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);

// Assert
Assert.Equal(type, problemDetails.Type);
Assert.Equal(title, problemDetails.Title);
Assert.Equal(status, problemDetails.Status);
Expand All @@ -113,7 +117,7 @@ public void Read_WithSomeMissingValues_Works()
kvp =>
{
Assert.Equal("traceId", kvp.Key);
Assert.Equal(traceId, kvp.Value.ToString());
Assert.Equal(traceId, kvp.Value?.ToString());
});
}

Expand Down
64 changes: 64 additions & 0 deletions src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Http;

internal sealed partial class DefaultProblemDetailsWriter : IProblemDetailsWriter
{
private static readonly MediaTypeHeaderValue _jsonMediaType = new("application/json");
private static readonly MediaTypeHeaderValue _problemDetailsJsonMediaType = new("application/problem+json");
private readonly ProblemDetailsOptions _options;

public DefaultProblemDetailsWriter(IOptions<ProblemDetailsOptions> options)
{
_options = options.Value;
}

public bool CanWrite(ProblemDetailsContext context)
{
var httpContext = context.HttpContext;
var acceptHeader = httpContext.Request.Headers.Accept.GetList<MediaTypeHeaderValue>();

if (acceptHeader?.Any(h => _jsonMediaType.IsSubsetOf(h) || _problemDetailsJsonMediaType.IsSubsetOf(h)) == true)
{
return true;
}

return false;
}

[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "JSON serialization of ProblemDetails.Extensions might require types that cannot be statically analyzed and we need to fallback" +
"to reflection-based. The ProblemDetailsConverter is marked as RequiresUnreferencedCode already.")]
public ValueTask WriteAsync(ProblemDetailsContext context)
{
var httpContext = context.HttpContext;
ProblemDetailsDefaults.Apply(context.ProblemDetails, httpContext.Response.StatusCode);
_options.CustomizeProblemDetails?.Invoke(context);

if (context.ProblemDetails.Extensions is { Count: 0 })
{
// We can use the source generation in this case
return new ValueTask(httpContext.Response.WriteAsJsonAsync(
context.ProblemDetails,
ProblemDetailsJsonContext.Default.ProblemDetails,
contentType: "application/problem+json"));
}

return new ValueTask(httpContext.Response.WriteAsJsonAsync(
context.ProblemDetails,
options: null,
contentType: "application/problem+json"));
}

[JsonSerializable(typeof(ProblemDetails))]
internal sealed partial class ProblemDetailsJsonContext : JsonSerializerContext
{ }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
<Compile Include="$(SharedSourceRoot)ParameterBindingMethodCache.cs" LinkBase="Shared"/>
<Compile Include="$(SharedSourceRoot)PropertyAsParameterInfo.cs" LinkBase="Shared"/>
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)ProblemDetailsJsonConverter.cs" LinkBase="Shared"/>
<Compile Include="$(SharedSourceRoot)HttpValidationProblemDetailsJsonConverter.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
<Compile Include="$(SharedSourceRoot)TypeNameHelper\TypeNameHelper.cs" LinkBase="Shared"/>
<Compile Include="$(SharedSourceRoot)ProblemDetails\ProblemDetailsDefaults.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
Expand Down
16 changes: 16 additions & 0 deletions src/Http/Http.Extensions/src/ProblemDetailsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Http;

/// <summary>
/// Options for controlling the behavior of <see cref="IProblemDetailsService.WriteAsync(ProblemDetailsContext)"/>
/// and similar methods.
/// </summary>
public class ProblemDetailsOptions
{
/// <summary>
/// The operation that customizes the current <see cref="Mvc.ProblemDetails"/> instance.
/// </summary>
public Action<ProblemDetailsContext>? CustomizeProblemDetails { get; set; }
}
Loading