Skip to content

Commit a5cb7e4

Browse files
committed
Add support for route handler filter factories
1 parent 54a7109 commit a5cb7e4

14 files changed

+333
-123
lines changed

src/Http/Http.Abstractions/src/IRouteHandlerFilter.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Http.Abstractions;
5+
46
namespace Microsoft.AspNetCore.Http;
57

68
/// <summary>
@@ -16,5 +18,5 @@ public interface IRouteHandlerFilter
1618
/// <param name="next">The next filter in the pipeline.</param>
1719
/// <returns>An awaitable result of calling the handler and apply
1820
/// any modifications made by filters in the pipeline.</returns>
19-
ValueTask<object?> InvokeAsync(RouteHandlerFilterContext context, Func<RouteHandlerFilterContext, ValueTask<object?>> next);
21+
ValueTask<object?> InvokeAsync(RouteHandlerFilterContext context, RouteHandlerFilterDelegate next);
2022
}

src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#nullable enable
22
*REMOVED*abstract Microsoft.AspNetCore.Http.HttpResponse.ContentType.get -> string!
3+
Microsoft.AspNetCore.Http.Abstractions.RouteHandlerFilterDelegate
34
Microsoft.AspNetCore.Http.EndpointMetadataCollection.GetRequiredMetadata<T>() -> T!
4-
Microsoft.AspNetCore.Http.RouteHandlerFilterContext.RouteHandlerFilterContext(Microsoft.AspNetCore.Http.HttpContext! httpContext, params object![]! parameters) -> void
5-
Microsoft.AspNetCore.Http.IRouteHandlerFilter.InvokeAsync(Microsoft.AspNetCore.Http.RouteHandlerFilterContext! context, System.Func<Microsoft.AspNetCore.Http.RouteHandlerFilterContext!, System.Threading.Tasks.ValueTask<object?>>! next) -> System.Threading.Tasks.ValueTask<object?>
5+
Microsoft.AspNetCore.Http.IRouteHandlerFilter.InvokeAsync(Microsoft.AspNetCore.Http.RouteHandlerFilterContext! context, Microsoft.AspNetCore.Http.Abstractions.RouteHandlerFilterDelegate! next) -> System.Threading.Tasks.ValueTask<object?>
66
Microsoft.AspNetCore.Http.Metadata.IFromFormMetadata
77
Microsoft.AspNetCore.Http.Metadata.IFromFormMetadata.Name.get -> string?
8+
Microsoft.AspNetCore.Http.RouteHandlerFilterContext.RouteHandlerFilterContext(Microsoft.AspNetCore.Http.HttpContext! httpContext, System.IServiceProvider! serviceProvider, params object![]! parameters) -> void
9+
Microsoft.AspNetCore.Http.RouteHandlerFilterContext.ServiceProvider.get -> System.IServiceProvider!
810
Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(Microsoft.AspNetCore.Routing.RouteValueDictionary? dictionary) -> void
911
Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, object?>>? values) -> void
1012
Microsoft.AspNetCore.Routing.RouteValueDictionary.RouteValueDictionary(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, string?>>? values) -> void

src/Http/Http.Abstractions/src/RouteHandlerFilterContext.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ public class RouteHandlerFilterContext
1313
/// Creates a new instance of the <see cref="RouteHandlerFilterContext"/> for a given request.
1414
/// </summary>
1515
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
16+
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> associated with the current request.</param>
1617
/// <param name="parameters">A list of parameters provided in the current request.</param>
17-
public RouteHandlerFilterContext(HttpContext httpContext, params object[] parameters)
18+
public RouteHandlerFilterContext(HttpContext httpContext, IServiceProvider serviceProvider, params object[] parameters)
1819
{
1920
HttpContext = httpContext;
2021
Parameters = parameters;
22+
ServiceProvider = serviceProvider;
2123
}
2224

