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);
}
}
The implementation for this feature exists in #40491.
We add a new
IRouteHandlerFilterinterface that users will implement when writing their own filters.The
RouteHandlerFilterContextcaptures theHttpContextand 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
Parametersproperty is typed asIListandIReadOnlyList. AlthoughIReadOnlyListhas the benefit of limiting additions/removals from the list, it also prohibits mutations of existing items (e.g.CS0200galore). However, we anticipate that we will make an incremental improvement toParametersand type it as a variadic generic object that provides information about the type, parameter name, and validity of a parameter so the question ofIListvsIReadOnlyListis minute at the moment.We add a new
RouteHandlerFiltersfield toRequestDelegateFactoryOptionsthat can be used to pass the list of filters registered on a handler to the code-gen in theRequestDelegateFactory.We add a collection of new extension methods that users can invoke on their endpoints to register handlers.
Code Sample