1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Diagnostics ;
4
5
using System . Linq ;
5
6
using System . Linq . Expressions ;
6
7
using System . Reflection ;
@@ -35,17 +36,19 @@ public static partial class RequestDelegateFactory
35
36
private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteResultWriteResponse ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
36
37
private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof ( RequestDelegateFactory ) . GetMethod ( nameof ( ExecuteWriteStringResponseAsync ) , BindingFlags . NonPublic | BindingFlags . Static ) ! ;
37
38
private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = GetMethodInfo < Func < HttpResponse , object , Task > > ( ( response , value ) => HttpResponseJsonExtensions . WriteAsJsonAsync ( response , value , default ) ) ;
38
- private static readonly MethodInfo LogParameterBindingFailureMethod = GetMethodInfo < Action < HttpContext , string , string , string > > ( ( httpContext , parameterType , parameterName , sourceValue ) =>
39
- Log . ParameterBindingFailed ( httpContext , parameterType , parameterName , sourceValue ) ) ;
40
39
40
+ private static readonly MethodInfo LogParameterBindingFailedMethod = GetMethodInfo < Action < HttpContext , string , string , string > > ( ( httpContext , parameterType , parameterName , sourceValue ) =>
41
+ Log . ParameterBindingFailed ( httpContext , parameterType , parameterName , sourceValue ) ) ;
41
42
private static readonly MethodInfo LogRequiredParameterNotProvidedMethod = GetMethodInfo < Action < HttpContext , string , string > > ( ( httpContext , parameterType , parameterName ) =>
42
43
Log . RequiredParameterNotProvided ( httpContext , parameterType , parameterName ) ) ;
44
+ private static readonly MethodInfo LogParameterBindingFromHttpContextFailedMethod = GetMethodInfo < Action < HttpContext , string , string > > ( ( httpContext , parameterType , parameterName ) =>
45
+ Log . ParameterBindingFromHttpContextFailed ( httpContext , parameterType , parameterName ) ) ;
43
46
44
47
private static readonly ParameterExpression TargetExpr = Expression . Parameter ( typeof ( object ) , "target" ) ;
45
- private static readonly ParameterExpression HttpContextExpr = Expression . Parameter ( typeof ( HttpContext ) , "httpContext" ) ;
46
48
private static readonly ParameterExpression BodyValueExpr = Expression . Parameter ( typeof ( object ) , "bodyValue" ) ;
47
49
private static readonly ParameterExpression WasParamCheckFailureExpr = Expression . Variable ( typeof ( bool ) , "wasParamCheckFailure" ) ;
48
50
51
+ private static ParameterExpression HttpContextExpr => TryParseMethodCache . HttpContextExpr ;
49
52
private static readonly MemberExpression RequestServicesExpr = Expression . Property ( HttpContextExpr , typeof ( HttpContext ) . GetProperty ( nameof ( HttpContext . RequestServices ) ) ! ) ;
50
53
private static readonly MemberExpression HttpRequestExpr = Expression . Property ( HttpContextExpr , typeof ( HttpContext ) . GetProperty ( nameof ( HttpContext . Request ) ) ! ) ;
51
54
private static readonly MemberExpression HttpResponseExpr = Expression . Property ( HttpContextExpr , typeof ( HttpContext ) . GetProperty ( nameof ( HttpContext . Response ) ) ! ) ;
@@ -57,8 +60,9 @@ public static partial class RequestDelegateFactory
57
60
private static readonly MemberExpression StatusCodeExpr = Expression . Property ( HttpResponseExpr , typeof ( HttpResponse ) . GetProperty ( nameof ( HttpResponse . StatusCode ) ) ! ) ;
58
61
private static readonly MemberExpression CompletedTaskExpr = Expression . Property ( null , ( PropertyInfo ) GetMemberInfo < Func < Task > > ( ( ) => Task . CompletedTask ) ) ;
59
62
60
- private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression . NotEqual ( TryParseMethodCache . TempSourceStringExpr , Expression . Constant ( null ) ) ;
61
- private static readonly BinaryExpression TempSourceStringNullExpr = Expression . Equal ( TryParseMethodCache . TempSourceStringExpr , Expression . Constant ( null ) ) ;
63
+ private static ParameterExpression TempSourceStringExpr => TryParseMethodCache . TempSourceStringExpr ;
64
+ private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression . NotEqual ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
65
+ private static readonly BinaryExpression TempSourceStringNullExpr = Expression . Equal ( TempSourceStringExpr , Expression . Constant ( null ) ) ;
62
66
63
67
/// <summary>
64
68
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="action"/>.
@@ -170,7 +174,7 @@ public static RequestDelegate Create(MethodInfo methodInfo, Func<HttpContext, ob
170
174
171
175
if ( factoryContext . UsingTempSourceString )
172
176
{
173
- responseWritingMethodCall = Expression . Block ( new [ ] { TryParseMethodCache . TempSourceStringExpr } , responseWritingMethodCall ) ;
177
+ responseWritingMethodCall = Expression . Block ( new [ ] { TempSourceStringExpr } , responseWritingMethodCall ) ;
174
178
}
175
179
176
180
return HandleRequestBodyAndCompileRequestDelegate ( responseWritingMethodCall , factoryContext ) ;
@@ -259,7 +263,11 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
259
263
{
260
264
return RequestAbortedExpr ;
261
265
}
262
- else if ( parameter . ParameterType == typeof ( string ) || TryParseMethodCache . HasTryParseMethod ( parameter ) )
266
+ else if ( TryParseMethodCache . HasTryParseHttpContextMethod ( parameter ) )
267
+ {
268
+ return BindParameterFromTryParseHttpContext ( parameter , factoryContext ) ;
269
+ }
270
+ else if ( parameter . ParameterType == typeof ( string ) || TryParseMethodCache . HasTryParseStringMethod ( parameter ) )
263
271
{
264
272
// 1. We bind from route values only, if route parameters are non-null and the parameter name is in that set.
265
273
// 2. We bind from query only, if route parameters are non-null and the parameter name is NOT in that set.
@@ -620,7 +628,7 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
620
628
var isNotNullable = underlyingNullableType is null ;
621
629
622
630
var nonNullableParameterType = underlyingNullableType ?? parameter . ParameterType ;
623
- var tryParseMethodCall = TryParseMethodCache . FindTryParseMethod ( nonNullableParameterType ) ;
631
+ var tryParseMethodCall = TryParseMethodCache . FindTryParseStringMethod ( nonNullableParameterType ) ;
624
632
625
633
if ( tryParseMethodCall is null )
626
634
{
@@ -664,16 +672,16 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
664
672
// param2_local = 42;
665
673
// }
666
674
667
- // If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot the parameter directly.
675
+ // If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot use the parameter directly.
668
676
var parsedValue = isNotNullable ? argument : Expression . Variable ( nonNullableParameterType , "parsedValue" ) ;
669
677
670
678
var parameterTypeNameConstant = Expression . Constant ( parameter . ParameterType . Name ) ;
671
679
var parameterNameConstant = Expression . Constant ( parameter . Name ) ;
672
680
673
681
var failBlock = Expression . Block (
674
682
Expression . Assign ( WasParamCheckFailureExpr , Expression . Constant ( true ) ) ,
675
- Expression . Call ( LogParameterBindingFailureMethod ,
676
- HttpContextExpr , parameterTypeNameConstant , parameterNameConstant , TryParseMethodCache . TempSourceStringExpr ) ) ;
683
+ Expression . Call ( LogParameterBindingFailedMethod ,
684
+ HttpContextExpr , parameterTypeNameConstant , parameterNameConstant , TempSourceStringExpr ) ) ;
677
685
678
686
var tryParseCall = tryParseMethodCall ( parsedValue ) ;
679
687
@@ -712,14 +720,14 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
712
720
var fullParamCheckBlock = ! isOptional
713
721
? Expression . Block (
714
722
// tempSourceString = httpContext.RequestValue["id"];
715
- Expression . Assign ( TryParseMethodCache . TempSourceStringExpr , valueExpression ) ,
723
+ Expression . Assign ( TempSourceStringExpr , valueExpression ) ,
716
724
// if (tempSourceString == null) { ... } only produced when parameter is required
717
725
checkRequiredParaseableParameterBlock ,
718
726
// if (tempSourceString != null) { ... }
719
727
ifNotNullTryParse )
720
728
: Expression . Block (
721
729
// tempSourceString = httpContext.RequestValue["id"];
722
- Expression . Assign ( TryParseMethodCache . TempSourceStringExpr , valueExpression ) ,
730
+ Expression . Assign ( TempSourceStringExpr , valueExpression ) ,
723
731
// if (tempSourceString != null) { ... }
724
732
ifNotNullTryParse ) ;
725
733
@@ -739,6 +747,42 @@ private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo
739
747
return BindParameterFromValue ( parameter , Expression . Coalesce ( routeValue , queryValue ) , factoryContext ) ;
740
748
}
741
749
750
+ private static Expression BindParameterFromTryParseHttpContext ( ParameterInfo parameter , FactoryContext factoryContext )
751
+ {
752
+ // bool wasParamCheckFailure = false;
753
+ //
754
+ // // Assume "Foo param1" is the first parameter and "public static bool TryParse(HttpContext context, out Foo foo)" exists.
755
+ // Foo param1_local;
756
+ //
757
+ // if (!Foo.TryParse(httpContext, out param1_local))
758
+ // {
759
+ // wasParamCheckFailure = true;
760
+ // Log.ParameterBindingFromHttpContextFailed(httpContext, "Foo", "foo")
761
+ // }
762
+
763
+ var argument = Expression . Variable ( parameter . ParameterType , $ "{ parameter . Name } _local") ;
764
+ var tryParseMethodCall = TryParseMethodCache . FindTryParseHttpContextMethod ( parameter . ParameterType ) ;
765
+
766
+ // There's no way to opt-in to using a TryParse method on HttpContext other than defining the method, so it's guaranteed to exist here.
767
+ Debug . Assert ( tryParseMethodCall is not null ) ;
768
+
769
+ var parameterTypeNameConstant = Expression . Constant ( parameter . ParameterType . Name ) ;
770
+ var parameterNameConstant = Expression . Constant ( parameter . Name ) ;
771
+
772
+ var failBlock = Expression . Block (
773
+ Expression . Assign ( WasParamCheckFailureExpr , Expression . Constant ( true ) ) ,
774
+ Expression . Call ( LogParameterBindingFromHttpContextFailedMethod ,
775
+ HttpContextExpr , parameterTypeNameConstant , parameterNameConstant ) ) ;
776
+
777
+ var tryParseCall = tryParseMethodCall ( argument ) ;
778
+ var fullParamCheckBlock = Expression . IfThen ( Expression . Not ( tryParseCall ) , failBlock ) ;
779
+
780
+ factoryContext . ExtraLocals . Add ( argument ) ;
781
+ factoryContext . ParamCheckExpressions . Add ( fullParamCheckBlock ) ;
782
+
783
+ return argument ;
784
+ }
785
+
742
786
private static Expression BindParameterFromBody ( ParameterInfo parameter , bool allowEmpty , FactoryContext factoryContext )
743
787
{
744
788
if ( factoryContext . JsonRequestBodyType is not null )
@@ -1025,19 +1069,27 @@ public static void RequestBodyInvalidDataException(HttpContext httpContext, Inva
1025
1069
public static void ParameterBindingFailed ( HttpContext httpContext , string parameterTypeName , string parameterName , string sourceValue )
1026
1070
=> ParameterBindingFailed ( GetLogger ( httpContext ) , parameterTypeName , parameterName , sourceValue ) ;
1027
1071
1028
- public static void RequiredParameterNotProvided ( HttpContext httpContext , string parameterTypeName , string parameterName )
1029
- => RequiredParameterNotProvided ( GetLogger ( httpContext ) , parameterTypeName , parameterName ) ;
1030
-
1031
1072
[ LoggerMessage ( 3 , LogLevel . Debug ,
1032
1073
@"Failed to bind parameter ""{ParameterType} {ParameterName}"" from ""{SourceValue}""." ,
1033
1074
EventName = "ParamaterBindingFailed" ) ]
1034
1075
private static partial void ParameterBindingFailed ( ILogger logger , string parameterType , string parameterName , string sourceValue ) ;
1035
1076
1077
+ public static void RequiredParameterNotProvided ( HttpContext httpContext , string parameterTypeName , string parameterName )
1078
+ => RequiredParameterNotProvided ( GetLogger ( httpContext ) , parameterTypeName , parameterName ) ;
1079
+
1036
1080
[ LoggerMessage ( 4 , LogLevel . Debug ,
1037
1081
@"Required parameter ""{ParameterType} {ParameterName}"" was not provided." ,
1038
1082
EventName = "RequiredParameterNotProvided" ) ]
1039
1083
private static partial void RequiredParameterNotProvided ( ILogger logger , string parameterType , string parameterName ) ;
1040
1084
1085
+ public static void ParameterBindingFromHttpContextFailed ( HttpContext httpContext , string parameterTypeName , string parameterName )
1086
+ => ParameterBindingFromHttpContextFailed ( GetLogger ( httpContext ) , parameterTypeName , parameterName ) ;
1087
+
1088
+ [ LoggerMessage ( 5 , LogLevel . Debug ,
1089
+ @"Failed to bind parameter ""{ParameterType} {ParameterName}"" from HttpContext." ,
1090
+ EventName = "ParameterBindingFromHttpContextFailed" ) ]
1091
+ private static partial void ParameterBindingFromHttpContextFailed ( ILogger logger , string parameterType , string parameterName ) ;
1092
+
1041
1093
private static ILogger GetLogger ( HttpContext httpContext )
1042
1094
{
1043
1095
var loggerFactory = httpContext . RequestServices . GetRequiredService < ILoggerFactory > ( ) ;
0 commit comments