-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Allow parameter and return types of route handler delegates (Minimal APIs) to contribute to endpoint metadata #40646
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
- IProvideEndpointParameterMetadata - IProvideEndpointResponseMetadata - Contributes to #40646
…return & attribute types Contributes to #40646
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:
|
Changing the public sealed class EndpointParameterMetadataContext
{
public ParameterInfo Parameter { init; }
public IServiceProvider Services { init; }
// Possible additions
public RoutePattern RoutePattern { init; }
public IList<object> EndpointMetadata { init; }
} Then we could do something similar with a public sealed class EndpointMetadataContext
{
public MethodInfo Method { init; }
public IServiceProvider Services { init; }
// Possible additions
public RoutePattern RoutePattern { init; }
publice IList<object> EndpointMetadata { init; }
} public interface IProvideEndpointParameterMetadata
{
static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
}
public interface IProvideEndpointMetadata
{
static abstract void PopulateMetadata(EndpointMetadataContext context);
} The return becomes |
It's not GetMetadata if it's populating a context. Maybe |
Changing to a context definitely adds flexibility to change in the future. Are you expecting that the mutable properties on the context would be preserved as each type's method is called? If so, that introduces a potential reliance on ordering that we shouldn't be guaranteeing. We could of course not preserve those values but that seems different to how we use context types in other places. I guess we could make the properties settable and be |
API Review:
namespace Microsoft.AspNetCore.Http;
public sealed class EndpointParameterMetadataContext
{
public ParameterInfo Parameter { init; }
public IServiceProvider Services { init; }
public IList<object> EndpointMetadata { init; }
}
public sealed class EndpointMetadataContext
{
public MethodInfo Method { init; }
public IServiceProvider Services { init; }
public IList<object> EndpointMetadata { init; }
}
public interface IEndpointParameterMetadataProvider
{
static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
}
public interface IEndpointMetadataProvider
{
static abstract void PopulateMetadata(EndpointMetadataContext context);
} |
API changes since API review based on implementation details and PR feedback: -namespace Microsoft.AspNetCore.Http;
+namespace Microsoft.AspNetCore.Http.Metadata;
public sealed class EndpointParameterMetadataContext
{
- public ParameterInfo Parameter { get; init; }
+ public ParameterInfo Parameter { get; internal set; } // internal set to allow re-use
public IServiceProvider Services { get; init; }
public IList<object>? EndpointMetadata { get; init; }
}
public sealed class EndpointMetadataContext
{
public MethodInfo Method { get; init; }
public IServiceProvider Services { get; init; }
public IList<object>? EndpointMetadata { get; init; }
}
public interface IEndpointParameterMetadataProvider
{
static abstract void PopulateMetadata(EndpointParameterMetadataContext parameterContext);
}
public interface IEndpointMetadataProvider
{
static abstract void PopulateMetadata(EndpointMetadataContext context);
}
public sealed class RequestDelegateFactoryOptions
{
public IServiceProvider? ServiceProvider { get; init; }
public IEnumerable<string>? RouteParameterNames { get; init; }
public bool ThrowOnBadRequest { get; init; }
public bool DisableInferBodyFromParameters { get; init; }
public IReadOnlyList<Func<RouteHandlerContext, RouteHandlerFilterDelegate, RouteHandlerFilterDelegate>>? RouteHandlerFilterFactories { get; init; }
+ public IEnumerable<object>? AdditionalEndpointMetadata { get; init; }
} |
Overview
Introduce the capability for types used for parameters and return values for route handler delegates (Minimal APIs) to contribute to metadata of the endpoint they're mapped to. This will allow types that implement custom binding logic via
TryParse
orBindAsync
, andIResult
types, to add metadata that describes the API parameters and responses in Swagger UI and OpenAPI documents.This, along with some other capabilities, will better allow route handler delegates to self-describe themselves from just the type information in their signature, reducing the need for the developer to manually provide the same type information about the API in the endpoint metadata.
A functionally equivalent version of this feature is already implemented in the MinimalApis.Extensions library.
Problem
Today, route handler delegates in Minimal APIs are auto-described in
ApiExplorer
and thus Swagger UI and OpenAPI documents (through libraries like Swashbuckle) including details of the parameters they accept and response they return, e.g.Program.cs
Swagger UI

