Skip to content

Commit bad6176

Browse files
authored
Fix support for native AOT in minimal (#42272)
* Fix support for native AOT in minimal - This includes 2 major changes to fix native AOT support in .NET 7. Explicitly rooting the indexers of various binding sources (query, formfile, header, routes). NativeAOT seems a bit more aggressive here. - We avoid the filter optimization that uses generics to avoid boxing. NativeAOT needs to see generic instantiations statically which we cannot do here.
1 parent 30423c3 commit bad6176

File tree

1 file changed

+51
-20
lines changed

1 file changed

+51
-20
lines changed

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

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Text.Json;
1616
using Microsoft.AspNetCore.Http.Features;
1717
using Microsoft.AspNetCore.Http.Metadata;
18+
using Microsoft.AspNetCore.Routing;
1819
using Microsoft.Extensions.DependencyInjection;
1920
using Microsoft.Extensions.Internal;
2021
using Microsoft.Extensions.Logging;
@@ -58,6 +59,11 @@ public static partial class RequestDelegateFactory
5859
private static readonly MethodInfo PopulateMetadataForEndpointMethod = typeof(RequestDelegateFactory).GetMethod(nameof(PopulateMetadataForEndpoint), BindingFlags.NonPublic | BindingFlags.Static)!;
5960
private static readonly MethodInfo ArrayEmptyOfObjectMethod = typeof(Array).GetMethod(nameof(Array.Empty), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(new Type[] { typeof(object) });
6061

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+
6167
// Call WriteAsJsonAsync<object?>() to serialize the runtime return type rather than the declared return type.
6268
// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism
6369
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
98104

99105
private static readonly ConstructorInfo DefaultRouteHandlerInvocationContextConstructor = typeof(DefaultRouteHandlerInvocationContext).GetConstructor(new[] { typeof(HttpContext), typeof(object[]) })!;
100106
private static readonly MethodInfo RouteHandlerInvocationContextGetArgument = typeof(RouteHandlerInvocationContext).GetMethod(nameof(RouteHandlerInvocationContext.GetArgument))!;
107+
private static readonly PropertyInfo ListIndexer = typeof(IList<object>).GetProperty("Item")!;
101108
private static readonly ParameterExpression FilterContextExpr = Expression.Parameter(typeof(RouteHandlerInvocationContext), "context");
102109
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))!);
103111
private static readonly MemberExpression FilterContextHttpContextResponseExpr = Expression.Property(FilterContextHttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
104112
private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
105113
private static readonly ParameterExpression InvokedFilterContextExpr = Expression.Parameter(typeof(RouteHandlerInvocationContext), "filterContext");
@@ -397,6 +405,13 @@ private static Expression CreateRouteHandlerInvocationContextBase(FactoryContext
397405
DefaultRouteHandlerInvocationContextConstructor,
398406
new Expression[] { HttpContextExpr, paramArray });
399407

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+
400415
var arguments = new Expression[factoryContext.ArgumentExpressions.Length + 1];
401416
arguments[0] = HttpContextExpr;
402417
factoryContext.ArgumentExpressions.CopyTo(arguments, 1);
@@ -513,16 +528,33 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
513528
factoryContext.BoxedArgs = new Expression[parameters.Length];
514529
factoryContext.Parameters = new List<ParameterInfo>(parameters);
515530

531+
var hasFilters = factoryContext.Filters is { Count: > 0 };
532+
516533
for (var i = 0; i < parameters.Length; i++)
517534
{
518535
args[i] = CreateArgument(parameters[i], factoryContext);
519536

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+
526558
factoryContext.ArgumentTypes[i] = parameters[i].ParameterType;
527559
factoryContext.ArgumentExpressions[i] = args[i];
528560
factoryContext.BoxedArgs[i] = Expression.Convert(args[i], typeof(object));
@@ -567,17 +599,17 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
567599
throw new InvalidOperationException($"'{routeName}' is not a route parameter.");
568600
}
569601

570-
return BindParameterFromProperty(parameter, RouteValuesExpr, routeName, factoryContext, "route");
602+
return BindParameterFromProperty(parameter, RouteValuesExpr, RouteValuesIndexerProperty, routeName, factoryContext, "route");
571603
}
572604
else if (parameterCustomAttributes.OfType<IFromQueryMetadata>().FirstOrDefault() is { } queryAttribute)
573605
{
574606
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");
576608
}
577609
else if (parameterCustomAttributes.OfType<IFromHeaderMetadata>().FirstOrDefault() is { } headerAttribute)
578610
{
579611
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");
581613
}
582614
else if (parameterCustomAttributes.OfType<IFromBodyMetadata>().FirstOrDefault() is { } bodyAttribute)
583615
{
@@ -672,12 +704,12 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
672704
// We're in the fallback case and we have a parameter and route parameter match so don't fallback
673705
// to query string in this case
674706
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");
676708
}
677709
else
678710
{
679711
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");
681713
}
682714
}
683715

@@ -692,7 +724,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
692724
// We only infer parameter types if you have an array of TryParsables/string[]/StringValues, and DisableInferredFromBody is true
693725

694726
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");
696728
}
697729
else
698730
{
@@ -1233,9 +1265,8 @@ static void ThrowIfRequestIsAuthenticated(HttpContext httpContext)
12331265
}
12341266
}
12351267

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)
12371269
{
1238-
var itemProperty = sourceExpression.Type.GetProperty("Item");
12391270
var indexArguments = new[] { Expression.Constant(key) };
12401271
var indexExpression = Expression.MakeIndex(sourceExpression, itemProperty, indexArguments);
12411272
return Expression.Convert(indexExpression, returnType ?? typeof(string));
@@ -1601,8 +1632,8 @@ private static Expression BindParameterFromExpression(
16011632
Expression.Convert(Expression.Constant(parameter.DefaultValue), parameter.ParameterType)));
16021633
}
16031634

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);
16061637

16071638
private static Type? GetExpressionType(Type type) =>
16081639
type.IsArray ? typeof(string[]) :
@@ -1611,8 +1642,8 @@ private static Expression BindParameterFromProperty(ParameterInfo parameter, Mem
16111642

16121643
private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo parameter, string key, FactoryContext factoryContext)
16131644
{
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);
16161647
return BindParameterFromValue(parameter, Expression.Coalesce(routeValue, queryValue), factoryContext, "route or query string");
16171648
}
16181649

@@ -1702,7 +1733,7 @@ private static Expression BindParameterFromFormFile(
17021733

17031734
factoryContext.ReadForm = true;
17041735

1705-
var valueExpression = GetValueFromProperty(FormFilesExpr, key, typeof(IFormFile));
1736+
var valueExpression = GetValueFromProperty(FormFilesExpr, FormFilesIndexerProperty, key, typeof(IFormFile));
17061737

17071738
return BindParameterFromExpression(parameter, valueExpression, factoryContext, "form file");
17081739
}
@@ -2088,7 +2119,7 @@ private sealed class FactoryContext
20882119
public Expression? MethodCall { get; set; }
20892120
public Type[] ArgumentTypes { get; set; } = Array.Empty<Type>();
20902121
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>();
20922123
public List<Func<RouteHandlerContext, RouteHandlerFilterDelegate, RouteHandlerFilterDelegate>>? Filters { get; init; }
20932124

20942125
public List<ParameterInfo> Parameters { get; set; } = new();

0 commit comments

Comments
 (0)