@@ -39,6 +39,7 @@ public static partial class RequestDelegateFactory
39
39
private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteResultWriteResponse ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
40
40
private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteWriteStringResponseAsync ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
41
41
private static readonly MethodInfo StringIsNullOrEmptyMethod = typeof ( string ) . GetMethod ( nameof ( string . IsNullOrEmpty ) , BindingFlags . Static | BindingFlags . Public ) ! ;
42
+ private static readonly MethodInfo WrapObjectAsValueTaskMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( WrapObjectAsValueTask ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
42
43
43
44
// Call WriteAsJsonAsync<object?>() to serialize the runtime return type rather than the declared return type.
44
45
// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism
@@ -71,12 +72,21 @@ public static partial class RequestDelegateFactory
71
72
private static readonly MemberExpression FormFilesExpr = Expression . Property ( FormExpr , typeof ( IFormCollection ) . GetProperty ( nameof ( IFormCollection . Files ) ) ! ) ;
72
73
private static readonly MemberExpression StatusCodeExpr = Expression . Property ( HttpResponseExpr , typeof ( HttpResponse ) . GetProperty ( nameof ( HttpResponse . StatusCode ) ) ! ) ;
73
74
private static readonly MemberExpression CompletedTaskExpr = Expression . Property ( null , ( PropertyInfo ) GetMemberInfo < Func < Task > > ( ( ) => Task . CompletedTask ) ) ;
75
+ private static readonly NewExpression CompletedValueTaskExpr = Expression . New ( typeof ( ValueTask < object > ) . GetConstructor ( new [ ] { typeof ( Task ) } ) ! , CompletedTaskExpr ) ;
74
76
75
77
private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache . TempSourceStringExpr ;
76
78
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression . NotEqual ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
77
79
private static readonly BinaryExpression TempSourceStringNullExpr = Expression . Equal ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
78
80
private static readonly UnaryExpression TempSourceStringIsNotNullOrEmptyExpr = Expression . Not ( Expression . Call ( StringIsNullOrEmptyMethod , TempSourceStringExpr ) ) ;
79
81
82
+ private static readonly ConstructorInfo RouteHandlerFilterContextConstructor = typeof ( RouteHandlerFilterContext ) . GetConstructor ( new [ ] { typeof ( HttpContext ) , typeof ( object [ ] ) } ) ! ;
83
+ private static readonly ParameterExpression FilterContextExpr = Expression . Parameter ( typeof ( RouteHandlerFilterContext ) , "context" ) ;
84
+ private static readonly MemberExpression FilterContextParametersExpr = Expression . Property ( FilterContextExpr , typeof ( RouteHandlerFilterContext ) . GetProperty ( nameof ( RouteHandlerFilterContext . Parameters ) ) ! ) ;
85
+ private static readonly MemberExpression FilterContextHttpContextExpr = Expression . Property ( FilterContextExpr , typeof ( RouteHandlerFilterContext ) . GetProperty ( nameof ( RouteHandlerFilterContext . HttpContext ) ) ! ) ;
86
+ private static readonly MemberExpression FilterContextHttpContextResponseExpr = Expression . Property ( FilterContextHttpContextExpr , typeof ( HttpContext ) . GetProperty ( nameof ( HttpContext . Response ) ) ! ) ;
87
+ private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression . Property ( FilterContextHttpContextResponseExpr , typeof ( HttpResponse ) . GetProperty ( nameof ( HttpResponse . StatusCode ) ) ! ) ;
88
+ private static readonly ParameterExpression InvokedFilterContextExpr = Expression . Parameter ( typeof ( RouteHandlerFilterContext ) , "filterContext" ) ;
89
+
80
90
private static readonly string [ ] DefaultAcceptsContentType = new [ ] { "application/json" } ;
81
91
private static readonly string [ ] FormFileContentType = new [ ] { "multipart/form-data" } ;
82
92
@@ -102,6 +112,7 @@ public static RequestDelegateResult Create(Delegate handler, RequestDelegateFact
102
112
} ;
103
113
104
114
var factoryContext = CreateFactoryContext ( options ) ;
115
+
105
116
var targetableRequestDelegate = CreateTargetableRequestDelegate ( handler . Method , targetExpression , factoryContext ) ;
106
117
107
118
return new RequestDelegateResult ( httpContext => targetableRequestDelegate ( handler . Target , httpContext ) , factoryContext . Metadata ) ;
@@ -155,6 +166,7 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
155
166
RouteParameters = options ? . RouteParameterNames ? . ToList ( ) ,
156
167
ThrowOnBadRequest = options ? . ThrowOnBadRequest ?? false ,
157
168
DisableInferredFromBody = options ? . DisableInferBodyFromParameters ?? false ,
169
+ Filters = options ? . RouteHandlerFilters ? . ToList ( )
158
170
} ;
159
171
160
172
private static Func < object ? , HttpContext , Task > CreateTargetableRequestDelegate ( MethodInfo methodInfo , Expression ? targetExpression , FactoryContext factoryContext )
@@ -176,10 +188,31 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
176
188
// }
177
189
178
190
var arguments = CreateArguments ( methodInfo . GetParameters ( ) , factoryContext ) ;
191
+ var returnType = methodInfo . ReturnType ;
192
+ factoryContext . MethodCall = CreateMethodCall ( methodInfo , targetExpression , arguments ) ;
193
+
194
+ // If there are filters registered on the route handler, then we update the method call and
195
+ // return type associated with the request to allow for the filter invocation pipeline.
196
+ if ( factoryContext . Filters is { Count : > 0 } )
197
+ {
198
+ var filterPipeline = CreateFilterPipeline ( methodInfo , targetExpression , factoryContext ) ;
199
+ Expression < Func < RouteHandlerFilterContext , ValueTask < object ? > > > invokePipeline = ( context ) => filterPipeline ( context ) ;
200
+ returnType = typeof ( ValueTask < object ? > ) ;
201
+ // var filterContext = new RouteHandlerFilterContext(httpContext, new[] { (object)name_local, (object)int_local });
202
+ // invokePipeline.Invoke(filterContext);
203
+ factoryContext . MethodCall = Expression . Block (
204
+ new [ ] { InvokedFilterContextExpr } ,
205
+ Expression . Assign (
206
+ InvokedFilterContextExpr ,
207
+ Expression . New ( RouteHandlerFilterContextConstructor ,
208
+ new Expression [ ] { HttpContextExpr , Expression . NewArrayInit ( typeof ( object ) , factoryContext . BoxedArgs ) } ) ) ,
209
+ Expression . Invoke ( invokePipeline , InvokedFilterContextExpr )
210
+ ) ;
211
+ }
179
212
180
213
var responseWritingMethodCall = factoryContext . ParamCheckExpressions . Count > 0 ?
181
- CreateParamCheckingResponseWritingMethodCall ( methodInfo , targetExpression , arguments , factoryContext ) :
182
- CreateResponseWritingMethodCall ( methodInfo , targetExpression , arguments ) ;
214
+ CreateParamCheckingResponseWritingMethodCall ( returnType , factoryContext ) :
215
+ AddResponseWritingToMethodCall ( factoryContext . MethodCall , returnType ) ;
183
216
184
217
if ( factoryContext . UsingTempSourceString )
185
218
{
@@ -189,6 +222,35 @@ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions
189
222
return HandleRequestBodyAndCompileRequestDelegate ( responseWritingMethodCall , factoryContext ) ;
190
223
}
191
224
225
+ private static Func < RouteHandlerFilterContext , ValueTask < object ? > > CreateFilterPipeline ( MethodInfo methodInfo , Expression ? target , FactoryContext factoryContext )
226
+ {
227
+ Debug . Assert ( factoryContext . Filters is not null ) ;
228
+ // httpContext.Response.StatusCode >= 400
229
+ // ? Task.CompletedTask
230
+ // : handler((string)context.Parameters[0], (int)context.Parameters[1])
231
+ var filteredInvocation = Expression . Lambda < Func < RouteHandlerFilterContext , ValueTask < object ? > > > (
232
+ Expression . Condition (
233
+ Expression . GreaterThanOrEqual ( FilterContextHttpContextStatusCodeExpr , Expression . Constant ( 400 ) ) ,
234
+ CompletedValueTaskExpr ,
235
+ Expression . Block (
236
+ new [ ] { TargetExpr } ,
237
+ Expression . Call ( WrapObjectAsValueTaskMethod ,
238
+ target is null
239
+ ? Expression . Call ( methodInfo , factoryContext . ContextArgAccess )
240
+ : Expression . Call ( target , methodInfo , factoryContext . ContextArgAccess ) )
241
+ ) ) ,
242
+ FilterContextExpr ) . Compile ( ) ;
243
+
244
+ for ( var i = factoryContext . Filters . Count - 1 ; i >= 0 ; i -- )
245
+ {
246
+ var currentFilter = factoryContext . Filters ! [ i ] ;
247
+ var nextFilter = filteredInvocation ;
248
+ filteredInvocation = ( RouteHandlerFilterContext context ) => currentFilter . InvokeAsync ( context , nextFilter ) ;
249
+
250
+ }
251
+ return filteredInvocation ;
252
+ }
253
+
192
254
private static Expression [ ] CreateArguments ( ParameterInfo [ ] ? parameters , FactoryContext factoryContext )
193
255
{
194
256
if ( parameters is null || parameters . Length == 0 )
@@ -201,6 +263,16 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
201
263
for ( var i = 0 ; i < parameters . Length ; i ++ )
202
264
{
203
265
args [ i ] = CreateArgument ( parameters [ i ] , factoryContext ) ;
266
+ // Register expressions containing the boxed and unboxed variants
267
+ // of the route handler's arguments for use in RouteHandlerFilterContext
268
+ // construction and route handler invocation.
269
+ // (string)context.Parameters[0];
270
+ factoryContext . ContextArgAccess . Add (
271
+ Expression . Convert (
272
+ Expression . Property ( FilterContextParametersExpr , "Item" , Expression . Constant ( i ) ) ,
273
+ parameters [ i ] . ParameterType ) ) ;
274
+ // (object)name_local
275
+ factoryContext . BoxedArgs . Add ( Expression . Convert ( args [ i ] , typeof ( object ) ) ) ;
204
276
}
205
277
206
278
if ( factoryContext . HasInferredBody && factoryContext . DisableInferredFromBody )
@@ -381,16 +453,14 @@ target is null ?
381
453
Expression . Call ( methodInfo , arguments ) :
382
454
Expression . Call ( target , methodInfo , arguments ) ;
383
455
384
- private static Expression CreateResponseWritingMethodCall ( MethodInfo methodInfo , Expression ? target , Expression [ ] arguments )
456
+ private static ValueTask < object ? > WrapObjectAsValueTask ( object ? obj )
385
457
{
386
- var callMethod = CreateMethodCall ( methodInfo , target , arguments ) ;
387
- return AddResponseWritingToMethodCall ( callMethod , methodInfo . ReturnType ) ;
458
+ return ValueTask . FromResult < object ? > ( obj ) ;
388
459
}
389
460
390
461
// If we're calling TryParse or validating parameter optionality and
391
462
// wasParamCheckFailure indicates it failed, set a 400 StatusCode instead of calling the method.
392
- private static Expression CreateParamCheckingResponseWritingMethodCall (
393
- MethodInfo methodInfo , Expression ? target , Expression [ ] arguments , FactoryContext factoryContext )
463
+ private static Expression CreateParamCheckingResponseWritingMethodCall ( Type returnType , FactoryContext factoryContext )
394
464
{
395
465
// {
396
466
// string tempSourceString;
@@ -440,17 +510,40 @@ private static Expression CreateParamCheckingResponseWritingMethodCall(
440
510
441
511
localVariables [ factoryContext . ExtraLocals . Count ] = WasParamCheckFailureExpr ;
442
512
443
- var set400StatusAndReturnCompletedTask = Expression . Block (
444
- Expression . Assign ( StatusCodeExpr , Expression . Constant ( 400 ) ) ,
445
- CompletedTaskExpr ) ;
446
-
447
- var methodCall = CreateMethodCall ( methodInfo , target , arguments ) ;
448
-
449
- var checkWasParamCheckFailure = Expression . Condition ( WasParamCheckFailureExpr ,
450
- set400StatusAndReturnCompletedTask ,
451
- AddResponseWritingToMethodCall ( methodCall , methodInfo . ReturnType ) ) ;
513
+ // If filters have been registered, we set the `wasParamCheckFailure` property
514
+ // but do not return from the invocation to allow the filters to run.
515
+ if ( factoryContext . Filters is { Count : > 0 } )
516
+ {
517
+ // if (wasParamCheckFailure)
518
+ // {
519
+ // httpContext.Response.StatusCode = 400;
520
+ // }
521
+ // return RequestDelegateFactory.ExecuteObjectReturn(invocationPipeline.Invoke(context) as object);
522
+ var checkWasParamCheckFailureWithFilters = Expression . Block (
523
+ Expression . IfThen (
524
+ WasParamCheckFailureExpr ,
525
+ Expression . Assign ( StatusCodeExpr , Expression . Constant ( 400 ) ) ) ,
526
+ AddResponseWritingToMethodCall ( factoryContext . MethodCall ! , returnType )
527
+ ) ;
452
528
453
- checkParamAndCallMethod [ factoryContext . ParamCheckExpressions . Count ] = checkWasParamCheckFailure ;
529
+ checkParamAndCallMethod [ factoryContext . ParamCheckExpressions . Count ] = checkWasParamCheckFailureWithFilters ;
530
+ }
531
+ else
532
+ {
533
+ // wasParamCheckFailure ? {
534
+ // httpContext.Response.StatusCode = 400;
535
+ // return Task.CompletedTask;
536
+ // } : {
537
+ // return RequestDelegateFactory.ExecuteObjectReturn(invocationPipeline.Invoke(context) as object);
538
+ // }
539
+ var checkWasParamCheckFailure = Expression . Condition (
540
+ WasParamCheckFailureExpr ,
541
+ Expression . Block (
542
+ Expression . Assign ( StatusCodeExpr , Expression . Constant ( 400 ) ) ,
543
+ CompletedTaskExpr ) ,
544
+ AddResponseWritingToMethodCall ( factoryContext . MethodCall ! , returnType ) ) ;
545
+ checkParamAndCallMethod [ factoryContext . ParamCheckExpressions . Count ] = checkWasParamCheckFailure ;
546
+ }
454
547
455
548
return Expression . Block ( localVariables , checkParamAndCallMethod ) ;
456
549
}
@@ -1596,6 +1689,11 @@ private class FactoryContext
1596
1689
1597
1690
public bool ReadForm { get ; set ; }
1598
1691
public ParameterInfo ? FirstFormRequestBodyParameter { get ; set ; }
1692
+ // Properties for constructing and managing filters
1693
+ public List < Expression > ContextArgAccess { get ; } = new ( ) ;
1694
+ public Expression ? MethodCall { get ; set ; }
1695
+ public List < Expression > BoxedArgs { get ; } = new ( ) ;
1696
+ public List < IRouteHandlerFilter > ? Filters { get ; init ; }
1599
1697
}
1600
1698
1601
1699
private static class RequestDelegateFactoryConstants
0 commit comments