Note that the input parameter
id
is correctly named and typed based on the delegate parameter, and the 200 response has a media type of application/json and a body schema based on theTodo
type that the delegate returns.This auto-describing of the API breaks down though when the extensibility options for parameter binding or result processing are utilized, namely
TryParse
orBindAsync
on parameter types, andIResult
for return types. e.g.:Program.cs
Swagger UI

Note this time that the
id
parameter is typed as "string" and the 200 response has no media type or schema information.To restore the API description details, the user is required to manually describe the API via extension methods (or attributes) that add endpoint metadata about the responses, e.g.:
Program.cs
Swagger UI

Note that today there is no endpoint metadata supported that will describe API parameters, so the
id
parameter is still typed incorrectly as string, but the 200 and 404 responses are now described.Proposed Solution
We should introduce a way for types used in the signature of route handlers (parameters and return values) to add metadata to the associated endpoint. This metadata should be featured enough to properly contribute all API parameter and response details in
ApiExplorer
and thus Swagger UI and OpenAPI documents.Interfaces
Two new interfaces will be introduced, one for parameter types and another for return value types. The presence of these interfaces on a type indicates they can contribute to the endpoint metadata.
Each interface has a single method that will be called by the framework when the endpoint is being built, with the returned objects being added to the endpoint metadata.
The
IProvideEndpointParameterMetadata
interface'sGetMetadata
method accepts aParameterInfo
representing the relevant route handler delegate parameter, such that the parameter name, type details, etc. are available, along with theIServiceProvider
for the application's DI container.The
IProvideEndpointMetadata
interface'sGetMetadata
method accepts theMethodInfo
instance for the endpoint that the returned metadata will be applied to, such that the details of the method are available, along with theIServiceProvider
for the application's DI container.Example
The following is an example of an API that accepts a parameter utilizing custom binding via
BindAsync
and returns a customIResult
type that describes itself in Swagger UI/OpenAPI by emitting metadata via theIProvideEndpointParameterMetadata
andIProvideEndpointMetadata
interfaces:Program.cs
Challenges/Open Questions
Existing result types
The existing
IResult
implementations in the framework are currently internal, although that will likely change as part of #37502. For this feature to be useful out-of-the-box, these result types must be public and be updated (where applicable) to implementIProvideEndpointMetadata
so that they can contribute to endpoint metadata.Result types that represent a response with a body that's based on a type need to be able to convey the schema of the body in their type signature, meaning they should need to be generic, where the generic type argument is the type that represents the resource being returned, e.g.
OkResult<Todo>
,CreatedAtResult<Todo>
, etc.Also each type needs to completely represent a distinct HTTP result without relying on runtime/instance data, e.g.
StatusCodeResult
is ambiguous when statically observed as to what the actual HTTP result is and thus will not work, whereas aNotFoundResult
can wholly represents an HTTP 404 Not Found result statically.Results
class static factory methodsThe current
Results
static factory methods that all returnIResult
do not allow the framework to observe the actual concrete types being returned, and thus we'll need to consider adding new methods to create the result types that preserve the concrete type information, or some other solution, e.g.HttpResults
, which has factory methods for the public result types that return the concrete typesResults
class but under a new property, e.g.Results.Typed.NotFound()
, etc. (this one is my preference right now)IResult
(Note along with this being a binary-breaking change, this has implications on the compiler's ability to infer the return type of anonymous lambdas when there's more than one return type in the method so is likely a source-breaking change too)IResult
-returning overloads (this is likely crazy but possibly crazy like a fox, so...). (Note this has the same issue as the point above RE the compiler's ability to infer anonymous lambda return type)return new NotFoundHttpResult();
(Note this has the same issue as the point above RE the compiler's ability to infer anonymous lambda return type)return NotFoundHttpResult.Create();
(Note this has the same issue as the point above RE the compiler's ability to infer anonymous lambda return type)Multiple return types
Most APIs end up having multiple return paths that return different result types depending on the outcome of the API invocation, e.g. returning an
OkResult
if the resource is found, and aNotFoundResult
if it isn't. C# doesn't currently have a language construct that allows a method to declare it returns multiple types (see discriminated unions at dotnet/csharplang#113) but this can be achieved using the type system thanks to generics, e.g.TodosApi.cs
See #40672 for details of a proposal to support multiple return types on route handler delegates.
The text was updated successfully, but these errors were encountered: