3
3
4
4
using System ;
5
5
using System . Collections . Concurrent ;
6
+ using System . Collections . Generic ;
6
7
using System . IO ;
7
8
using System . Linq ;
8
9
using System . Linq . Expressions ;
@@ -53,6 +54,8 @@ public static class RequestDelegateFactory
53
54
private static readonly MemberExpression StatusCodeExpr = Expression . Property ( HttpResponseExpr , nameof ( HttpResponse . StatusCode ) ) ;
54
55
private static readonly MemberExpression CompletedTaskExpr = Expression . Property ( null , ( PropertyInfo ) GetMemberInfo < Func < Task > > ( ( ) => Task . CompletedTask ) ) ;
55
56
57
+ private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression . NotEqual ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
58
+
56
59
private static readonly ConcurrentDictionary < Type , MethodInfo ? > TryParseMethodCache = new ( ) ;
57
60
58
61
/// <summary>
@@ -155,10 +158,15 @@ public static RequestDelegate Create(MethodInfo methodInfo, Func<HttpContext, ob
155
158
156
159
var arguments = CreateArguments ( methodInfo . GetParameters ( ) , factoryContext ) ;
157
160
158
- var responseWritingMethodCall = factoryContext . CheckForTryParseFailure ?
159
- CreateTryParseCheckingResponseWritingMethodCall ( methodInfo , targetExpression , arguments ) :
161
+ var responseWritingMethodCall = factoryContext . TryParseParams . Count > 0 ?
162
+ CreateTryParseCheckingResponseWritingMethodCall ( methodInfo , targetExpression , arguments , factoryContext ) :
160
163
CreateResponseWritingMethodCall ( methodInfo , targetExpression , arguments ) ;
161
164
165
+ if ( factoryContext . UsingTempSourceString )
166
+ {
167
+ responseWritingMethodCall = Expression . Block ( new [ ] { TempSourceStringExpr } , responseWritingMethodCall ) ;
168
+ }
169
+
162
170
return HandleRequestBodyAndCompileRequestDelegate ( responseWritingMethodCall , factoryContext ) ;
163
171
}
164
172
@@ -242,30 +250,30 @@ private static Expression CreateResponseWritingMethodCall(MethodInfo methodInfo,
242
250
}
243
251
244
252
// If we're calling TryParse and wasTryParseFailure indicates it failed, set a 400 StatusCode instead of calling the method.
245
- private static Expression CreateTryParseCheckingResponseWritingMethodCall ( MethodInfo methodInfo , Expression ? target , Expression [ ] arguments )
253
+ private static Expression CreateTryParseCheckingResponseWritingMethodCall (
254
+ MethodInfo methodInfo , Expression ? target , Expression [ ] arguments , FactoryContext factoryContext )
246
255
{
247
256
// {
248
- // bool wasTryParseFailure = false;
249
257
// string tempSourceString;
258
+ // bool wasTryParseFailure = false;
250
259
//
251
- // // Assume "[FromRoute] int id" is the first parameter.
252
- //
253
- // tempSourceString = httpContext.RequestValue["id"];
254
- // int param1 = tempSourceString == null ? default :
255
- // {
256
- // int parsedValue = default;
260
+ // // Assume "int param1" is the first parameter, "[FromRoute] int? param2 = 42" is the second parameter ...
261
+ // int param1_local;
262
+ // int? param2_local;
263
+ // // ...
257
264
//
258
- // if (!int.TryParse(tempSourceString, out parsedValue))
259
- // {
260
- // wasTryParseFailure = true;
261
- // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
262
- // }
265
+ // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
263
266
//
264
- // return parsedValue;
265
- // };
267
+ // if (tempSourceString != null)
268
+ // {
269
+ // if (!int.TryParse(tempSourceString, out param1_local))
270
+ // {
271
+ // wasTryParseFailure = true;
272
+ // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
273
+ // }
274
+ // }
266
275
//
267
- // tempSourceString = httpContext.RequestValue["param2"];
268
- // int param2 = tempSourceString == null ? default :
276
+ // tempSourceString = httpContext.RouteValue["param2"];
269
277
// // ...
270
278
//
271
279
// return wasTryParseFailure ?
@@ -274,41 +282,33 @@ private static Expression CreateTryParseCheckingResponseWritingMethodCall(Method
274
282
// return Task.CompletedTask;
275
283
// } :
276
284
// {
277
- // // Logic generated by AddResponseWritingToMethodCall() that calls action(param1, parm2 , ...)
285
+ // // Logic generated by AddResponseWritingToMethodCall() that calls action(param1_local, param2_local , ...)
278
286
// };
279
287
// }
280
288
281
- var parameters = methodInfo . GetParameters ( ) ;
282
- var storedArguments = new ParameterExpression [ parameters . Length ] ;
283
- var localVariables = new ParameterExpression [ parameters . Length + 2 ] ;
289
+ var localVariables = new ParameterExpression [ factoryContext . TryParseParams . Count + 1 ] ;
290
+ var tryParseAndCallMethod = new Expression [ factoryContext . TryParseParams . Count + 1 ] ;
284
291
285
- for ( var i = 0 ; i < parameters . Length ; i ++ )
292
+ for ( var i = 0 ; i < factoryContext . TryParseParams . Count ; i ++ )
286
293
{
287
- storedArguments [ i ] = localVariables [ i ] = Expression . Parameter ( parameters [ i ] . ParameterType ) ;
294
+ ( localVariables [ i ] , tryParseAndCallMethod [ i ] ) = factoryContext . TryParseParams [ i ] ;
288
295
}
289
296
290
- localVariables [ parameters . Length ] = WasTryParseFailureExpr ;
291
- localVariables [ parameters . Length + 1 ] = TempSourceStringExpr ;
292
-
293
- var assignAndCall = new Expression [ parameters . Length + 1 ] ;
294
- for ( var i = 0 ; i < parameters . Length ; i ++ )
295
- {
296
- assignAndCall [ i ] = Expression . Assign ( localVariables [ i ] , arguments [ i ] ) ;
297
- }
297
+ localVariables [ factoryContext . TryParseParams . Count ] = WasTryParseFailureExpr ;
298
298
299
299
var set400StatusAndReturnCompletedTask = Expression . Block (
300
300
Expression . Assign ( StatusCodeExpr , Expression . Constant ( 400 ) ) ,
301
301
CompletedTaskExpr ) ;
302
302
303
- var methodCall = CreateMethodCall ( methodInfo , target , storedArguments ) ;
303
+ var methodCall = CreateMethodCall ( methodInfo , target , arguments ) ;
304
304
305
305
var checkWasTryParseFailure = Expression . Condition ( WasTryParseFailureExpr ,
306
306
set400StatusAndReturnCompletedTask ,
307
307
AddResponseWritingToMethodCall ( methodCall , methodInfo . ReturnType ) ) ;
308
308
309
- assignAndCall [ parameters . Length ] = checkWasTryParseFailure ;
309
+ tryParseAndCallMethod [ factoryContext . TryParseParams . Count ] = checkWasTryParseFailure ;
310
310
311
- return Expression . Block ( localVariables , assignAndCall ) ;
311
+ return Expression . Block ( localVariables , tryParseAndCallMethod ) ;
312
312
}
313
313
314
314
private static Expression AddResponseWritingToMethodCall ( Expression methodCall , Type returnType )
@@ -540,10 +540,24 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
540
540
{
541
541
if ( parameter . ParameterType == typeof ( string ) )
542
542
{
543
- return valueExpression ;
543
+ if ( ! parameter . HasDefaultValue )
544
+ {
545
+ return valueExpression ;
546
+ }
547
+
548
+ factoryContext . UsingTempSourceString = true ;
549
+ return Expression . Block (
550
+ Expression . Assign ( TempSourceStringExpr , valueExpression ) ,
551
+ Expression . Condition ( TempSourceStringNotNullExpr ,
552
+ TempSourceStringExpr ,
553
+ Expression . Constant ( parameter . DefaultValue ) ) ) ;
544
554
}
545
555
556
+ factoryContext . UsingTempSourceString = true ;
557
+
546
558
var underlyingNullableType = Nullable . GetUnderlyingType ( parameter . ParameterType ) ;
559
+ var isNotNullable = underlyingNullableType is null ;
560
+
547
561
var nonNullableParameterType = underlyingNullableType ?? parameter . ParameterType ;
548
562
var tryParseMethod = FindTryParseMethod ( nonNullableParameterType ) ;
549
563
@@ -552,28 +566,47 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
552
566
throw new InvalidOperationException ( $ "No public static bool { parameter . ParameterType . Name } .TryParse(string, out { parameter . ParameterType . Name } ) method found for { parameter . Name } .") ;
553
567
}
554
568
555
- // bool wasTryParseFailure = false;
556
569
// string tempSourceString;
570
+ // bool wasTryParseFailure = false;
557
571
//
558
- // // Assume "[FromRoute] int id" is the first parameter.
559
- // tempSourceString = httpContext.RequestValue["id"];
572
+ // // Assume "int param1" is the first parameter and "[FromRoute] int? param2 = 42" is the second parameter.
573
+ // int param1_local;
574
+ // int? param2_local;
560
575
//
561
- // int param1 = tempSourceString == null ? default :
576
+ // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
577
+ //
578
+ // if (tempSourceString != null)
562
579
// {
563
- // int parsedValue = default;
580
+ // if (!int.TryParse(tempSourceString, out param1_local))
581
+ // {
582
+ // wasTryParseFailure = true;
583
+ // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
584
+ // }
585
+ // }
564
586
//
565
- // if (!int.TryParse(tempSourceString, out parsedValue))
566
- // {
567
- // wasTryParseFailure = true;
568
- // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
569
- // }
587
+ // tempSourceString = httpContext.RouteValue["param2"];
570
588
//
571
- // return parsedValue;
572
- // };
589
+ // if (tempSourceString != null)
590
+ // {
591
+ // if (int.TryParse(tempSourceString, out int parsedValue))
592
+ // {
593
+ // param2_local = parsedValue;
594
+ // }
595
+ // else
596
+ // {
597
+ // wasTryParseFailure = true;
598
+ // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
599
+ // }
600
+ // }
601
+ // else
602
+ // {
603
+ // param2_local = 42;
604
+ // }
573
605
574
- factoryContext . CheckForTryParseFailure = true ;
606
+ var argument = Expression . Variable ( parameter . ParameterType , $ " { parameter . Name } _local" ) ;
575
607
576
- var parsedValue = Expression . Variable ( nonNullableParameterType ) ;
608
+ // If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot the parameter directly.
609
+ var parsedValue = isNotNullable ? argument : Expression . Variable ( nonNullableParameterType , "parsedValue" ) ;
577
610
578
611
var parameterTypeNameConstant = Expression . Constant ( parameter . ParameterType . Name ) ;
579
612
var parameterNameConstant = Expression . Constant ( parameter . Name ) ;
@@ -584,32 +617,30 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
584
617
HttpContextExpr , parameterTypeNameConstant , parameterNameConstant , TempSourceStringExpr ) ) ;
585
618
586
619
var tryParseCall = Expression . Call ( tryParseMethod , TempSourceStringExpr , parsedValue ) ;
587
- var ifFailExpression = Expression . IfThen ( Expression . Not ( tryParseCall ) , failBlock ) ;
588
-
589
- Expression parsedValueExpression = Expression . Block ( new [ ] { parsedValue } ,
590
- ifFailExpression ,
591
- parsedValue ) ;
592
-
593
- // Convert back to nullable if necessary.
594
- if ( underlyingNullableType is not null )
595
- {
596
- parsedValueExpression = Expression . Convert ( parsedValueExpression , parameter . ParameterType ) ;
597
- }
598
-
599
- Expression defaultExpression = parameter . HasDefaultValue ?
600
- Expression . Constant ( parameter . DefaultValue ) :
601
- Expression . Default ( parameter . ParameterType ) ;
602
-
603
- // tempSourceString = httpContext.RequestValue["id"];
604
- var storeValueToTemp = Expression . Assign ( TempSourceStringExpr , valueExpression ) ;
605
-
606
- // int param1 = tempSourcString == null ? default : ...
607
- var ternary = Expression . Condition (
608
- Expression . Equal ( TempSourceStringExpr , Expression . Constant ( null ) ) ,
609
- defaultExpression ,
610
- parsedValueExpression ) ;
611
620
612
- return Expression . Block ( storeValueToTemp , ternary ) ;
621
+ // If the parameter is nullable, we need to assign the "parsedValue" local to the nullable parameter on success.
622
+ Expression tryParseExpression = isNotNullable ?
623
+ Expression . IfThen ( Expression . Not ( tryParseCall ) , failBlock ) :
624
+ Expression . Block ( new [ ] { parsedValue } ,
625
+ Expression . IfThenElse ( tryParseCall ,
626
+ Expression . Assign ( argument , Expression . Convert ( parsedValue , parameter . ParameterType ) ) ,
627
+ failBlock ) ) ;
628
+
629
+ var ifNotNullTryParse = ! parameter . HasDefaultValue ?
630
+ Expression . IfThen ( TempSourceStringNotNullExpr , tryParseExpression ) :
631
+ Expression . IfThenElse ( TempSourceStringNotNullExpr ,
632
+ tryParseExpression ,
633
+ Expression . Assign ( argument , Expression . Constant ( parameter . DefaultValue ) ) ) ;
634
+
635
+ var fullTryParseBlock = Expression . Block (
636
+ // tempSourceString = httpContext.RequestValue["id"];
637
+ Expression . Assign ( TempSourceStringExpr , valueExpression ) ,
638
+ // if (tempSourceString != null) { ... }
639
+ ifNotNullTryParse ) ;
640
+
641
+ factoryContext . TryParseParams . Add ( ( argument , fullTryParseBlock ) ) ;
642
+
643
+ return argument ;
613
644
}
614
645
615
646
private static Expression BindParameterFromProperty ( ParameterInfo parameter , MemberExpression property , string key , FactoryContext factoryContext ) =>
@@ -747,7 +778,8 @@ private class FactoryContext
747
778
public Type ? JsonRequestBodyType { get ; set ; }
748
779
public bool AllowEmptyRequestBody { get ; set ; }
749
780
750
- public bool CheckForTryParseFailure { get ; set ; }
781
+ public bool UsingTempSourceString { get ; set ; }
782
+ public List < ( ParameterExpression , Expression ) > TryParseParams { get ; } = new ( ) ;
751
783
}
752
784
753
785
private static class Log
0 commit comments