Skip to content

[API Review]: Add support for endpoint filters in minimal APIs #40506

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

Closed
captainsafia opened this issue Mar 2, 2022 · 2 comments
Closed

[API Review]: Add support for endpoint filters in minimal APIs #40506

captainsafia opened this issue Mar 2, 2022 · 2 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented enhancement This issue represents an ask for new feature or an enhancement to an existing one old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels Priority:1 Work that is critical for the release, but we could probably ship without
Milestone

Comments

@captainsafia
Copy link
Member

captainsafia commented Mar 2, 2022

The implementation for this feature exists in #40491.

We add a new IRouteHandlerFilter interface that users will implement when writing their own filters.

public interface IRouteHandlerFilter
{
  ValueTask<object?> InvokeAsync(RouteHandlerFilterContext context, Func<RouteHandlerFilterContext, ValueTask<object?>> next)
}

The RouteHandlerFilterContext captures the HttpContext and the list of parameters provided to a route handler for access in the filter.

Note: we want to users to be able to modify the existing parameters that are provided to the handler in their filters which is why the Parameters property is typed as IList and IReadOnlyList. Although IReadOnlyList has the benefit of limiting additions/removals from the list, it also prohibits mutations of existing items (e.g. CS0200 galore). However, we anticipate that we will make an incremental improvement to Parameters and type it as a variadic generic object that provides information about the type, parameter name, and validity of a parameter so the question of IList vs IReadOnlyList is minute at the moment.

public class RouteHandlerFilterContext
{
    public RouteHandlerFilterContext(HttpContext httpContext, params object[] parameters)
    {
        HttpContext = httpContext;
        Parameters = parameters;
    }

    public HttpContext HttpContext { get; }

    public IList<object?> Parameters { get; }
}

We add a new RouteHandlerFilters field to RequestDelegateFactoryOptions that can be used to pass the list of filters registered on a handler to the code-gen in the RequestDelegateFactory.

public class RequestDelegateFactoryOptions
{
    public IEnumerable<IRouteHandlerFilter>? RouteHandlerFilters { get; init; }
}

We add a collection of new extension methods that users can invoke on their endpoints to register handlers.

public static class RouteHandlerFilterExtensions
{
    public static RouteHandlerBuilder AddFilter(this RouteHandlerBuilder builder, IRouteHandlerFilter filter) { }

    public static RouteHandlerBuilder AddFilter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFilterType>(this RouteHandlerBuilder builder) where TFilterType : IRouteHandlerFilter, new() { }

    public static RouteHandlerBuilder AddFilter(this RouteHandlerBuilder builder, Func<RouteHandlerFilterContext, Func<RouteHandlerFilterContext, ValueTask<object?>>, ValueTask<object?>> routeHandlerFilter) { }
}

Code Sample

string SayHello(string name, int age, DateOnly birthDate) => $"Hello, {name}! You are {age} years old and born in {birthDate.Year} on a {birthDate.DayOfWeek}.";
app.MapGet("/hello/{name}/{age}/{birthDate}", SayHello)
    .AddFilter(async (RouteHandlerFilterContext context, Func<RouteHandlerFilterContext, ValueTask<object>> next) =>
    {
        var age = (int)context.Parameters[1];
        var birthDate = (DateOnly)context.Parameters[2];
        var expectedAge = DateTime.Now.Year - birthDate.Year;
        // if (age != expectedAge)
        if (context.HttpContext.Response.StatusCode == 400)
        {
            return Results.Problem($"Age of {age} does not match birthdate.");
        }
        return await next(context);
    })
    .AddFilter<UpdateDateFilter>()
    .AddFilter(new AddLastNameFilter("Abdalla"));

app.Run();

public class UpdateDateFilter : IRouteHandlerFilter
{
    public async ValueTask<object> InvokeAsync(RouteHandlerFilterContext context, Func<RouteHandlerFilterContext, ValueTask<object>> next)
    {
        var date = (DateOnly)context.Parameters[2];
        context.Parameters[2] = date.AddYears(2);
        return await next(context);
    }
}

public class AddLastNameFilter : IRouteHandlerFilter
{
    private readonly string _lastName;

    public AddLastNameFilter(string lastName)
    {
        _lastName = lastName;
    }

    public async ValueTask<object> InvokeAsync(RouteHandlerFilterContext context, Func<RouteHandlerFilterContext, ValueTask<object>> next)
    {
        var name = (string)context.Parameters[0];
        context.Parameters[0] = $"{name} {_lastName}";
        return await next(context);
    }
}
@captainsafia captainsafia added the api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews label Mar 2, 2022
@ghost
Copy link

ghost commented Mar 2, 2022

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:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@Pilchie Pilchie added the old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Mar 3, 2022
@captainsafia captainsafia added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Mar 3, 2022
@rafikiassumani-msft rafikiassumani-msft added this to the 7.0-preview3 milestone Mar 3, 2022
@rafikiassumani-msft rafikiassumani-msft added the Priority:1 Work that is critical for the release, but we could probably ship without label Mar 3, 2022
@captainsafia
Copy link
Member Author

Closing since this API has been approved.

Will file separate API review issues for the follow-up items.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 3, 2022
@danroth27 danroth27 added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Apr 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented enhancement This issue represents an ask for new feature or an enhancement to an existing one old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels Priority:1 Work that is critical for the release, but we could probably ship without
Projects
None yet
Development

No branches or pull requests

4 participants