Skip to content

Allow endpoint filter factories to manipulate endpoint metadataย #41722

Closed
@DamianEdwards

Description

@DamianEdwards

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:

  1. 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
  2. all the endpoint metadata added by such methods; or
  3. 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.

Metadata

Metadata

Labels

DocsThis issue tracks updating documentationapi-approvedAPI was approved in API review, it can be implementedarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-minimal-actionsController-like actions for endpoint routingold-area-web-frameworks-do-not-use*DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions