15
15
using System . Text . Json ;
16
16
using Microsoft . AspNetCore . Http . Features ;
17
17
using Microsoft . AspNetCore . Http . Metadata ;
18
+ using Microsoft . AspNetCore . Routing ;
18
19
using Microsoft . Extensions . DependencyInjection ;
19
20
using Microsoft . Extensions . Internal ;
20
21
using Microsoft . Extensions . Logging ;
@@ -58,6 +59,11 @@ public static partial class RequestDelegateFactory
58
59
private static readonly MethodInfo PopulateMetadataForEndpointMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( PopulateMetadataForEndpoint ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
59
60
private static readonly MethodInfo ArrayEmptyOfObjectMethod = typeof ( Array ) . GetMethod ( nameof ( Array . Empty ) , BindingFlags . Public | BindingFlags . Static ) ! . MakeGenericMethod ( new Type [ ] { typeof ( object ) } ) ;
60
61
62
+ private static readonly PropertyInfo QueryIndexerProperty = typeof ( IQueryCollection ) . GetProperty ( "Item" ) ! ;
63
+ private static readonly PropertyInfo RouteValuesIndexerProperty = typeof ( RouteValueDictionary ) . GetProperty ( "Item" ) ! ;
64
+ private static readonly PropertyInfo HeaderIndexerProperty = typeof ( IHeaderDictionary ) . GetProperty ( "Item" ) ! ;
65
+ private static readonly PropertyInfo FormFilesIndexerProperty = typeof ( IFormFileCollection ) . GetProperty ( "Item" ) ! ;
66
+
61
67
// Call WriteAsJsonAsync<object?>() to serialize the runtime return type rather than the declared return type.
62
68
// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism
63
69
private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = GetMethodInfo < Func < HttpResponse , object ? , Task > > ( ( response , value ) => HttpResponseJsonExtensions . WriteAsJsonAsync < object ? > ( response , value , default ) ) ;
@@ -98,8 +104,10 @@ public static partial class RequestDelegateFactory
98
104
99
105
private static readonly ConstructorInfo DefaultRouteHandlerInvocationContextConstructor = typeof ( DefaultRouteHandlerInvocationContext ) . GetConstructor ( new [ ] { typeof ( HttpContext ) , typeof ( object [ ] ) } ) ! ;
100
106
private static readonly MethodInfo RouteHandlerInvocationContextGetArgument = typeof ( RouteHandlerInvocationContext ) . GetMethod ( nameof ( RouteHandlerInvocationContext . GetArgument ) ) ! ;
107
+ private static readonly PropertyInfo ListIndexer = typeof ( IList < object > ) . GetProperty ( "Item" ) ! ;
101
108
private static readonly ParameterExpression FilterContextExpr = Expression . Parameter ( typeof ( RouteHandlerInvocationContext ) , "context" ) ;
102
109
private static readonly MemberExpression FilterContextHttpContextExpr = Expression . Property ( FilterContextExpr , typeof ( RouteHandlerInvocationContext ) . GetProperty ( nameof ( RouteHandlerInvocationContext . HttpContext ) ) ! ) ;
110
+ private static readonly MemberExpression FilterContextArgumentsExpr = Expression . Property ( FilterContextExpr , typeof ( RouteHandlerInvocationContext ) . GetProperty ( nameof ( RouteHandlerInvocationContext . Arguments ) ) ! ) ;
103
111
private static readonly MemberExpression FilterContextHttpContextResponseExpr = Expression . Property ( FilterContextHttpContextExpr , typeof ( HttpContext ) . GetProperty ( nameof ( HttpContext . Response ) ) ! ) ;
104
112
private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression . Property ( FilterContextHttpContextResponseExpr , typeof ( HttpResponse ) . GetProperty ( nameof ( HttpResponse . StatusCode ) ) ! ) ;
105
113
private static readonly ParameterExpression InvokedFilterContextExpr = Expression . Parameter ( typeof ( RouteHandlerInvocationContext ) , "filterContext" ) ;
@@ -397,6 +405,13 @@ private static Expression CreateRouteHandlerInvocationContextBase(FactoryContext
397
405
DefaultRouteHandlerInvocationContextConstructor ,
398
406
new Expression [ ] { HttpContextExpr , paramArray } ) ;
399
407
408
+ if ( ! RuntimeFeature . IsDynamicCodeCompiled )
409
+ {
410
+ // For AOT platforms it's not possible to support the closed generic arguments that are based on the
411
+ // parameter arguments dynamically (for value types). In that case, fallback to boxing the argument list.
412
+ return fallbackConstruction ;
413
+ }
414
+
400
415
var arguments = new Expression [ factoryContext . ArgumentExpressions . Length + 1 ] ;
401
416
arguments [ 0 ] = HttpContextExpr ;
402
417
factoryContext . ArgumentExpressions . CopyTo ( arguments , 1 ) ;
@@ -513,16 +528,33 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
513
528
factoryContext . BoxedArgs = new Expression [ parameters . Length ] ;
514
529
factoryContext . Parameters = new List < ParameterInfo > ( parameters ) ;
515
530
531
+ var hasFilters = factoryContext . Filters is { Count : > 0 } ;
532
+
516
533
for ( var i = 0 ; i < parameters . Length ; i ++ )
517
534
{
518
535
args [ i ] = CreateArgument ( parameters [ i ] , factoryContext ) ;
519
536
520
- // Register expressions containing the boxed and unboxed variants
521
- // of the route handler's arguments for use in RouteHandlerInvocationContext
522
- // construction and route handler invocation.
523
- // context.GetArgument<string>(0)
524
- factoryContext . ContextArgAccess . Add ( Expression . Call ( FilterContextExpr , RouteHandlerInvocationContextGetArgument . MakeGenericMethod ( parameters [ i ] . ParameterType ) , Expression . Constant ( i ) ) ) ;
525
- // (string, name_local), (int, int_local)
537
+ // Only populate the context args if there are filters for this handler
538
+ if ( hasFilters )
539
+ {
540
+ if ( RuntimeFeature . IsDynamicCodeSupported )
541
+ {
542
+ // Register expressions containing the boxed and unboxed variants
543
+ // of the route handler's arguments for use in RouteHandlerInvocationContext
544
+ // construction and route handler invocation.
545
+ // context.GetArgument<string>(0)
546
+ // (string, name_local), (int, int_local)
547
+ factoryContext . ContextArgAccess . Add ( Expression . Call ( FilterContextExpr , RouteHandlerInvocationContextGetArgument . MakeGenericMethod ( parameters [ i ] . ParameterType ) , Expression . Constant ( i ) ) ) ;
548
+ }
549
+ else
550
+ {
551
+ // We box if dynamic code isn't supported
552
+ factoryContext . ContextArgAccess . Add ( Expression . Convert (
553
+ Expression . Property ( FilterContextArgumentsExpr , ListIndexer , Expression . Constant ( i ) ) ,
554
+ parameters [ i ] . ParameterType ) ) ;
555
+ }
556
+ }
557
+
526
558
factoryContext . ArgumentTypes [ i ] = parameters [ i ] . ParameterType ;
527
559
factoryContext . ArgumentExpressions [ i ] = args [ i ] ;
528
560
factoryContext . BoxedArgs [ i ] = Expression . Convert ( args [ i ] , typeof ( object ) ) ;
@@ -567,17 +599,17 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
567
599
throw new InvalidOperationException ( $ "'{ routeName } ' is not a route parameter.") ;
568
600
}
569
601
570
- return BindParameterFromProperty ( parameter , RouteValuesExpr , routeName , factoryContext , "route" ) ;
602
+ return BindParameterFromProperty ( parameter , RouteValuesExpr , RouteValuesIndexerProperty , routeName , factoryContext , "route" ) ;
571
603
}
572
604
else if ( parameterCustomAttributes . OfType < IFromQueryMetadata > ( ) . FirstOrDefault ( ) is { } queryAttribute )
573
605
{
574
606
factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryAttribute ) ;
575
- return BindParameterFromProperty ( parameter , QueryExpr , queryAttribute . Name ?? parameter . Name , factoryContext , "query string" ) ;
607
+ return BindParameterFromProperty ( parameter , QueryExpr , QueryIndexerProperty , queryAttribute . Name ?? parameter . Name , factoryContext , "query string" ) ;
576
608
}
577
609
else if ( parameterCustomAttributes . OfType < IFromHeaderMetadata > ( ) . FirstOrDefault ( ) is { } headerAttribute )
578
610
{
579
611
factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . HeaderAttribute ) ;
580
- return BindParameterFromProperty ( parameter , HeadersExpr , headerAttribute . Name ?? parameter . Name , factoryContext , "header" ) ;
612
+ return BindParameterFromProperty ( parameter , HeadersExpr , HeaderIndexerProperty , headerAttribute . Name ?? parameter . Name , factoryContext , "header" ) ;
581
613
}
582
614
else if ( parameterCustomAttributes . OfType < IFromBodyMetadata > ( ) . FirstOrDefault ( ) is { } bodyAttribute )
583
615
{
@@ -672,12 +704,12 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
672
704
// We're in the fallback case and we have a parameter and route parameter match so don't fallback
673
705
// to query string in this case
674
706
factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteParameter ) ;
675
- return BindParameterFromProperty ( parameter , RouteValuesExpr , parameter . Name , factoryContext , "route" ) ;
707
+ return BindParameterFromProperty ( parameter , RouteValuesExpr , RouteValuesIndexerProperty , parameter . Name , factoryContext , "route" ) ;
676
708
}
677
709
else
678
710
{
679
711
factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryStringParameter ) ;
680
- return BindParameterFromProperty ( parameter , QueryExpr , parameter . Name , factoryContext , "query string" ) ;
712
+ return BindParameterFromProperty ( parameter , QueryExpr , QueryIndexerProperty , parameter . Name , factoryContext , "query string" ) ;
681
713
}
682
714
}
683
715
@@ -692,7 +724,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
692
724
// We only infer parameter types if you have an array of TryParsables/string[]/StringValues, and DisableInferredFromBody is true
693
725
694
726
factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryStringParameter ) ;
695
- return BindParameterFromProperty ( parameter , QueryExpr , parameter . Name , factoryContext , "query string" ) ;
727
+ return BindParameterFromProperty ( parameter , QueryExpr , QueryIndexerProperty , parameter . Name , factoryContext , "query string" ) ;
696
728
}
697
729
else
698
730
{
@@ -1233,9 +1265,8 @@ static void ThrowIfRequestIsAuthenticated(HttpContext httpContext)
1233
1265
}
1234
1266
}
1235
1267
1236
- private static Expression GetValueFromProperty ( Expression sourceExpression , string key , Type ? returnType = null )
1268
+ private static Expression GetValueFromProperty ( MemberExpression sourceExpression , PropertyInfo itemProperty , string key , Type ? returnType = null )
1237
1269
{
1238
- var itemProperty = sourceExpression . Type . GetProperty ( "Item" ) ;
1239
1270
var indexArguments = new [ ] { Expression . Constant ( key ) } ;
1240
1271
var indexExpression = Expression . MakeIndex ( sourceExpression , itemProperty , indexArguments ) ;
1241
1272
return Expression . Convert ( indexExpression , returnType ?? typeof ( string ) ) ;
@@ -1601,8 +1632,8 @@ private static Expression BindParameterFromExpression(
1601
1632
Expression . Convert ( Expression . Constant ( parameter . DefaultValue ) , parameter . ParameterType ) ) ) ;
1602
1633
}
1603
1634
1604
- private static Expression BindParameterFromProperty ( ParameterInfo parameter , MemberExpression property , string key , FactoryContext factoryContext , string source ) =>
1605
- BindParameterFromValue ( parameter , GetValueFromProperty ( property , key , GetExpressionType ( parameter . ParameterType ) ) , factoryContext , source ) ;
1635
+ private static Expression BindParameterFromProperty ( ParameterInfo parameter , MemberExpression property , PropertyInfo itemProperty , string key , FactoryContext factoryContext , string source ) =>
1636
+ BindParameterFromValue ( parameter , GetValueFromProperty ( property , itemProperty , key , GetExpressionType ( parameter . ParameterType ) ) , factoryContext , source ) ;
1606
1637
1607
1638
private static Type ? GetExpressionType ( Type type ) =>
1608
1639
type . IsArray ? typeof ( string [ ] ) :
@@ -1611,8 +1642,8 @@ private static Expression BindParameterFromProperty(ParameterInfo parameter, Mem
1611
1642
1612
1643
private static Expression BindParameterFromRouteValueOrQueryString ( ParameterInfo parameter , string key , FactoryContext factoryContext )
1613
1644
{
1614
- var routeValue = GetValueFromProperty ( RouteValuesExpr , key ) ;
1615
- var queryValue = GetValueFromProperty ( QueryExpr , key ) ;
1645
+ var routeValue = GetValueFromProperty ( RouteValuesExpr , RouteValuesIndexerProperty , key ) ;
1646
+ var queryValue = GetValueFromProperty ( QueryExpr , QueryIndexerProperty , key ) ;
1616
1647
return BindParameterFromValue ( parameter , Expression . Coalesce ( routeValue , queryValue ) , factoryContext , "route or query string" ) ;
1617
1648
}
1618
1649
@@ -1702,7 +1733,7 @@ private static Expression BindParameterFromFormFile(
1702
1733
1703
1734
factoryContext . ReadForm = true ;
1704
1735
1705
- var valueExpression = GetValueFromProperty ( FormFilesExpr , key , typeof ( IFormFile ) ) ;
1736
+ var valueExpression = GetValueFromProperty ( FormFilesExpr , FormFilesIndexerProperty , key , typeof ( IFormFile ) ) ;
1706
1737
1707
1738
return BindParameterFromExpression ( parameter , valueExpression , factoryContext , "form file" ) ;
1708
1739
}
@@ -2088,7 +2119,7 @@ private sealed class FactoryContext
2088
2119
public Expression ? MethodCall { get ; set ; }
2089
2120
public Type [ ] ArgumentTypes { get ; set ; } = Array . Empty < Type > ( ) ;
2090
2121
public Expression [ ] ArgumentExpressions { get ; set ; } = Array . Empty < Expression > ( ) ;
2091
- public Expression [ ] BoxedArgs { get ; set ; } = Array . Empty < Expression > ( ) ;
2122
+ public Expression [ ] BoxedArgs { get ; set ; } = Array . Empty < Expression > ( ) ;
2092
2123
public List < Func < RouteHandlerContext , RouteHandlerFilterDelegate , RouteHandlerFilterDelegate > > ? Filters { get ; init ; }
2093
2124
2094
2125
public List < ParameterInfo > Parameters { get ; set ; } = new ( ) ;
0 commit comments