-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Allow consistent Problem Details
generation
#42212
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
Comments
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
Problem Details Middleware does a lot (if not all?) of this already and seems a lot less complex than the proposal here. Perhaps the work there could be piggybacked on some how? It definitely would be nice to not need a reference to a 3rd party to get some of what that library offers |
@pinkfloydx33 Thanks for the feedback. I'd like to know why do you think this proposal is complex? The usage will be just a call for Also, just to let you know I don't want to introduce a middleware because it doesn't compose with the other middlewares. As example, in my proposal, the ExceptionMiddleware will behave differently when the service is available instead of have another middleware responsible for do the same. |
I agree, this doesn't look complicated (though I haven't dug in deeply as yet). cc @khellang for feedback. |
Woah, finally 😅 This will most certainly kill my middleware (which I don't necessarily mind) if done right. I see the current proposal doesn't mention the existing What about a quick way to configure mapping of exceptions to different types of problem details? Is that something you've considered? I'm on the phone right now, but I'll take a deeper look and see if I can come up with some additional feedback later 👌 |
Sorry, "complex" may have been the wrong word. The middleware I cite makes use of I'm using the middleware both in my APIs and in my YARP-backed API gateway (to catch anything that may slip through and to augment 50x statuses), where I've also got Custom StatusCode pages / error pages and have wired everything together to get a nice problem details response in 100% of cases. That said, I did have to reinvent content negotiation in the custom-status-code-page to ensure my gateway returns a nice html page when expected and a problem response otherwise. So that part of the proposal appeals to me since I'd likely be able to remove the hack. If the goal is to not have this as a middleware, I guess I understand the need for the extra interfaces. It just seems weird this can't be a couple extra options and some tweaks to No matter the solution, I welcome this. I am a very satisfied user of @khellang's project and have it included by default in our internal templates. It would definitely be nice to have something like it in-box (Thinking on it a bit more, I suppose without a middleware you need some mechanism to convert general error responses into problem details, somewhere higher up in the stack where the writer can be injected/available and likely optimized... it's making a bit more sense ). |
I am not sure if you have a chance to see the detailed design but problemdetailsfactory will be use to handle APi controllers as well but since the idea is to create something that does not rely on MVC the default That said, those are all good feedback and will make me review the proposal this week. Thanks. |
Yeah, my understanding is that this is kinda "repurposing" the error handler middleware to produce I still think the proposal needs a few more knobs before you'll be able to make the switch and keep the same functionality. Configuring different mapping functions for different exception types is one of them. |
@khellang the In my initial proposal I included the public class ExceptionHandlerOptions
{
+ public Action<IExceptionHandlerFeature, ProblemDetails>? ConfigureDetails { get; set; }
} I prefer not include it in my final proposal, and make it follow up issue, but I will talk about it during the API review. Also, it potentially could be similar to your middleware Since you have been working in your middleware for while I really appreciate your feedback. |
Yeah, it makes sense to scope exception mapping to the error handler middleware, since it's the one actually handling the exceptions, but I also see it as a way to misconfigure stuff. What if you try to set |
It will be a no-op, since the service is not registered (or if registered and not Exceptions allowed) the exception handler will never produce a BTW, if you call |
API review notes:
@brunolins16 will update the proposal for either email or next review |
Updated API Proposal: namespace Microsoft.Extensions.DependencyInjection;
+public static class ProblemDetailsServiceCollectionExtensions
+{
+ public static IServiceCollection AddProblemDetails(this IServiceCollection services) { }
+ public static IServiceCollection AddProblemDetails(this IServiceCollection services, Action<ProblemDetailsOptions> configureOptions) +{}
+} namespace Microsoft.AspNetCore.Http;
+public class ProblemDetailsOptions
+{
+ public ProblemTypes AllowedProblemTypes { get; set; } = ProblemTypes.All;
+ public Action<HttpContext, ProblemDetails>? ConfigureDetails { get; set; }
+}
+[Flags]
+public enum ProblemDetailsTypes: uint
+{
+ Unspecified = 0,
+ Server = 1,
+ Routing = 2,
+ Client = 4,
+ All =Server | Routing | Client ,
+}
+public class ProblemDetailsContext
+{
+ public ProblemDetailsContext(HttpContext httpContext) {}
+ public HttpContext HttpContext { get; }
+ public EndpointMetadataCollection? AdditionalMetadata { get; init; }
+ public ProblemDetails ProblemDetails { get; init; } = new ProblemDetailt();
+}
+public interface IProblemDetailsWriter
+{
+ ValueTask<bool> WriteAsync(ProblemDetailsContext context);
+}
+public interface IProblemDetailsService
+{
+ ValueTask WriteAsync(ProblemDetailsContext context);
+}
namespace Microsoft.AspNetCore.Http.Metadata;
+public interface IProblemDetailsMetadata
+{
+ public int? StatusCode { get; }
+ public ProblemTypes ProblemType { get; }
+} namespace Microsoft.AspNetCore.Diagnostics;
public class ExceptionHandlerMiddleware
{
+ public ExceptionHandlerMiddleware(
+ RequestDelegate next,
+ ILoggerFactory loggerFactory,
+ IOptions<ExceptionHandlerOptions> options,
+ DiagnosticListener diagnosticListener,
+ IProblemDetailsService? problemDetailsService) {}
}
public class DeveloperExceptionPageMiddleware
{
+ public DeveloperExceptionPageMiddleware(
+ RequestDelegate next,
+ IOptions<DeveloperExceptionPageOptions> options,
+ ILoggerFactory loggerFactory,
+ IWebHostEnvironment hostingEnvironment,
+ DiagnosticSource diagnosticSource,
+ IEnumerable<IDeveloperPageExceptionFilter> filters,
+ IProblemDetailsService? problemDetailsService) {}
} |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
API Review Notes:
API Approved! namespace Microsoft.Extensions.DependencyInjection;
+public static class ProblemDetailsServiceCollectionExtensions
+{
+ public static IServiceCollection AddProblemDetails(this IServiceCollection services) { }
+ public static IServiceCollection AddProblemDetails(this IServiceCollection services, Action<ProblemDetailsOptions>? configure) { }
+}
namespace Microsoft.AspNetCore.Http;
+public class ProblemDetailsOptions
+{
+ public Action<ProblemDetailsContext>? CustomizeProblemDetails { get; set; }
+}
+public class ProblemDetailsContext
+{
+ public required HttpContext HttpContext { get; init; }
+ public EndpointMetadataCollection? AdditionalMetadata { get; init; }
+ public ProblemDetails ProblemDetails { get; init; } = new ProblemDetails();
+}
+public interface IProblemDetailsWriter
+{
+ ValueTask<bool> TryWriteAsync(ProblemDetailsContext context);
+}
+public interface IProblemDetailsService
+{
+ ValueTask WriteAsync(ProblemDetailsContext context);
+} |
During the PR review was found that we could have a small memory allocation improvement (avoiding multiple async state machines) if we do the following API change comparing with the approved API: public interface IProblemDetailsWriter
{
- ValueTask<bool> TryWriteAsync(ProblemDetailsContext context);
+ ValueTask WriteAsync(ProblemDetailsContext context);
+ bool CanWrite(ProblemDetailsContext context);
} |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
Quick API review notes:
API with update approved! namespace Microsoft.Extensions.DependencyInjection;
+public static class ProblemDetailsServiceCollectionExtensions
+{
+ public static IServiceCollection AddProblemDetails(this IServiceCollection services) { }
+ public static IServiceCollection AddProblemDetails(this IServiceCollection services, Action<ProblemDetailsOptions>? configure) { }
+}
namespace Microsoft.AspNetCore.Http;
+public class ProblemDetailsOptions
+{
+ public Action<ProblemDetailsContext>? CustomizeProblemDetails { get; set; }
+}
+public class ProblemDetailsContext
+{
+ public required HttpContext HttpContext { get; init; }
+ public EndpointMetadataCollection? AdditionalMetadata { get; init; }
+ public ProblemDetails ProblemDetails { get; init; } = new ProblemDetails();
+}
+public interface IProblemDetailsWriter
+{
+ bool CanWrite(ProblemDetailsContext context);
+ ValueTask WriteAsync(ProblemDetailsContext context);
+}
+public interface IProblemDetailsService
+{
+ ValueTask WriteAsync(ProblemDetailsContext context);
+} |
So what would be the recommended method for customizing the Problem Details based on caught exceptions? Assuming we're using the Exception handling middleware, would we just check for the |
@pinkfloydx33 Exactly, and you can do this on the public class SamppleExceptionWriter : IProblemDetailsWriter
{
public bool CanWrite(ProblemDetailsContext context)
=> context.HttpContext.Response.StatusCode >= 500 &&
context.HttpContext.Features.Get<IExceptionHandlerFeature>() is not null;
public ValueTask WriteAsync(ProblemDetailsContext context)
{
var httpContext = context.HttpContext;
// Customize you PD (if you want you need to explicit call the CustomizeProblemDetails here)
// Write to the response
return new ValueTask(httpContext.Response.WriteAsJsonAsync(context.ProblemDetails));
}
} Just to let you know, I am planning to create a new issue to propose changes, in .NET 8, to the |
Cool, thanks for the example. That's pretty much what I was thinking. The trick with the writers however will be ensuring you add them in the correct order since it's the first |
Background and Motivation
API Controllers have a mechanism to auto generated
Problem Details
(https://datatracker.ietf.org/doc/html/rfc7807) for API ControllerActionResult
. The mechanism is enabled by default for allAPI Controllers
, however, it will only generates aProblem Details
payload when theAPI Controller Action
is processed and produces aHTTP Status Code 400+
and noResponse Body
, that means, scenarios like - unhandled exceptions, routing issues - won't produce aProblem Details
payload.Here is overview of when the mechanism will produce the payload:
❌ = Not generated
✅ = Automatically generated
Routing issues: ❌
Unhandled Exceptions: ❌
MVC
StatusCodeResult
400 and up: ✅ (based onSuppressMapClientErrors
)BadRequestResult
andUnprocessableEntityResult
areStatusCodeResult
ProblemDetails
is specified in the input)UnprocessableEntityObjectResult
415 UnsupportedMediaType
: ✅ (Unless when aConsumesAttribute
is defined)406 NotAcceptable
: ❌ (when happens in the output formatter)Minimal APIs
won't generate aProblem Details
payload as well.Here are some examples of reported issues by the community:
Proposed API
🎯 The goal of the proposal is to have the
ProblemDetails
generated, for all Status 400+ (except inMinimal APIs
- for now) but the user need to opt-in and also have a mechanism that allows devs or library authors (eg. API Versioning) generateProblemDetails
responses when opted-in by the users.An important part of the proposed design is the auto generation will happen only when a Body content is not provided, even when the content is a
ProblemDetails
that means scenario, similar to the sample below, will continue generate the ProblemDetails specified by the user and will not use any of the options to suppress the generation:Overview:
ProblemDetails
.Exception Handler Middleware
will autogenerateProblemDetails
only when noExceptionHandler
orExceptionHandlerPath
is provided, theIProblemDetailsService
is registered,ProblemDetailsOptions.AllowedProblemTypes
containsServer
and aIProblemMetadata
is added to the current endpoint.Developer Exception Page Middleware
will autogenerateProblemDetails
only when detected that the client does not accepttext/html
, theIProblemDetailsService
is registered,ProblemDetailsOptions.AllowedProblemTypes
containsServer
and aIProblemMetadata
is added to the current endpoint.Status Code Pages Middleware
default handler will generate aProblemDetails
only when detected theIProblemDetailsService
is registered and theProblemType
requested is allowed and aIProblemMetadata
is added to the current endpoint.AddProblemDetails
is required and will register theIProblemDetailsService
and aDefaultProblemDetailsWriter
.AddProblemDetails
is required and will register theIProblemDetailsService
and aDefaultProblemDetailsWriter
.APIBehaviorOptions.SuppressMapClientErrors
isfalse
, aIProblemMetadata
will be added to all API Controller Actions.IProblemDetailsWriter
that allows content-negotiation that will be used forAPI Controllers
, routing and exceptions. The payload will be generated only when aAPIBehaviorMetadata
is included in the endpoint.Problem Details
configuration, usingProblemDetailsOptions.ConfigureDetails
, will be applied for all autogenerated payload, includingBadRequest
responses caused by validation issues.406 NotAcceptable
response (auto generated) will only autogenerate the payload whenProblemDetailsOptions.AllowedMapping
containsRouting
.404
,405
,415
) will only autogenerate the payload whenProblemDetailsOptions.AllowedMapping
containsRouting
.A detailed spec is here.
Usage Examples
AddProblemDetails
Default options
Custom Options
Creating a custom
ProblemDetails
writerWriting a
Problem Details
response withIProblemDetailsService
The text was updated successfully, but these errors were encountered: