@@ -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 JsonResultWriteResponseAsyncMethod = GetMethodInfo < Func < HttpResponse , object , Task > > ( ( response , value ) => HttpResponseJsonExtensions . WriteAsJsonAsync ( response , value , default ) ) ;
42
+ private static readonly MethodInfo StringIsNullOrEmptyMethod = typeof ( string ) . GetMethod ( nameof ( string . IsNullOrEmpty ) , BindingFlags . Static | BindingFlags . Public ) ! ;
42
43
43
44
private static readonly MethodInfo LogParameterBindingFailedMethod = GetMethodInfo < Action < HttpContext , string , string , string , bool > > ( ( httpContext , parameterType , parameterName , sourceValue , shouldThrow ) =>
44
45
Log . ParameterBindingFailed ( httpContext , parameterType , parameterName , sourceValue , shouldThrow ) ) ;
@@ -71,6 +72,8 @@ public static partial class RequestDelegateFactory
71
72
private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache . TempSourceStringExpr ;
72
73
private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression . NotEqual ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
73
74
private static readonly BinaryExpression TempSourceStringNullExpr = Expression . Equal ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
75
+ private static readonly UnaryExpression TempSourceStringIsNotNullOrEmptyExpr = Expression . Not ( Expression . Call ( StringIsNullOrEmptyMethod , TempSourceStringExpr ) ) ;
76
+
74
77
private static readonly string [ ] DefaultAcceptsContentType = new [ ] { "application/json" } ;
75
78
private static readonly string [ ] FormFileContentType = new [ ] { "multipart/form-data" } ;
76
79
@@ -202,6 +205,7 @@ private static Expression[] CreateArguments(ParameterInfo[]? parameters, Factory
202
205
var errorMessage = BuildErrorMessageForInferredBodyParameter ( factoryContext ) ;
203
206
throw new InvalidOperationException ( errorMessage ) ;
204
207
}
208
+
205
209
if ( factoryContext . JsonRequestBodyParameter is not null &&
206
210
factoryContext . FirstFormRequestBodyParameter is not null )
207
211
{
@@ -317,7 +321,7 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
317
321
{
318
322
return BindParameterFromBindAsync ( parameter , factoryContext ) ;
319
323
}
320
- else if ( parameter . ParameterType == typeof ( string ) || ParameterBindingMethodCache . HasTryParseMethod ( parameter ) )
324
+ else if ( parameter . ParameterType == typeof ( string ) || ParameterBindingMethodCache . HasTryParseMethod ( parameter . ParameterType ) )
321
325
{
322
326
// 1. We bind from route values only, if route parameters are non-null and the parameter name is in that set.
323
327
// 2. We bind from query only, if route parameters are non-null and the parameter name is NOT in that set.
@@ -342,6 +346,16 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
342
346
factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . RouteOrQueryStringParameter ) ;
343
347
return BindParameterFromRouteValueOrQueryString ( parameter , parameter . Name , factoryContext ) ;
344
348
}
349
+ else if ( factoryContext . DisableInferredFromBody && (
350
+ ( parameter . ParameterType . IsArray && ParameterBindingMethodCache . HasTryParseMethod ( parameter . ParameterType . GetElementType ( ) ! ) ) ||
351
+ parameter . ParameterType == typeof ( string [ ] ) ||
352
+ parameter . ParameterType == typeof ( StringValues ) ) )
353
+ {
354
+ // We only infer parameter types if you have an array of TryParsables/string[]/StringValues, and DisableInferredFromBody is true
355
+
356
+ factoryContext . TrackedParameters . Add ( parameter . Name , RequestDelegateFactoryConstants . QueryStringParameter ) ;
357
+ return BindParameterFromProperty ( parameter , QueryExpr , parameter . Name , factoryContext , "query string" ) ;
358
+ }
345
359
else
346
360
{
347
361
if ( factoryContext . ServiceProviderIsService is IServiceProviderIsService serviceProviderIsService )
@@ -884,22 +898,24 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
884
898
var parameterNameConstant = Expression . Constant ( parameter . Name ) ;
885
899
var sourceConstant = Expression . Constant ( source ) ;
886
900
887
- if ( parameter . ParameterType == typeof ( string ) )
901
+ if ( parameter . ParameterType == typeof ( string ) || parameter . ParameterType == typeof ( string [ ] ) || parameter . ParameterType == typeof ( StringValues ) )
888
902
{
889
903
return BindParameterFromExpression ( parameter , valueExpression , factoryContext , source ) ;
890
904
}
891
905
892
906
factoryContext . UsingTempSourceString = true ;
893
907
894
- var underlyingNullableType = Nullable . GetUnderlyingType ( parameter . ParameterType ) ;
908
+ var targetParseType = parameter . ParameterType . IsArray ? parameter . ParameterType . GetElementType ( ) ! : parameter . ParameterType ;
909
+
910
+ var underlyingNullableType = Nullable . GetUnderlyingType ( targetParseType ) ;
895
911
var isNotNullable = underlyingNullableType is null ;
896
912
897
- var nonNullableParameterType = underlyingNullableType ?? parameter . ParameterType ;
913
+ var nonNullableParameterType = underlyingNullableType ?? targetParseType ;
898
914
var tryParseMethodCall = ParameterBindingMethodCache . FindTryParseMethod ( nonNullableParameterType ) ;
899
915
900
916
if ( tryParseMethodCall is null )
901
917
{
902
- var typeName = TypeNameHelper . GetTypeDisplayName ( parameter . ParameterType , fullName : false ) ;
918
+ var typeName = TypeNameHelper . GetTypeDisplayName ( targetParseType , fullName : false ) ;
903
919
throw new InvalidOperationException ( $ "No public static bool { typeName } .TryParse(string, out { typeName } ) method found for { parameter . Name } .") ;
904
920
}
905
921
@@ -940,8 +956,32 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
940
956
// param2_local = 42;
941
957
// }
942
958
959
+ // string[]? values = httpContext.Request.Query["param1"].ToArray();
960
+ // int[] param_local = values.Length > 0 ? new int[values.Length] : Array.Empty<int>();
961
+
962
+ // if (values != null)
963
+ // {
964
+ // int index = 0;
965
+ // while (index < values.Length)
966
+ // {
967
+ // tempSourceString = values[i];
968
+ // if (int.TryParse(tempSourceString, out var parsedValue))
969
+ // {
970
+ // param_local[i] = parsedValue;
971
+ // }
972
+ // else
973
+ // {
974
+ // wasParamCheckFailure = true;
975
+ // Log.ParameterBindingFailed(httpContext, "Int32[]", "param1", tempSourceString);
976
+ // break;
977
+ // }
978
+ //
979
+ // index++
980
+ // }
981
+ // }
982
+
943
983
// If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot use the parameter directly.
944
- var parsedValue = isNotNullable ? argument : Expression . Variable ( nonNullableParameterType , "parsedValue" ) ;
984
+ var parsedValue = Expression . Variable ( nonNullableParameterType , "parsedValue" ) ;
945
985
946
986
var failBlock = Expression . Block (
947
987
Expression . Assign ( WasParamCheckFailureExpr , Expression . Constant ( true ) ) ,
@@ -970,33 +1010,104 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
970
1010
)
971
1011
) ;
972
1012
1013
+ var index = Expression . Variable ( typeof ( int ) , "index" ) ;
1014
+
973
1015
// If the parameter is nullable, we need to assign the "parsedValue" local to the nullable parameter on success.
974
- Expression tryParseExpression = isNotNullable ?
975
- Expression . IfThen ( Expression . Not ( tryParseCall ) , failBlock ) :
976
- Expression . Block ( new [ ] { parsedValue } ,
1016
+ var tryParseExpression = Expression . Block ( new [ ] { parsedValue } ,
977
1017
Expression . IfThenElse ( tryParseCall ,
978
- Expression . Assign ( argument , Expression . Convert ( parsedValue , parameter . ParameterType ) ) ,
1018
+ Expression . Assign ( parameter . ParameterType . IsArray ? Expression . ArrayAccess ( argument , index ) : argument , Expression . Convert ( parsedValue , targetParseType ) ) ,
979
1019
failBlock ) ) ;
980
1020
981
- var ifNotNullTryParse = ! parameter . HasDefaultValue ?
982
- Expression . IfThen ( TempSourceStringNotNullExpr , tryParseExpression ) :
983
- Expression . IfThenElse ( TempSourceStringNotNullExpr ,
984
- tryParseExpression ,
985
- Expression . Assign ( argument , Expression . Constant ( parameter . DefaultValue ) ) ) ;
1021
+ var ifNotNullTryParse = ! parameter . HasDefaultValue
1022
+ ? Expression . IfThen ( TempSourceStringNotNullExpr , tryParseExpression )
1023
+ : Expression . IfThenElse ( TempSourceStringNotNullExpr , tryParseExpression ,
1024
+ Expression . Assign ( argument ,
1025
+ Expression . Constant ( parameter . DefaultValue ) ) ) ;
1026
+
1027
+ var loopExit = Expression . Label ( ) ;
1028
+
1029
+ // REVIEW: We can reuse this like we reuse temp source string
1030
+ var stringArrayExpr = parameter . ParameterType . IsArray ? Expression . Variable ( typeof ( string [ ] ) , "tempStringArray" ) : null ;
1031
+ var elementTypeNullabilityInfo = parameter . ParameterType . IsArray ? factoryContext . NullabilityContext . Create ( parameter ) ? . ElementType : null ;
1032
+
1033
+ // Determine optionality of the element type of the array
1034
+ var elementTypeOptional = ! isNotNullable || ( elementTypeNullabilityInfo ? . ReadState != NullabilityState . NotNull ) ;
1035
+
1036
+ // The loop that populates the resulting array values
1037
+ var arrayLoop = parameter . ParameterType . IsArray ? Expression . Block (
1038
+ // param_local = new int[values.Length];
1039
+ Expression . Assign ( argument , Expression . NewArrayBounds ( parameter . ParameterType . GetElementType ( ) ! , Expression . ArrayLength ( stringArrayExpr ! ) ) ) ,
1040
+ // index = 0
1041
+ Expression . Assign ( index , Expression . Constant ( 0 ) ) ,
1042
+ // while (index < values.Length)
1043
+ Expression . Loop (
1044
+ Expression . Block (
1045
+ Expression . IfThenElse (
1046
+ Expression . LessThan ( index , Expression . ArrayLength ( stringArrayExpr ! ) ) ,
1047
+ // tempSourceString = values[index];
1048
+ Expression . Block (
1049
+ Expression . Assign ( TempSourceStringExpr , Expression . ArrayIndex ( stringArrayExpr ! , index ) ) ,
1050
+ elementTypeOptional ? Expression . IfThen ( TempSourceStringIsNotNullOrEmptyExpr , tryParseExpression )
1051
+ : tryParseExpression
1052
+ ) ,
1053
+ // else break
1054
+ Expression . Break ( loopExit )
1055
+ ) ,
1056
+ // index++
1057
+ Expression . PostIncrementAssign ( index )
1058
+ )
1059
+ , loopExit )
1060
+ ) : null ;
1061
+
1062
+ var fullParamCheckBlock = ( parameter . ParameterType . IsArray , isOptional ) switch
1063
+ {
1064
+ // (isArray: true, optional: true)
1065
+ ( true , true ) =>
1066
+
1067
+ Expression . Block (
1068
+ new [ ] { index , stringArrayExpr ! } ,
1069
+ // values = httpContext.Request.Query["id"];
1070
+ Expression . Assign ( stringArrayExpr ! , valueExpression ) ,
1071
+ Expression . IfThen (
1072
+ Expression . NotEqual ( stringArrayExpr ! , Expression . Constant ( null ) ) ,
1073
+ arrayLoop !
1074
+ )
1075
+ ) ,
1076
+
1077
+ // (isArray: true, optional: false)
1078
+ ( true , false ) =>
1079
+
1080
+ Expression . Block (
1081
+ new [ ] { index , stringArrayExpr ! } ,
1082
+ // values = httpContext.Request.Query["id"];
1083
+ Expression . Assign ( stringArrayExpr ! , valueExpression ) ,
1084
+ Expression . IfThenElse (
1085
+ Expression . NotEqual ( stringArrayExpr ! , Expression . Constant ( null ) ) ,
1086
+ arrayLoop ! ,
1087
+ failBlock
1088
+ )
1089
+ ) ,
986
1090
987
- var fullParamCheckBlock = ! isOptional
988
- ? Expression . Block (
1091
+ // (isArray: false, optional: false)
1092
+ ( false , false ) =>
1093
+
1094
+ Expression . Block (
989
1095
// tempSourceString = httpContext.RequestValue["id"];
990
1096
Expression . Assign ( TempSourceStringExpr , valueExpression ) ,
991
1097
// if (tempSourceString == null) { ... } only produced when parameter is required
992
1098
checkRequiredParaseableParameterBlock ,
993
1099
// if (tempSourceString != null) { ... }
994
- ifNotNullTryParse )
995
- : Expression . Block (
1100
+ ifNotNullTryParse ) ,
1101
+
1102
+ // (isArray: false, optional: true)
1103
+ ( false , true ) =>
1104
+
1105
+ Expression . Block (
996
1106
// tempSourceString = httpContext.RequestValue["id"];
997
1107
Expression . Assign ( TempSourceStringExpr , valueExpression ) ,
998
1108
// if (tempSourceString != null) { ... }
999
- ifNotNullTryParse ) ;
1109
+ ifNotNullTryParse )
1110
+ } ;
1000
1111
1001
1112
factoryContext . ExtraLocals . Add ( argument ) ;
1002
1113
factoryContext . ParamCheckExpressions . Add ( fullParamCheckBlock ) ;
@@ -1065,7 +1176,12 @@ private static Expression BindParameterFromExpression(
1065
1176
}
1066
1177
1067
1178
private static Expression BindParameterFromProperty ( ParameterInfo parameter , MemberExpression property , string key , FactoryContext factoryContext , string source ) =>
1068
- BindParameterFromValue ( parameter , GetValueFromProperty ( property , key ) , factoryContext , source ) ;
1179
+ BindParameterFromValue ( parameter , GetValueFromProperty ( property , key , GetExpressionType ( parameter . ParameterType ) ) , factoryContext , source ) ;
1180
+
1181
+ private static Type ? GetExpressionType ( Type type ) =>
1182
+ type . IsArray ? typeof ( string [ ] ) :
1183
+ type == typeof ( StringValues ) ? typeof ( StringValues ) :
1184
+ null ;
1069
1185
1070
1186
private static Expression BindParameterFromRouteValueOrQueryString ( ParameterInfo parameter , string key , FactoryContext factoryContext )
1071
1187
{
@@ -1077,7 +1193,6 @@ private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo
1077
1193
private static Expression BindParameterFromBindAsync ( ParameterInfo parameter , FactoryContext factoryContext )
1078
1194
{
1079
1195
// We reference the boundValues array by parameter index here
1080
- var nullability = factoryContext . NullabilityContext . Create ( parameter ) ;
1081
1196
var isOptional = IsOptionalParameter ( parameter , factoryContext ) ;
1082
1197
1083
1198
// Get the BindAsync method for the type.
0 commit comments