@@ -612,6 +612,54 @@ public static async ValueTask<MyAwaitedBindAsyncStruct> BindAsync(HttpContext co
612612 }
613613 }
614614
615+ private record struct MyBothBindAsyncStruct ( Uri Uri )
616+ {
617+ public static ValueTask < MyBothBindAsyncStruct > BindAsync ( HttpContext context , ParameterInfo parameter )
618+ {
619+ Assert . True ( parameter . ParameterType == typeof ( MyBothBindAsyncStruct ) || parameter . ParameterType == typeof ( MyBothBindAsyncStruct ? ) ) ;
620+ Assert . Equal ( "myBothBindAsyncStruct" , parameter . Name ) ;
621+
622+ if ( ! Uri . TryCreate ( context . Request . Headers . Referer , UriKind . Absolute , out var uri ) )
623+ {
624+ throw new BadHttpRequestException ( "The request is missing the required Referer header." ) ;
625+ }
626+
627+ return new ( result : new ( uri ) ) ;
628+ }
629+
630+ // BindAsync with ParameterInfo is preferred
631+ public static ValueTask < MyBothBindAsyncStruct > BindAsync ( HttpContext context )
632+ {
633+ throw new NotImplementedException ( ) ;
634+ }
635+ }
636+
637+ private record struct MySimpleBindAsyncStruct ( Uri Uri )
638+ {
639+ public static ValueTask < MySimpleBindAsyncStruct > BindAsync ( HttpContext context )
640+ {
641+ if ( ! Uri . TryCreate ( context . Request . Headers . Referer , UriKind . Absolute , out var uri ) )
642+ {
643+ throw new BadHttpRequestException ( "The request is missing the required Referer header." ) ;
644+ }
645+
646+ return new ( result : new ( uri ) ) ;
647+ }
648+ }
649+
650+ private record MySimpleBindAsyncRecord ( Uri Uri )
651+ {
652+ public static ValueTask < MySimpleBindAsyncRecord ? > BindAsync ( HttpContext context )
653+ {
654+ if ( ! Uri . TryCreate ( context . Request . Headers . Referer , UriKind . Absolute , out var uri ) )
655+ {
656+ return new ( result : null ) ;
657+ }
658+
659+ return new ( result : new ( uri ) ) ;
660+ }
661+ }
662+
615663 [ Theory ]
616664 [ MemberData ( nameof ( TryParsableParameters ) ) ]
617665 public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValue ( Delegate action , string ? routeValue , object ? expectedParameterValue )
@@ -724,6 +772,24 @@ public async Task RequestDelegateUsesBindAsyncOverTryParseGivenNullableStruct()
724772 Assert . Equal ( new MyBindAsyncStruct ( new Uri ( "https://example.org" ) ) , httpContext . Items [ "myBindAsyncStruct" ] ) ;
725773 }
726774
775+ [ Fact ]
776+ public async Task RequestDelegateUsesParameterInfoBindAsyncOverOtherBindAsync ( )
777+ {
778+ var httpContext = CreateHttpContext ( ) ;
779+
780+ httpContext . Request . Headers . Referer = "https://example.org" ;
781+
782+ var resultFactory = RequestDelegateFactory . Create ( ( HttpContext httpContext , MyBothBindAsyncStruct ? myBothBindAsyncStruct ) =>
783+ {
784+ httpContext . Items [ "myBothBindAsyncStruct" ] = myBothBindAsyncStruct ;
785+ } ) ;
786+
787+ var requestDelegate = resultFactory . RequestDelegate ;
788+ await requestDelegate ( httpContext ) ;
789+
790+ Assert . Equal ( new MyBothBindAsyncStruct ( new Uri ( "https://example.org" ) ) , httpContext . Items [ "myBothBindAsyncStruct" ] ) ;
791+ }
792+
727793 [ Fact ]
728794 public async Task RequestDelegateUsesTryParseOverBindAsyncGivenExplicitAttribute ( )
729795 {
@@ -873,7 +939,7 @@ void TestAction([FromRoute] int tryParsable, [FromRoute] int tryParsable2)
873939 [ Fact ]
874940 public async Task RequestDelegateLogsBindAsyncFailuresAndSets400Response ( )
875941 {
876- // Not supplying any headers will cause the HttpContext TryParse overload to fail .
942+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null .
877943 var httpContext = CreateHttpContext ( ) ;
878944 var invoked = false ;
879945
@@ -905,7 +971,7 @@ public async Task RequestDelegateLogsBindAsyncFailuresAndSets400Response()
905971 [ Fact ]
906972 public async Task RequestDelegateLogsBindAsyncFailuresAndThrowsIfThrowOnBadRequest ( )
907973 {
908- // Not supplying any headers will cause the HttpContext TryParse overload to fail .
974+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null .
909975 var httpContext = CreateHttpContext ( ) ;
910976 var invoked = false ;
911977
@@ -931,10 +997,72 @@ public async Task RequestDelegateLogsBindAsyncFailuresAndThrowsIfThrowOnBadReque
931997 Assert . Equal ( 400 , badHttpRequestException . StatusCode ) ;
932998 }
933999
1000+ [ Fact ]
1001+ public async Task RequestDelegateLogsSingleArgBindAsyncFailuresAndSets400Response ( )
1002+ {
1003+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
1004+ var httpContext = CreateHttpContext ( ) ;
1005+ var invoked = false ;
1006+
1007+ var factoryResult = RequestDelegateFactory . Create ( ( MySimpleBindAsyncRecord mySimpleBindAsyncRecord1 ,
1008+ MySimpleBindAsyncRecord mySimpleBindAsyncRecord2 ) =>
1009+ {
1010+ invoked = true ;
1011+ } ) ;
1012+
1013+ var requestDelegate = factoryResult . RequestDelegate ;
1014+ await requestDelegate ( httpContext ) ;
1015+
1016+ Assert . False ( invoked ) ;
1017+ Assert . False ( httpContext . RequestAborted . IsCancellationRequested ) ;
1018+ Assert . Equal ( 400 , httpContext . Response . StatusCode ) ;
1019+
1020+ var logs = TestSink . Writes . ToArray ( ) ;
1021+
1022+ Assert . Equal ( 2 , logs . Length ) ;
1023+
1024+ Assert . Equal ( new EventId ( 4 , "RequiredParameterNotProvided" ) , logs [ 0 ] . EventId ) ;
1025+ Assert . Equal ( LogLevel . Debug , logs [ 0 ] . LogLevel ) ;
1026+ Assert . Equal ( @"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord1"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext)." , logs [ 0 ] . Message ) ;
1027+
1028+ Assert . Equal ( new EventId ( 4 , "RequiredParameterNotProvided" ) , logs [ 1 ] . EventId ) ;
1029+ Assert . Equal ( LogLevel . Debug , logs [ 1 ] . LogLevel ) ;
1030+ Assert . Equal ( @"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord2"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext)." , logs [ 1 ] . Message ) ;
1031+ }
1032+
1033+ [ Fact ]
1034+ public async Task RequestDelegateLogsSingleArgBindAsyncFailuresAndThrowsIfThrowOnBadRequest ( )
1035+ {
1036+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
1037+ var httpContext = CreateHttpContext ( ) ;
1038+ var invoked = false ;
1039+
1040+ var factoryResult = RequestDelegateFactory . Create ( ( MySimpleBindAsyncRecord mySimpleBindAsyncRecord1 ,
1041+ MySimpleBindAsyncRecord mySimpleBindAsyncRecord2 ) =>
1042+ {
1043+ invoked = true ;
1044+ } , new ( ) { ThrowOnBadRequest = true } ) ;
1045+
1046+ var requestDelegate = factoryResult . RequestDelegate ;
1047+ var badHttpRequestException = await Assert . ThrowsAsync < BadHttpRequestException > ( ( ) => requestDelegate ( httpContext ) ) ;
1048+
1049+ Assert . False ( invoked ) ;
1050+
1051+ // The httpContext should be untouched.
1052+ Assert . False ( httpContext . RequestAborted . IsCancellationRequested ) ;
1053+ Assert . Equal ( 200 , httpContext . Response . StatusCode ) ;
1054+ Assert . False ( httpContext . Response . HasStarted ) ;
1055+
1056+ // We don't log bad requests when we throw.
1057+ Assert . Empty ( TestSink . Writes ) ;
1058+
1059+ Assert . Equal ( @"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord1"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext)." , badHttpRequestException . Message ) ;
1060+ Assert . Equal ( 400 , badHttpRequestException . StatusCode ) ;
1061+ }
1062+
9341063 [ Fact ]
9351064 public async Task BindAsyncExceptionsAreUncaught ( )
9361065 {
937- // Not supplying any headers will cause the HttpContext BindAsync overload to fail.
9381066 var httpContext = CreateHttpContext ( ) ;
9391067
9401068 var factoryResult = RequestDelegateFactory . Create ( ( MyBindAsyncTypeThatThrows arg1 ) => { } ) ;
@@ -2239,6 +2367,10 @@ void nullableReferenceType(HttpContext context, MyBindAsyncRecord? myBindAsyncRe
22392367 {
22402368 context . Items [ "uri" ] = myBindAsyncRecord ? . Uri ;
22412369 }
2370+ void requiredReferenceTypeSimple ( HttpContext context , MySimpleBindAsyncRecord mySimpleBindAsyncRecord )
2371+ {
2372+ context . Items [ "uri" ] = mySimpleBindAsyncRecord . Uri ;
2373+ }
22422374
22432375
22442376 void requiredValueType ( HttpContext context , MyNullableBindAsyncStruct myNullableBindAsyncStruct )
@@ -2253,11 +2385,16 @@ void nullableValueType(HttpContext context, MyNullableBindAsyncStruct? myNullabl
22532385 {
22542386 context . Items [ "uri" ] = myNullableBindAsyncStruct ? . Uri ;
22552387 }
2388+ void requiredValueTypeSimple ( HttpContext context , MySimpleBindAsyncStruct mySimpleBindAsyncStruct )
2389+ {
2390+ context . Items [ "uri" ] = mySimpleBindAsyncStruct . Uri ;
2391+ }
22562392
22572393 return new object ? [ ] [ ]
22582394 {
22592395 new object ? [ ] { ( Action < HttpContext , MyBindAsyncRecord > ) requiredReferenceType , false , true , false } ,
22602396 new object ? [ ] { ( Action < HttpContext , MyBindAsyncRecord > ) requiredReferenceType , true , false , false , } ,
2397+ new object ? [ ] { ( Action < HttpContext , MySimpleBindAsyncRecord > ) requiredReferenceTypeSimple , true , false , false } ,
22612398
22622399 new object ? [ ] { ( Action < HttpContext , MyBindAsyncRecord ? > ) defaultReferenceType , false , false , false , } ,
22632400 new object ? [ ] { ( Action < HttpContext , MyBindAsyncRecord ? > ) defaultReferenceType , true , false , false } ,
@@ -2267,6 +2404,7 @@ void nullableValueType(HttpContext context, MyNullableBindAsyncStruct? myNullabl
22672404
22682405 new object ? [ ] { ( Action < HttpContext , MyNullableBindAsyncStruct > ) requiredValueType , false , true , true } ,
22692406 new object ? [ ] { ( Action < HttpContext , MyNullableBindAsyncStruct > ) requiredValueType , true , false , true } ,
2407+ new object ? [ ] { ( Action < HttpContext , MySimpleBindAsyncStruct > ) requiredValueTypeSimple , true , false , true } ,
22702408
22712409 new object ? [ ] { ( Action < HttpContext , MyNullableBindAsyncStruct ? > ) defaultValueType , false , false , true } ,
22722410 new object ? [ ] { ( Action < HttpContext , MyNullableBindAsyncStruct ? > ) defaultValueType , true , false , true } ,
0 commit comments