2325
/// <summary>
@@ -28,8 +30,13 @@ public RouteHandlerFilterContext(HttpContext httpContext, params object[] parame
2830
/// <summary>
2931
/// A list of parameters provided in the current request to the filter.
3032
/// <remarks>
31-
/// This list is not read-only to premit modifying of existing parameters by filters.
33+
/// This list is not read-only to permit modifying of existing parameters by filters.
3234
/// </remarks>
3335
/// </summary>
3436
public IList<object?> Parameters { get; }
37+
38+
/// <summary>
39+
/// The <see cref="IServiceProvider"/> associated with the current request.
40+
/// </summary>
41+
public IServiceProvider ServiceProvider { get; }
3542
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Http.Abstractions;
5+
6+
/// <summary>
7+
/// A delegate that is applied as a filter on a route handler.
8+
/// </summary>
9+
/// <param name="context">The <see cref="RouteHandlerFilterContext"/> associated with the current request.</param>
10+
/// <returns>
11+
/// An awaitable result of calling the handler and applying any modifications made by filters in the pipeline.
12+
/// </returns>
13+
public delegate ValueTask<object?> RouteHandlerFilterDelegate(RouteHandlerFilterContext context);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.RouteHandlerFilterFactories.get -> System.Collections.Generic.IReadOnlyList<System.Func<System.Reflection.MethodInfo!, Microsoft.AspNetCore.Http.Abstractions.RouteHandlerFilterDelegate!, Microsoft.AspNetCore.Http.Abstractions.RouteHandlerFilterDelegate!>!>?
3+
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.RouteHandlerFilterFactories.init -> void
24
Microsoft.Extensions.DependencyInjection.RouteHandlerJsonServiceExtensions
35
static Microsoft.Extensions.DependencyInjection.RouteHandlerJsonServiceExtensions.ConfigureRouteHandlerJsonOptions(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Microsoft.AspNetCore.Http.Json.JsonOptions!>! configureOptions) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
4-
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.RouteHandlerFilters.get -> System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.IRouteHandlerFilter!>?
5-
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.RouteHandlerFilters.init -> void

src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Security.Claims;
1111
using System.Text;
1212
using System.Text.Json;
13+
using Microsoft.AspNetCore.Http.Abstractions;
1314
using Microsoft.AspNetCore.Http.Features;
1415
using Microsoft.AspNetCore.Http.Metadata;
1516
using Microsoft.Extensions.DependencyInjection;
@@ -79,7 +80,7 @@ public static partial class RequestDelegateFactory
7980
private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
8081
private static readonly UnaryExpression TempSourceStringIsNotNullOrEmptyExpr = Expression.Not(Expression.Call(StringIsNullOrEmptyMethod, TempSourceStringExpr));
8182

82-
private static readonly ConstructorInfo RouteHandlerFilterContextConstructor = typeof(RouteHandlerFilterContext).GetConstructor(new[] { typeof(HttpContext), typeof(object[]) })!;
83+
private static readonly ConstructorInfo RouteHandlerFilterContextConstructor = typeof(RouteHandlerFilterContext).GetConstructor(new[] { typeof(HttpContext), typeof(IServiceProvider), typeof(object[]) })!;
8384
private static readonly ParameterExpression FilterContextExpr = Expression.Parameter(typeof(RouteHandlerFilterContext), "context");
8485
private static readonly MemberExpression FilterContextParametersExpr = Expression.Property(FilterContextExpr, typeof(RouteHandlerFilterContext).GetProperty(nameof(RouteHandlerFilterContext.Parameters))!);
8586
private static readonly MemberExpression FilterContextHttpContextExpr = Expression.Property(FilterContextExpr, typeof(RouteHandlerFilterContext).GetProperty(nameof(RouteHandlerFilterContext.HttpContext))!);
@@ -166,7 +167,7 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
166167
RouteParameters = options?.RouteParameterNames?.ToList(),
167168
ThrowOnBadRequest = options?.ThrowOnBadRequest ?? false,
168169
DisableInferredFromBody = options?.DisableInferBodyFromParameters ?? false,
169-
Filters = options?.RouteHandlerFilters?.ToList()
170+
Filters = options?.RouteHandlerFilterFactories?.ToList()
170171
};
171172

