Description
When adding endpoint filters via a filter factory delegate, it would sometimes be useful to be able to manipulate the endpoint's metadata too, in the case where the filter will impact the effective endpoint operation.
For example, below shows a rudimentary XML formatting filter that adds the ability for an endpoint to format its response as XML if the client supports it, instead of the usual JSON. This filter should be able to update the endpoint metadata to indicate that it can now return XML as well, so that it's described as such in ApiExplorer
and OpenAPI/Swagger.
app.MapGet("/todos", async Task<Ok<Todo[]>> (TodoDb db) => TypedResults.Ok(await db.Todos.ToArrayAsync())
.AddFilter((context, next) =>
{
var returnType = context.MethodInfo.ReturnType;
if (returnType.IsAssignableTo(typeof(Ok<Todo[]>)))
{
// Would be nice to be able to manipulate endpoint metadata here, e.g.
context.EndpointMetadata.Add(new ProducesResponseTypeAttribute(typeof(Todo), 200, "text/xml"));
// Return an "XML formatter" filter
return async c =>
{
var result = await next(c);
if (!c.HttpContext.Request.Headers.Accepts.Contains("text/xml")
{
// Client doesn't support XML, just return original result
return result;
}
return result switch
{
Ok<Todo[]> ok => CustomResults.OkXml(ok.Value),
_ => throw new InvalidOperationException("Result type cannot be formatted as XML.")
};
}
}
return next;
});
The suggested API diff:
namespace Microsoft.AspNetCore.Http;
public sealed class RouteHandlerContext
{
- public RouteHandlerContext(MethodInfo methodInfo, EndpointMetadataCollection endpointMetadata, IServiceProvider applicationServices);
+ public RouteHandlerContext(MethodInfo methodInfo, IList<object> endpointMetadata, IServiceProvider applicationServices);
- public EndpointMetadataCollection EndpointMetadata { get; }
+ public IList<object> EndpointMetadata { get; }
}
Ordering considerations
Imagine the above example is extended so that other methods on the IEndpointConventionBuilder
are called that also manipulate endpoint metadata, e.g.:
app.MapGet("/todos", async Task<Ok<Todo[]>> (TodoDb db) => TypedResults.Ok(await db.Todos.ToArrayAsync())
.WithName("GetTodos")
.WithTags("FirstTag")
.AddFilter((context, next) =>
{
// Code from above...
}
.WithTags("SecondTag");
What is the order of operations WRT to the buildup of the endpoint metadata collection?
Will the filter factory delegate see:
- the metadata for just the methods declared before it (e.g.
EndpointNameAttribute("GetTodos"), TagsAttribute("FirstTag")
), and not that for the methods declared after it; or - all the endpoint metadata added by such methods; or
- none of the endpoint metadata added by such methods
Ideally I think, it would see the endpoint metadata added by any methods declared before it in the endpoint builder (point 1), along with any metadata added by RequestDelegateFactory
it self for the endpoint delegate.