172173
private static Func<object?, HttpContext, Task> CreateTargetableRequestDelegate(MethodInfo methodInfo, Expression? targetExpression, FactoryContext factoryContext)
@@ -205,7 +206,7 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
205206
Expression.Assign(
206207
InvokedFilterContextExpr,
207208
Expression.New(RouteHandlerFilterContextConstructor,
208-
new Expression[] { HttpContextExpr, Expression.NewArrayInit(typeof(object), factoryContext.BoxedArgs) })),
209+
new Expression[] { HttpContextExpr, RequestServicesExpr, Expression.NewArrayInit(typeof(object), factoryContext.BoxedArgs) })),
209210
Expression.Invoke(invokePipeline, InvokedFilterContextExpr)
210211
);
211212
}
@@ -222,13 +223,13 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
222223
return HandleRequestBodyAndCompileRequestDelegate(responseWritingMethodCall, factoryContext);
223224
}
224225

225-
private static Func<RouteHandlerFilterContext, ValueTask<object?>> CreateFilterPipeline(MethodInfo methodInfo, Expression? target, FactoryContext factoryContext)
226+
private static RouteHandlerFilterDelegate CreateFilterPipeline(MethodInfo methodInfo, Expression? target, FactoryContext factoryContext)
226227
{
227228
Debug.Assert(factoryContext.Filters is not null);
228229
// httpContext.Response.StatusCode >= 400
229230
// ? Task.CompletedTask
230231
// : handler((string)context.Parameters[0], (int)context.Parameters[1])
231-
var filteredInvocation = Expression.Lambda<Func<RouteHandlerFilterContext, ValueTask<object?>>>(
232+
var filteredInvocation = Expression.Lambda<RouteHandlerFilterDelegate>(
232233
Expression.Condition(
233234
Expression.GreaterThanOrEqual(FilterContextHttpContextStatusCodeExpr, Expression.Constant(400)),
234235
CompletedValueTaskExpr,
@@ -243,9 +244,9 @@ target is null
243244

244245
for (var i = factoryContext.Filters.Count - 1; i >= 0; i--)
245246
{
246-
var currentFilter = factoryContext.Filters![i];
247+
var currentFilterFactory = factoryContext.Filters![i];
247248
var nextFilter = filteredInvocation;
248-
filteredInvocation = (RouteHandlerFilterContext context) => currentFilter.InvokeAsync(context, nextFilter);
249+
filteredInvocation = (RouteHandlerFilterContext context) => currentFilterFactory(methodInfo, nextFilter)(context);
249250

250251
}
251252
return filteredInvocation;
@@ -1693,7 +1694,7 @@ private class FactoryContext
16931694
public List<Expression> ContextArgAccess { get; } = new();
16941695
public Expression? MethodCall { get; set; }
16951696
public List<Expression> BoxedArgs { get; } = new();
1696-
public List<IRouteHandlerFilter>? Filters { get; init; }
1697+
public List<Func<MethodInfo, RouteHandlerFilterDelegate, RouteHandlerFilterDelegate>>? Filters { get; init; }
16971698
}
16981699

16991700
private static class RequestDelegateFactoryConstants

src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Reflection;
5+
using Microsoft.AspNetCore.Http.Abstractions;
46
using Microsoft.AspNetCore.Http.Metadata;
57
using Microsoft.Extensions.Logging;
68

@@ -35,5 +37,5 @@ public sealed class RequestDelegateFactoryOptions
3537
/// <summary>
3638
/// The list of filters that must run in the pipeline for a given route handler.
3739
/// </summary>
38-
public IReadOnlyList<IRouteHandlerFilter>? RouteHandlerFilters { get; init; }
40+
public IReadOnlyList<Func<MethodInfo, RouteHandlerFilterDelegate, RouteHandlerFilterDelegate>>? RouteHandlerFilterFactories { get; init; }
3941
}

0 commit comments

Comments
 (0)