Pārlūkot izejas kodu

Add BindAsync support to RequestDelegateGenerator (#47033)

* Add BindAsync support to RequestDelegateGenerator

# Conflicts:
#	src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs
#	src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs
#	src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt
#	src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt
#	src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt
#	src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt
#	src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt
#	src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs

* Support explicit interface implementations

* Add MapAction_BindAsync_Snapshot

* Route parameters do not support BindAsync

* Move test types to shared types

---------

Co-authored-by: Safia Abdalla <[email protected]>
Stephen Halter 3 gadi atpakaļ
vecāks
revīzija
46a3d2ba55
36 mainītis faili ar 2767 papildinājumiem un 475 dzēšanām
  1. 2 2
      src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs
  2. 1 4
      src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj
  3. 3 19
      src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs
  4. 1 1
      src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/RouteHandlerAnalyzer.cs
  5. 19 11
      src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs
  6. 1 4
      src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj
  7. 4 0
      src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs
  8. 6 0
      src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
  9. 12 10
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs
  10. 50 6
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs
  11. 21 6
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs
  12. 49 35
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs
  13. 8 4
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs
  14. 1 1
      src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
  15. 0 92
      src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
  16. 1726 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt
  17. 25 19
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt
  18. 12 6
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt
  19. 32 22
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt
  20. 8 2
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt
  21. 12 6
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt
  22. 8 2
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt
  23. 20 14
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt
  24. 20 14
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt
  25. 10 4
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt
  26. 10 4
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt
  27. 14 8
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt
  28. 12 6
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt
  29. 17 104
      src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs
  30. 221 10
      src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs
  31. 361 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs
  32. 0 14
      src/Shared/RoslynUtils/Bindability.cs
  33. 0 5
      src/Shared/RoslynUtils/Parsability.cs
  34. 66 21
      src/Shared/RoslynUtils/ParsabilityHelper.cs
  35. 0 14
      src/Shared/RoslynUtils/ParsabilityMethod.cs
  36. 15 4
      src/Shared/RoslynUtils/SymbolExtensions.cs

+ 2 - 2
src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs

@@ -161,10 +161,10 @@ internal static class DiagnosticDescriptors
         isEnabledByDefault: true,
         helpLinkUri: "https://aka.ms/aspnet/analyzers");
 
-    internal static readonly DiagnosticDescriptor RouteParameterComplexTypeIsNotParsableOrBindable = new(
+    internal static readonly DiagnosticDescriptor RouteParameterComplexTypeIsNotParsable = new(
         "ASP0020",
         "Complex types referenced by route parameters must be parsable",
-        "Parameter '{0}' of type {1} should define a bool TryParse(string, IFormatProvider, out {1}) method, or implement IParsable<{1}>, or define a ValueTask<{1}> BindAsync(HttpContext), or implement IBindableFromHttpContext<{1}>.",
+        "Parameter '{0}' of type {1} should define a bool TryParse(string, IFormatProvider, out {1}) method, or implement IParsable<{1}>.",
         "Usage",
         DiagnosticSeverity.Error,
         isEnabledByDefault: true,

+ 1 - 4
src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj

@@ -27,10 +27,7 @@
     <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypes.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypeData.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\SymbolExtensions.cs" LinkBase="Shared" />
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\Bindability.cs" LinkBase="Shared"/>
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\Parsability.cs" LinkBase="Shared"/>
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityMethod.cs" LinkBase="Shared"/>
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityHelper.cs" LinkBase="Shared"/>
+    <Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityHelper.cs" LinkBase="Shared" />
   </ItemGroup>
 
 </Project>

+ 3 - 19
src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs

@@ -68,14 +68,11 @@ public partial class RouteHandlerAnalyzer : DiagnosticAnalyzer
             if (IsRouteParameter(routeUsage, handlerDelegateParameter))
             {
                 var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes);
-                var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes);
 
-                if (!(parsability == Parsability.Parsable || bindability == Bindability.Bindable))
+                if (parsability != Parsability.Parsable)
                 {
-                    var descriptor = SelectDescriptor(parsability, bindability);
-
                     context.ReportDiagnostic(Diagnostic.Create(
-                        descriptor,
+                        DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable,
                         location,
                         handlerDelegateParameter.Name,
                         parameterTypeSymbol.Name
@@ -103,10 +100,8 @@ public partial class RouteHandlerAnalyzer : DiagnosticAnalyzer
             var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes);
             if (parameter.HasAttributeImplementingInterface(fromMetadataInterfaceTypeSymbol) && parsability != Parsability.Parsable)
             {
-                var descriptor = SelectDescriptor(parsability, Bindability.NotBindable);
-
                 context.ReportDiagnostic(Diagnostic.Create(
-                    descriptor,
+                    DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable,
                     location,
                     parameter.Name,
                     parameterTypeSymbol.Name
@@ -140,16 +135,5 @@ public partial class RouteHandlerAnalyzer : DiagnosticAnalyzer
 
             return parameterTypeSymbol;
         }
-
-        static DiagnosticDescriptor SelectDescriptor(Parsability parsability, Bindability bindability)
-        {
-            // This abomination is used to take the parsability and bindability and together figure
-            // out what the most optimal diagnostic message is to give to our plucky user.
-            return (parsability, bindability) switch
-            {
-                { parsability: Parsability.NotParsable, bindability: Bindability.InvalidReturnType } => DiagnosticDescriptors.BindAsyncSignatureMustReturnValueTaskOfT,
-                _ => DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable
-            };
-        }
     }
 }

+ 1 - 1
src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/RouteHandlerAnalyzer.cs

@@ -25,7 +25,7 @@ public partial class RouteHandlerAnalyzer : DiagnosticAnalyzer
         DiagnosticDescriptors.DoNotReturnActionResultsFromRouteHandlers,
         DiagnosticDescriptors.DetectMisplacedLambdaAttribute,
         DiagnosticDescriptors.DetectMismatchedParameterOptionality,
-        DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable,
+        DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable,
         DiagnosticDescriptors.BindAsyncSignatureMustReturnValueTaskOfT,
         DiagnosticDescriptors.AmbiguousRouteHandlerRoute,
         DiagnosticDescriptors.AtMostOneFromBodyAttribute

+ 19 - 11
src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs

@@ -195,7 +195,7 @@ public class Customer
 }
 """;
 
-        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable)
+        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable)
             .WithArguments("customer", "Customer")
             .WithLocation(0);
 
@@ -218,7 +218,7 @@ public class Customer
 }
 """;
 
-        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable)
+        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable)
             .WithArguments("customer", "Customer")
             .WithLocation(0);
 
@@ -289,7 +289,7 @@ public class Customer : IParsable<Customer>
     }
 
     [Fact]
-    public async Task Route_Parameter_withBindAsyncMethodThatReturnsTask_of_T_Fails()
+    public async Task Route_Parameter_withBindAsyncMethod_Fails()
     {
         // Arrange
         var source = $$"""
@@ -301,14 +301,14 @@ webApp.MapGet("/customers/{customer}", ({|#0:Customer customer|}) => {});
 
 public class Customer
 {
-    public async static Task<Customer> BindAsync(HttpContext context)
+    public async static ValueTask<Customer> BindAsync(HttpContext context)
     {
         return new Customer();
     }
 }
 """;
 
-        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.BindAsyncSignatureMustReturnValueTaskOfT)
+        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable)
             .WithArguments("customer", "Customer")
             .WithLocation(0);
 
@@ -422,7 +422,7 @@ public class Customer
     }
 
     [Fact]
-    public async Task Route_Parameter_withHttpContextBindableComplexType_viaImplicitIBindableFromHttp_Works()
+    public async Task Route_Parameter_withHttpContextBindableComplexType_viaImplicitIBindableFromHttp_Fails()
     {
         // Arrange
         var source = $$"""
@@ -433,7 +433,7 @@ using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
 
 var webApp = WebApplication.Create();
-webApp.MapGet("/customers/{customer}", (Customer customer) => {});
+webApp.MapGet("/customers/{customer}", ({|#0:Customer customer|}) => {});
 
 public class Customer : IBindableFromHttpContext<Customer>
 {
@@ -444,12 +444,16 @@ public class Customer : IBindableFromHttpContext<Customer>
 }
 """;
 
+        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable)
+            .WithArguments("customer", "Customer")
+            .WithLocation(0);
+
         // Act
-        await VerifyCS.VerifyAnalyzerAsync(source);
+        await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic);
     }
 
     [Fact]
-    public async Task Route_Parameter_withHttpContextBindableComplexType_viaExplicitIBindableFromHttp_Works()
+    public async Task Route_Parameter_withHttpContextBindableComplexType_viaExplicitIBindableFromHttp_Fails()
     {
         // Arrange
         var source = $$"""
@@ -460,7 +464,7 @@ using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
 
 var webApp = WebApplication.Create();
-webApp.MapGet("/customers/{customer}", (Customer customer) => {});
+webApp.MapGet("/customers/{customer}", ({|#0:Customer customer|}) => {});
 
 public class Customer : IBindableFromHttpContext<Customer>
 {
@@ -471,8 +475,12 @@ public class Customer : IBindableFromHttpContext<Customer>
 }
 """;
 
+        var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable)
+            .WithArguments("customer", "Customer")
+            .WithLocation(0);
+
         // Act
-        await VerifyCS.VerifyAnalyzerAsync(source);
+        await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic);
     }
 
     [Fact]

+ 1 - 4
src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj

@@ -24,10 +24,7 @@
     <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypeData.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypes.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\SymbolExtensions.cs" LinkBase="Shared" />
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\Bindability.cs" LinkBase="Shared"/>
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\Parsability.cs" LinkBase="Shared"/>
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityMethod.cs" LinkBase="Shared"/>
-    <Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityHelper.cs" LinkBase="Shared"/>
+    <Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityHelper.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\CodeWriter.cs" LinkBase="Shared" />
   </ItemGroup>
 

+ 4 - 0
src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs

@@ -81,6 +81,10 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
             codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;");
             endpoint.EmitRouteOrQueryResolver(codeWriter);
             endpoint.EmitJsonPreparation(codeWriter);
+            if (endpoint.NeedsParameterArray)
+            {
+                codeWriter.WriteLine("var parameters = del.Method.GetParameters();");
+            }
             codeWriter.WriteLineNoTabs(string.Empty);
             codeWriter.WriteLine("if (options?.EndpointBuilder?.FilterFactories.Count > 0)");
             codeWriter.StartBlock();

+ 6 - 0
src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs

@@ -158,6 +158,12 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }
 """;

+ 12 - 10
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs

@@ -1,11 +1,9 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
-using System.Linq;
 using System.Globalization;
 using System.IO;
-using System.Text;
+using System.Linq;
 
 namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
 internal static class EndpointEmitter
@@ -17,24 +15,28 @@ internal static class EndpointEmitter
 
         foreach (var parameter in endpoint.Parameters)
         {
-            switch (parameter)
+            switch (parameter.Source)
             {
-                case { Source: EndpointParameterSource.SpecialType }:
+                case EndpointParameterSource.SpecialType:
                     parameter.EmitSpecialParameterPreparation(parameterPreparationBuilder);
                     break;
-                case { Source: EndpointParameterSource.Query or EndpointParameterSource.Header }:
+                case EndpointParameterSource.Query:
+                case EndpointParameterSource.Header:
                     parameter.EmitQueryOrHeaderParameterPreparation(parameterPreparationBuilder);
                     break;
-                case { Source: EndpointParameterSource.Route }:
+                case EndpointParameterSource.Route:
                     parameter.EmitRouteParameterPreparation(parameterPreparationBuilder);
                     break;
-                case { Source: EndpointParameterSource.RouteOrQuery }:
+                case EndpointParameterSource.RouteOrQuery:
                     parameter.EmitRouteOrQueryParameterPreparation(parameterPreparationBuilder);
                     break;
-                case { Source: EndpointParameterSource.JsonBody }:
+                case EndpointParameterSource.BindAsync:
+                    parameter.EmitBindAsyncPreparation(parameterPreparationBuilder);
+                    break;
+                case EndpointParameterSource.JsonBody:
                     parameter.EmitJsonBodyParameterPreparationString(parameterPreparationBuilder);
                     break;
-                case { Source: EndpointParameterSource.Service }:
+                case EndpointParameterSource.Service:
                     parameter.EmitServiceParameterPreparation(parameterPreparationBuilder);
                     break;
             }

+ 50 - 6
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs

@@ -2,10 +2,12 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
-using System.Text;
+using Microsoft.AspNetCore.Analyzers.Infrastructure;
+using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
 using Microsoft.CodeAnalysis;
 
 namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
+
 internal static class EndpointParameterEmitter
 {
     internal static void EmitSpecialParameterPreparation(this EndpointParameter endpointParameter, CodeWriter codeWriter)
@@ -26,7 +28,7 @@ internal static class EndpointParameterEmitter
         // compiler errors around null handling.
         if (endpointParameter.IsOptional)
         {
-            codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}.Count > 0 ? {endpointParameter.EmitAssigningCodeResult()}.ToString() : null;");
+            codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}.Count > 0 ? (string?){endpointParameter.EmitAssigningCodeResult()} : null;");
         }
         else
         {
@@ -34,7 +36,7 @@ internal static class EndpointParameterEmitter
             codeWriter.StartBlock();
             codeWriter.WriteLine("wasParamCheckFailure = true;");
             codeWriter.EndBlock();
-            codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}.ToString();");
+            codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = (string?){endpointParameter.EmitAssigningCodeResult()};");
         }
 
         endpointParameter.EmitParsingBlock(codeWriter);
@@ -64,7 +66,7 @@ internal static class EndpointParameterEmitter
         codeWriter.WriteLine($$"""throw new InvalidOperationException($"'{{endpointParameter.Name}}' is not a route parameter.");""");
         codeWriter.EndBlock();
 
-        var assigningCode = $"httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]?.ToString()";
+        var assigningCode = $"(string?)httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]";
         codeWriter.WriteLine($"var {endpointParameter.EmitAssigningCodeResult()} = {assigningCode};");
 
         if (!endpointParameter.IsOptional)
@@ -75,7 +77,7 @@ internal static class EndpointParameterEmitter
             codeWriter.EndBlock();
         }
 
-        codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}?.ToString();");
+        codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = (string?){endpointParameter.EmitAssigningCodeResult()};");
         endpointParameter.EmitParsingBlock(codeWriter);
     }
 
@@ -94,7 +96,8 @@ internal static class EndpointParameterEmitter
             codeWriter.EndBlock();
         }
 
-        codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()};");
+        codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = (string?){endpointParameter.EmitAssigningCodeResult()};");
+        endpointParameter.EmitParsingBlock(codeWriter);
     }
 
     internal static void EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter, CodeWriter codeWriter)
@@ -116,6 +119,47 @@ internal static class EndpointParameterEmitter
         codeWriter.EndBlock();
     }
 
+    internal static void EmitBindAsyncPreparation(this EndpointParameter endpointParameter, CodeWriter codeWriter)
+    {
+        var unwrappedType = endpointParameter.Type.UnwrapTypeSymbol();
+        var unwrappedTypeString = unwrappedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+        switch (endpointParameter.BindMethod)
+        {
+            case BindabilityMethod.IBindableFromHttpContext:
+                codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = await BindAsync<{unwrappedTypeString}>(httpContext, parameters[{endpointParameter.Ordinal}]);");
+                break;
+            case BindabilityMethod.BindAsyncWithParameter:
+                codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = await {unwrappedTypeString}.BindAsync(httpContext, parameters[{endpointParameter.Ordinal}]);");
+                break;
+            case BindabilityMethod.BindAsync:
+                codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = await {unwrappedTypeString}.BindAsync(httpContext);");
+                break;
+            default:
+                throw new Exception("Unreachable!");
+        }
+
+        // TODO: Generate more compact code if the type is a reference type and/or the BindAsync return nullability matches the handler parameter type.
+        if (endpointParameter.IsOptional)
+        {
+            codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = ({unwrappedTypeString}?){endpointParameter.EmitTempArgument()};");
+        }
+        else
+        {
+            codeWriter.WriteLine($"{unwrappedTypeString} {endpointParameter.EmitHandlerArgument()};");
+
+            codeWriter.WriteLine($"if ((object?){endpointParameter.EmitTempArgument()} == null)");
+            codeWriter.StartBlock();
+            codeWriter.WriteLine("wasParamCheckFailure = true;");
+            codeWriter.WriteLine($"{endpointParameter.EmitHandlerArgument()} = default!;");
+            codeWriter.EndBlock();
+            codeWriter.WriteLine("else");
+            codeWriter.StartBlock();
+            codeWriter.WriteLine($"{endpointParameter.EmitHandlerArgument()} = ({unwrappedTypeString}){endpointParameter.EmitTempArgument()};");
+            codeWriter.EndBlock();
+        }
+    }
+
     internal static void EmitServiceParameterPreparation(this EndpointParameter endpointParameter, CodeWriter codeWriter)
     {
         codeWriter.WriteLine(endpointParameter.EmitParameterDiagnosticComment());

+ 21 - 6
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs

@@ -3,7 +3,7 @@
 
 using System;
 using System.Collections.Generic;
-using System.Linq;
+using Microsoft.AspNetCore.Analyzers.Infrastructure;
 using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -47,21 +47,36 @@ internal class Endpoint
         {
             var parameter = new EndpointParameter(method.Parameters[i], wellKnownTypes);
 
-            if (parameter.Source == EndpointParameterSource.Unknown)
+            switch (parameter.Source)
             {
-                Diagnostics.Add(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor(parameter.Name));
-                return;
+                case EndpointParameterSource.BindAsync:
+                    IsAwaitable = true;
+                    switch (parameter.BindMethod)
+                    {
+                        case BindabilityMethod.IBindableFromHttpContext:
+                        case BindabilityMethod.BindAsyncWithParameter:
+                            NeedsParameterArray = true;
+                            break;
+                    }
+                    break;
+                case EndpointParameterSource.JsonBody:
+                case EndpointParameterSource.JsonBodyOrService:
+                    IsAwaitable = true;
+                    break;
+                case EndpointParameterSource.Unknown:
+                    Diagnostics.Add(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor(parameter.Name));
+                    break;
             }
 
             parameters[i] = parameter;
         }
 
         Parameters = parameters;
-        IsAwaitable |= parameters.Any(parameter => parameter.Source == EndpointParameterSource.JsonBody);
     }
 
     public string HttpMethod { get; }
-    public bool IsAwaitable { get; set; }
+    public bool IsAwaitable { get; }
+    public bool NeedsParameterArray { get; }
     public string? RoutePattern { get; }
     public EndpointResponse? Response { get; }
     public EndpointParameter[] Parameters { get; } = Array.Empty<EndpointParameter>();

+ 49 - 35
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs

@@ -3,13 +3,11 @@
 
 using System;
 using System.Diagnostics.CodeAnalysis;
-using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
+using Microsoft.AspNetCore.Analyzers.Infrastructure;
 using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
+using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
 using Microsoft.CodeAnalysis;
 using WellKnownType = Microsoft.AspNetCore.App.Analyzers.Infrastructure.WellKnownTypeData.WellKnownType;
-using Microsoft.AspNetCore.Analyzers.Infrastructure;
-using System.Linq;
-using System.Globalization;
 
 namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
 
@@ -19,35 +17,28 @@ internal class EndpointParameter
     {
         Type = parameter.Type;
         Name = parameter.Name;
+        Ordinal = parameter.Ordinal;
         Source = EndpointParameterSource.Unknown;
+        IsOptional = parameter.IsOptional();
 
-        var fromQueryMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata);
-        var fromServiceMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata);
-        var fromRouteMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata);
-        var fromHeaderMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata);
-
-        if (parameter.HasAttributeImplementingInterface(fromRouteMetadataInterfaceType, out var fromRouteAttribute))
+        if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata), out var fromRouteAttribute))
         {
             Source = EndpointParameterSource.Route;
             Name = GetParameterName(fromRouteAttribute, parameter.Name);
-            IsOptional = parameter.IsOptional();
             IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter);
             ParsingBlockEmitter = parsingBlockEmitter;
         }
-        else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType, out var fromQueryAttribute))
+        else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata), out var fromQueryAttribute))
         {
             Source = EndpointParameterSource.Query;
             Name = GetParameterName(fromQueryAttribute, parameter.Name);
-            IsOptional = parameter.IsOptional();
-            AssigningCode = $"httpContext.Request.Query[\"{parameter.Name}\"]";
             IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter);
             ParsingBlockEmitter = parsingBlockEmitter;
         }
-        else if (parameter.HasAttributeImplementingInterface(fromHeaderMetadataInterfaceType, out var fromHeaderAttribute))
+        else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata), out var fromHeaderAttribute))
         {
             Source = EndpointParameterSource.Header;
             Name = GetParameterName(fromHeaderAttribute, parameter.Name);
-            IsOptional = parameter.IsOptional();
             IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter);
             ParsingBlockEmitter = parsingBlockEmitter;
         }
@@ -56,7 +47,7 @@ internal class EndpointParameter
             Source = EndpointParameterSource.JsonBody;
             IsOptional = isOptional;
         }
-        else if (parameter.HasAttributeImplementingInterface(fromServiceMetadataInterfaceType))
+        else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata)))
         {
             Source = EndpointParameterSource.Service;
             IsOptional = parameter.Type is INamedTypeSymbol { NullableAnnotation: NullableAnnotation.Annotated } || parameter.HasExplicitDefaultValue;
@@ -66,10 +57,20 @@ internal class EndpointParameter
             Source = EndpointParameterSource.SpecialType;
             AssigningCode = specialTypeAssigningCode;
         }
+        else if (HasBindAsync(parameter, wellKnownTypes, out var bindMethod))
+        {
+            Source = EndpointParameterSource.BindAsync;
+            BindMethod = bindMethod;
+        }
         else if (parameter.Type.SpecialType == SpecialType.System_String)
         {
             Source = EndpointParameterSource.RouteOrQuery;
-            IsOptional = parameter.IsOptional();
+        }
+        else if (TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter))
+        {
+            Source = EndpointParameterSource.RouteOrQuery;
+            IsParsable = true;
+            ParsingBlockEmitter = parsingBlockEmitter;
         }
         else
         {
@@ -78,23 +79,46 @@ internal class EndpointParameter
         }
     }
 
-    private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)]out Action<CodeWriter, string, string>? parsingBlockEmitter)
+    public ITypeSymbol Type { get; }
+    public string Name { get; }
+    public int Ordinal { get; }
+    public bool IsOptional { get; }
+
+    public EndpointParameterSource Source { get; }
+
+    // Only used for SpecialType parameters that need
+    // to be resolved by a specific WellKnownType
+    public string? AssigningCode { get; }
+
+    [MemberNotNullWhen(true, nameof(ParsingBlockEmitter))]
+    public bool IsParsable { get; }
+    public Action<CodeWriter, string, string>? ParsingBlockEmitter { get; }
+
+    public BindabilityMethod? BindMethod { get; }
+
+    private static bool HasBindAsync(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out BindabilityMethod? bindMethod)
+    {
+        var parameterType = parameter.Type.UnwrapTypeSymbol();
+        return ParsabilityHelper.GetBindability(parameterType, wellKnownTypes, out bindMethod) == Bindability.Bindable;
+    }
+
+    private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out Action<CodeWriter, string, string>? parsingBlockEmitter)
     {
         var parameterType = parameter.Type.UnwrapTypeSymbol();
 
         // ParsabilityHelper returns a single enumeration with a Parsable/NonParsable enumeration result. We use this already
         // in the analyzers to determine whether we need to warn on whether a type needs to implement TryParse/IParsable<T>. To
-        // support usage in the code generator an optiona out parameter has been added to hint at what variant of the various
+        // support usage in the code generator an optional out parameter has been added to hint at what variant of the various
         // TryParse methods should be used (this implies that the preferences are baked into ParsabilityHelper). If we aren't
         // parsable at all we bail.
-        if (ParsabilityHelper.GetParsability(parameterType, wellKnownTypes, out var parsabilityMethod) == Parsability.NotParsable)
+        if (ParsabilityHelper.GetParsability(parameterType, wellKnownTypes, out var parsabilityMethod) != Parsability.Parsable)
         {
             parsingBlockEmitter = null;
             return false;
         }
 
         // If we are parsable we need to emit code based on the enumeration ParsabilityMethod which has a bunch of members
-        // which spell out the preferred TryParse uage. This swtich statement makes slight variations to them based on
+        // which spell out the preferred TryParse usage. This switch statement makes slight variations to them based on
         // which method was encountered.
         Func<string, string, string>? preferredTryParseInvocation = parsabilityMethod switch
         {
@@ -104,7 +128,7 @@ internal class EndpointParameter
             ParsabilityMethod.Enum => (string inputArgument, string outputArgument) => $$"""Enum.TryParse<{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}>({{inputArgument}}!, out var {{outputArgument}})""",
             ParsabilityMethod.Uri => (string inputArgument, string outputArgument) => $$"""Uri.TryCreate({{inputArgument}}!, UriKind.RelativeOrAbsolute, out var {{outputArgument}})""",
             ParsabilityMethod.String => null, // string parameters don't require parsing
-            _ => null
+            _ => throw new Exception("Unreachable!"),
         };
 
         // Special case handling for specific types
@@ -166,18 +190,6 @@ internal class EndpointParameter
         return true;
     }
 
-    public ITypeSymbol Type { get; }
-    public EndpointParameterSource Source { get; }
-
-    // Only used for SpecialType parameters that need
-    // to be resolved by a specific WellKnownType
-    internal string? AssigningCode { get; set; }
-    public string Name { get; }
-    public bool IsOptional { get; }
-    [MemberNotNull("ParsingBlockEmitter")]
-    public bool IsParsable { get; }
-    public Action<CodeWriter, string, string> ParsingBlockEmitter { get; }
-
     // TODO: Handle special form types like IFormFileCollection that need special body-reading logic.
     private static bool TryGetSpecialTypeAssigningCode(ITypeSymbol type, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out string? callingCode)
     {
@@ -245,6 +257,8 @@ internal class EndpointParameter
         obj is EndpointParameter other &&
         other.Source == Source &&
         other.Name == Name &&
+        other.Ordinal == Ordinal &&
+        other.IsOptional == IsOptional &&
         SymbolEqualityComparer.Default.Equals(other.Type, Type);
 
     public bool SignatureEquals(object obj) =>

+ 8 - 4
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs

@@ -2,9 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
-using System.CodeDom.Compiler;
-using System.Globalization;
-using System.IO;
 using System.Linq;
 using System.Text;
 using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
@@ -110,7 +107,14 @@ internal static class StaticRouteHandlerModelEmitter
             codeWriter.Write("await ");
         }
         codeWriter.WriteLine($"handler({endpoint.EmitArgumentList()});");
-        codeWriter.WriteLine(endpoint.Response.IsVoid ? "return Task.CompletedTask;" : endpoint.EmitResponseWritingCall());
+        if (!endpoint.Response.IsVoid)
+        {
+            codeWriter.WriteLine(endpoint.EmitResponseWritingCall());
+        }
+        else if (!endpoint.IsAwaitable)
+        {
+            codeWriter.WriteLine("return Task.CompletedTask;");
+        }
         codeWriter.EndBlock(); // End handler method block
     }
 

+ 1 - 1
src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj

@@ -29,7 +29,7 @@
 
   <ItemGroup>
     <Content Include="RequestDelegateGenerator\Baselines\*.generated.txt">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
 </Project>

+ 0 - 92
src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs

@@ -628,12 +628,6 @@ public partial class RequestDelegateFactoryTests : LoggedTest
         }
     }
 
-    private class MyBindAsyncTypeThatThrows
-    {
-        public static ValueTask<MyBindAsyncTypeThatThrows?> BindAsync(HttpContext context, ParameterInfo parameter) =>
-            throw new InvalidOperationException("BindAsync failed");
-    }
-
     private record MyBindAsyncRecord(Uri Uri)
     {
         public static ValueTask<MyBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
@@ -1123,79 +1117,6 @@ public partial class RequestDelegateFactoryTests : LoggedTest
         Assert.Equal(42, httpContext.Items["tryParsable"]);
     }
 
-    [Fact]
-    public async Task RequestDelegatePrefersBindAsyncOverTryParse()
-    {
-        var httpContext = CreateHttpContext();
-
-        httpContext.Request.Headers.Referer = "https://example.org";
-
-        var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncRecord myBindAsyncRecord) =>
-        {
-            httpContext.Items["myBindAsyncRecord"] = myBindAsyncRecord;
-        });
-
-        var requestDelegate = resultFactory.RequestDelegate;
-
-        await requestDelegate(httpContext);
-
-        Assert.Equal(new MyBindAsyncRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncRecord"]);
-    }
-
-    [Fact]
-    public async Task RequestDelegatePrefersBindAsyncOverTryParseForNonNullableStruct()
-    {
-        var httpContext = CreateHttpContext();
-
-        httpContext.Request.Headers.Referer = "https://example.org";
-
-        var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct myBindAsyncStruct) =>
-        {
-            httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct;
-        });
-
-        var requestDelegate = resultFactory.RequestDelegate;
-        await requestDelegate(httpContext);
-
-        Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]);
-    }
-
-    [Fact]
-    public async Task RequestDelegateUsesBindAsyncOverTryParseGivenNullableStruct()
-    {
-        var httpContext = CreateHttpContext();
-
-        httpContext.Request.Headers.Referer = "https://example.org";
-
-        var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct? myBindAsyncStruct) =>
-        {
-            httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct;
-        });
-
-        var requestDelegate = resultFactory.RequestDelegate;
-        await requestDelegate(httpContext);
-
-        Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]);
-    }
-
-    [Fact]
-    public async Task RequestDelegateUsesParameterInfoBindAsyncOverOtherBindAsync()
-    {
-        var httpContext = CreateHttpContext();
-
-        httpContext.Request.Headers.Referer = "https://example.org";
-
-        var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBothBindAsyncStruct? myBothBindAsyncStruct) =>
-        {
-            httpContext.Items["myBothBindAsyncStruct"] = myBothBindAsyncStruct;
-        });
-
-        var requestDelegate = resultFactory.RequestDelegate;
-        await requestDelegate(httpContext);
-
-        Assert.Equal(new MyBothBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBothBindAsyncStruct"]);
-    }
-
     [Fact]
     public async Task RequestDelegateUsesTryParseOverBindAsyncGivenExplicitAttribute()
     {
@@ -1554,19 +1475,6 @@ public partial class RequestDelegateFactoryTests : LoggedTest
         Assert.Equal(400, badHttpRequestException.StatusCode);
     }
 
-    [Fact]
-    public async Task BindAsyncExceptionsAreUncaught()
-    {
-        var httpContext = CreateHttpContext();
-
-        var factoryResult = RequestDelegateFactory.Create((MyBindAsyncTypeThatThrows arg1) => { });
-
-        var requestDelegate = factoryResult.RequestDelegate;
-
-        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegate(httpContext));
-        Assert.Equal("BindAsync failed", ex.Message);
-    }
-
     [Fact]
     public async Task BindAsyncWithBodyArgument()
     {

+ 1726 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt

@@ -0,0 +1,1726 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+#nullable enable
+
+namespace Microsoft.AspNetCore.Builder
+{
+    %GENERATEDCODEATTRIBUTE%
+    internal class SourceKey
+    {
+        public string Path { get; init; }
+        public int Line { get; init; }
+
+        public SourceKey(string path, int line)
+        {
+            Path = path;
+            Line = line;
+        }
+    }
+
+    // This class needs to be internal so that the compiled application
+    // has access to the strongly-typed endpoint definitions that are
+    // generated by the compiler so that they will be favored by
+    // overload resolution and opt the runtime in to the code generated
+    // implementation produced here.
+    %GENERATEDCODEATTRIBUTE%
+    internal static class GenerateRouteBuilderEndpoints
+    {
+        private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get };
+        private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post };
+        private static readonly string[] PutVerb = new[]  { global::Microsoft.AspNetCore.Http.HttpMethods.Put };
+        private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete };
+        private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch };
+
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
+            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
+            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?, global::System.String> handler,
+            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
+            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
+        {
+            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
+                endpoints,
+                pattern,
+                handler,
+                GetVerb,
+                filePath,
+                lineNumber);
+        }
+
+    }
+}
+
+namespace Microsoft.AspNetCore.Http.Generated
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using System.Linq;
+    using System.Reflection;
+    using System.Text.Json;
+    using System.Text.Json.Serialization.Metadata;
+    using System.Threading.Tasks;
+    using System.IO;
+    using Microsoft.AspNetCore.Routing;
+    using Microsoft.AspNetCore.Routing.Patterns;
+    using Microsoft.AspNetCore.Builder;
+    using Microsoft.AspNetCore.Http;
+    using Microsoft.AspNetCore.Http.Json;
+    using Microsoft.AspNetCore.Http.Metadata;
+    using Microsoft.Extensions.DependencyInjection;
+    using Microsoft.Extensions.FileProviders;
+    using Microsoft.Extensions.Primitives;
+    using Microsoft.Extensions.Options;
+
+    using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
+    using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
+
+    file static class GeneratedRouteBuilderExtensionsCore
+    {
+
+        private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
+        {
+            [(@"TestMapActions.cs", 24)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 25)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 26)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 27)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 28)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 28));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 29)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 29));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 30)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 30));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 31)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 31));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 32)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 32));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 33)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 33));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 34)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 34));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 35)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 35));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 36)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 36));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface>(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface>(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 37)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 37));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface>(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface>(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 38)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 38));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 39)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 39));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 40)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 40));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.HttpContext>(0), ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface>(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(httpContext_local, myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var httpContext_local = httpContext;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface>(httpContext, parameters[1]);
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface myBindAsyncParam_local;
+                        if ((object?)myBindAsyncParam_temp == null)
+                        {
+                            wasParamCheckFailure = true;
+                            myBindAsyncParam_local = default!;
+                        }
+                        else
+                        {
+                            myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface)myBindAsyncParam_temp;
+                        }
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.HttpContext, global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface>(httpContext, httpContext_local, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+            [(@"TestMapActions.cs", 41)] = (
+                (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 41));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?, global::System.String>)del;
+                    EndpointFilterDelegate? filteredInvocation = null;
+                    var parameters = del.Method.GetParameters();
+
+                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
+                    {
+                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
+                        {
+                            if (ic.HttpContext.Response.StatusCode == 400)
+                            {
+                                return ValueTask.FromResult<object?>(Results.Empty);
+                            }
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    async Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface>(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(myBindAsyncParam_local);
+                        await httpContext.Response.WriteAsync(result);
+                    }
+
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        var myBindAsyncParam_temp = await BindAsync<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface>(httpContext, parameters[0]);
+                        var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?)myBindAsyncParam_temp;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?>(httpContext, myBindAsyncParam_local));
+                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
+                    }
+
+                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
+                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
+                    return new RequestDelegateResult(targetDelegate, metadata);
+                }),
+
+        };
+
+        internal static RouteHandlerBuilder MapCore(
+            this IEndpointRouteBuilder routes,
+            string pattern,
+            Delegate handler,
+            IEnumerable<string> httpMethods,
+            string filePath,
+            int lineNumber)
+        {
+            var (populateMetadata, createRequestDelegate) = map[(filePath, lineNumber)];
+            return RouteHandlerServices.Map(routes, pattern, handler, httpMethods, populateMetadata, createRequestDelegate);
+        }
+
+        private static EndpointFilterDelegate BuildFilterDelegate(EndpointFilterDelegate filteredInvocation, EndpointBuilder builder, MethodInfo mi)
+        {
+            var routeHandlerFilters =  builder.FilterFactories;
+            var context0 = new EndpointFilterFactoryContext
+            {
+                MethodInfo = mi,
+                ApplicationServices = builder.ApplicationServices,
+            };
+            var initialFilteredInvocation = filteredInvocation;
+            for (var i = routeHandlerFilters.Count - 1; i >= 0; i--)
+            {
+                var filterFactory = routeHandlerFilters[i];
+                filteredInvocation = filterFactory(context0, filteredInvocation);
+            }
+            return filteredInvocation;
+        }
+
+        private static Task ExecuteObjectResult(object? obj, HttpContext httpContext)
+        {
+            if (obj is IResult r)
+            {
+                return r.ExecuteAsync(httpContext);
+            }
+            else if (obj is string s)
+            {
+                return httpContext.Response.WriteAsync(s);
+            }
+            else
+            {
+                return httpContext.Response.WriteAsJsonAsync(obj);
+            }
+        }
+
+        private static Func<HttpContext, StringValues> ResolveFromRouteOrQuery(string parameterName, IEnumerable<string>? routeParameterNames)
+        {
+            return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true
+                ? (httpContext) => new StringValues((string?)httpContext.Request.RouteValues[parameterName])
+                : (httpContext) => httpContext.Request.Query[parameterName];
+        }
+
+        private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
+        {
+            var runtimeType = value?.GetType();
+            if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
+            {
+                return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
+            }
+
+            return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
+        }
+
+        private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
+        {
+            var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();
+
+            if (feature?.CanHaveBody == true)
+            {
+                if (!httpContext.Request.HasJsonContentType())
+                {
+                    httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
+                    return (false, default);
+                }
+                try
+                {
+                    var bodyValue = await httpContext.Request.ReadFromJsonAsync<T>();
+                    if (!allowEmpty && bodyValue == null)
+                    {
+                        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
+                        return (false, bodyValue);
+                    }
+                    return (true, bodyValue);
+                }
+                catch (IOException)
+                {
+                    return (false, default);
+                }
+                catch (System.Text.Json.JsonException)
+                {
+                    httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
+                    return (false, default);
+                }
+            }
+            return (false, default);
+        }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
+    }
+}

+ 25 - 19
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt

@@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder
         internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapPost(
             this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
             [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
-            global::System.Func<global::Todo, global::Microsoft.AspNetCore.Http.HttpResults.Ok<global::Todo>> handler,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo, global::Microsoft.AspNetCore.Http.HttpResults.Ok<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>> handler,
             [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
             [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
         {
@@ -90,16 +90,16 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
                 {
-                    var handler = (Func<global::Todo, global::Microsoft.AspNetCore.Http.HttpResults.Ok<global::Todo>>)del;
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo, global::Microsoft.AspNetCore.Http.HttpResults.Ok<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>>)del;
                     EndpointFilterDelegate? filteredInvocation = null;
 
                     if (options?.EndpointBuilder?.FilterFactories.Count > 0)
@@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                             {
                                 return ValueTask.FromResult<object?>(Results.Empty);
                             }
-                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Todo>(0)));
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(0)));
                         },
                         options.EndpointBuilder,
                         handler.Method);
@@ -119,8 +119,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                     async Task RequestHandler(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: todo (Type = Todo, IsOptional = False, IsParsable = False, Source = JsonBody)
-                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Todo>(httpContext, false);
+                        // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, Source = JsonBody)
+                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(httpContext, false);
                         if (!isSuccessful)
                         {
                             return;
@@ -139,8 +139,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: todo (Type = Todo, IsOptional = False, IsParsable = False, Source = JsonBody)
-                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Todo>(httpContext, false);
+                        // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, Source = JsonBody)
+                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(httpContext, false);
                         if (!isSuccessful)
                         {
                             return;
@@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             httpContext.Response.StatusCode = 400;
                         }
-                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Todo>(httpContext, todo_local!));
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(httpContext, todo_local!));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 
@@ -158,16 +158,16 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 25)] = (
+            [(@"TestMapActions.cs", 26)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
                 {
-                    var handler = (Func<global::Todo?, global::Microsoft.AspNetCore.Http.HttpResults.Ok<global::Todo>>)del;
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?, global::Microsoft.AspNetCore.Http.HttpResults.Ok<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>>)del;
                     EndpointFilterDelegate? filteredInvocation = null;
 
                     if (options?.EndpointBuilder?.FilterFactories.Count > 0)
@@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                             {
                                 return ValueTask.FromResult<object?>(Results.Empty);
                             }
-                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Todo?>(0)));
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?>(0)));
                         },
                         options.EndpointBuilder,
                         handler.Method);
@@ -187,8 +187,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                     async Task RequestHandler(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: todo (Type = Todo?, IsOptional = True, IsParsable = False, Source = JsonBody)
-                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Todo?>(httpContext, true);
+                        // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, IsParsable = False, Source = JsonBody)
+                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?>(httpContext, true);
                         if (!isSuccessful)
                         {
                             return;
@@ -207,8 +207,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: todo (Type = Todo?, IsOptional = True, IsParsable = False, Source = JsonBody)
-                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Todo?>(httpContext, true);
+                        // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, IsParsable = False, Source = JsonBody)
+                        var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?>(httpContext, true);
                         if (!isSuccessful)
                         {
                             return;
@@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             httpContext.Response.StatusCode = 400;
                         }
-                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Todo?>(httpContext, todo_local));
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?>(httpContext, todo_local));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 
@@ -325,5 +325,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 12 - 6
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt

@@ -120,11 +120,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -180,11 +180,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 24)] = (
+            [(@"TestMapActions.cs", 25)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -240,11 +240,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 25)] = (
+            [(@"TestMapActions.cs", 26)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -403,5 +403,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 32 - 22
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt

@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var queryValue_temp = queryValue_raw.ToString();
+                        var queryValue_temp = (string?)queryValue_raw;
                         string queryValue_local = queryValue_temp!;
 
                         if (wasParamCheckFailure)
@@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var queryValue_temp = queryValue_raw.ToString();
+                        var queryValue_temp = (string?)queryValue_raw;
                         string queryValue_local = queryValue_temp!;
 
                         if (wasParamCheckFailure)
@@ -162,11 +162,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 24)] = (
+            [(@"TestMapActions.cs", 25)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var headerValue_temp = headerValue_raw.ToString();
+                        var headerValue_temp = (string?)headerValue_raw;
                         string headerValue_local = headerValue_temp!;
 
                         if (wasParamCheckFailure)
@@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var headerValue_temp = headerValue_raw.ToString();
+                        var headerValue_temp = (string?)headerValue_raw;
                         string headerValue_local = headerValue_temp!;
 
                         if (wasParamCheckFailure)
@@ -234,11 +234,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 25)] = (
+            [(@"TestMapActions.cs", 26)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -268,12 +268,12 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             throw new InvalidOperationException($"'routeValue' is not a route parameter.");
                         }
-                        var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString();
+                        var routeValue_raw = (string?)httpContext.Request.RouteValues["routeValue"];
                         if (routeValue_raw == null)
                         {
                             wasParamCheckFailure = true;
                         }
-                        var routeValue_temp = routeValue_raw?.ToString();
+                        var routeValue_temp = (string?)routeValue_raw;
                         string routeValue_local = routeValue_temp!;
 
                         if (wasParamCheckFailure)
@@ -294,12 +294,12 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             throw new InvalidOperationException($"'routeValue' is not a route parameter.");
                         }
-                        var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString();
+                        var routeValue_raw = (string?)httpContext.Request.RouteValues["routeValue"];
                         if (routeValue_raw == null)
                         {
                             wasParamCheckFailure = true;
                         }
-                        var routeValue_temp = routeValue_raw?.ToString();
+                        var routeValue_temp = (string?)routeValue_raw;
                         string routeValue_local = routeValue_temp!;
 
                         if (wasParamCheckFailure)
@@ -314,11 +314,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 26)] = (
+            [(@"TestMapActions.cs", 27)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -350,7 +350,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var value_local = value_raw;
+                        var value_temp = (string?)value_raw;
+                        string value_local = value_temp!;
 
                         if (wasParamCheckFailure)
                         {
@@ -371,7 +372,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var value_local = value_raw;
+                        var value_temp = (string?)value_raw;
+                        string value_local = value_temp!;
 
                         if (wasParamCheckFailure)
                         {
@@ -385,11 +387,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 27)] = (
+            [(@"TestMapActions.cs", 28)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 28));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -421,7 +423,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var value_local = value_raw;
+                        var value_temp = (string?)value_raw;
+                        string value_local = value_temp!;
 
                         if (wasParamCheckFailure)
                         {
@@ -442,7 +445,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var value_local = value_raw;
+                        var value_temp = (string?)value_raw;
+                        string value_local = value_temp!;
 
                         if (wasParamCheckFailure)
                         {
@@ -555,5 +559,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 8 - 2
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt

@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -249,5 +249,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 12 - 6
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt

@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p1_temp = p1_raw.ToString();
+                        var p1_temp = (string?)p1_raw;
                         string p1_local = p1_temp!;
                         // Endpoint Parameter: p2 (Type = string, IsOptional = False, IsParsable = False, Source = Query)
                         var p2_raw = httpContext.Request.Query["p2"];
@@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p2_temp = p2_raw.ToString();
+                        var p2_temp = (string?)p2_raw;
                         string p2_local = p2_temp!;
 
                         if (wasParamCheckFailure)
@@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p1_temp = p1_raw.ToString();
+                        var p1_temp = (string?)p1_raw;
                         string p1_local = p1_temp!;
                         // Endpoint Parameter: p2 (Type = string, IsOptional = False, IsParsable = False, Source = Query)
                         var p2_raw = httpContext.Request.Query["p2"];
@@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p2_temp = p2_raw.ToString();
+                        var p2_temp = (string?)p2_raw;
                         string p2_local = p2_temp!;
 
                         if (wasParamCheckFailure)
@@ -277,5 +277,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 8 - 2
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt

@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -243,5 +243,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 20 - 14
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt

@@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder
         internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
             this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
             [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
-            global::System.Func<global::Todo, global::System.String> handler,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo, global::System.String> handler,
             [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
             [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
         {
@@ -90,16 +90,16 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
                 {
-                    var handler = (Func<global::Todo, global::System.String>)del;
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo, global::System.String>)del;
                     EndpointFilterDelegate? filteredInvocation = null;
 
                     if (options?.EndpointBuilder?.FilterFactories.Count > 0)
@@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                             {
                                 return ValueTask.FromResult<object?>(Results.Empty);
                             }
-                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Todo>(0)));
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(0)));
                         },
                         options.EndpointBuilder,
                         handler.Method);
@@ -119,18 +119,18 @@ namespace Microsoft.AspNetCore.Http.Generated
                     Task RequestHandler(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: p (Type = Todo, IsOptional = False, IsParsable = True, Source = Query)
+                        // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
                         if (StringValues.IsNullOrEmpty(p_raw))
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_temp = p_raw.ToString();
-                        if (!global::Todo.TryParse(p_temp!, out var p_parsed_temp))
+                        var p_temp = (string?)p_raw;
+                        if (!global::Microsoft.AspNetCore.Http.Generators.Tests.Todo.TryParse(p_temp!, out var p_parsed_temp))
                         {
                             wasParamCheckFailure = true;
                         }
-                        global::Todo p_local = p_parsed_temp!;
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.Todo p_local = p_parsed_temp!;
 
                         if (wasParamCheckFailure)
                         {
@@ -145,24 +145,24 @@ namespace Microsoft.AspNetCore.Http.Generated
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: p (Type = Todo, IsOptional = False, IsParsable = True, Source = Query)
+                        // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
                         if (StringValues.IsNullOrEmpty(p_raw))
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_temp = p_raw.ToString();
-                        if (!global::Todo.TryParse(p_temp!, out var p_parsed_temp))
+                        var p_temp = (string?)p_raw;
+                        if (!global::Microsoft.AspNetCore.Http.Generators.Tests.Todo.TryParse(p_temp!, out var p_parsed_temp))
                         {
                             wasParamCheckFailure = true;
                         }
-                        global::Todo p_local = p_parsed_temp!;
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.Todo p_local = p_parsed_temp!;
 
                         if (wasParamCheckFailure)
                         {
                             httpContext.Response.StatusCode = 400;
                         }
-                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Todo>(httpContext, p_local));
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.Todo>(httpContext, p_local));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 
@@ -269,5 +269,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 20 - 14
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt

@@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder
         internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
             this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
             [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
-            global::System.Func<global::TodoStatus, global::System.String> handler,
+            global::System.Func<global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus, global::System.String> handler,
             [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
             [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
         {
@@ -90,16 +90,16 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
                 {
-                    var handler = (Func<global::TodoStatus, global::System.String>)del;
+                    var handler = (Func<global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus, global::System.String>)del;
                     EndpointFilterDelegate? filteredInvocation = null;
 
                     if (options?.EndpointBuilder?.FilterFactories.Count > 0)
@@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                             {
                                 return ValueTask.FromResult<object?>(Results.Empty);
                             }
-                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::TodoStatus>(0)));
+                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus>(0)));
                         },
                         options.EndpointBuilder,
                         handler.Method);
@@ -119,18 +119,18 @@ namespace Microsoft.AspNetCore.Http.Generated
                     Task RequestHandler(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: p (Type = TodoStatus, IsOptional = False, IsParsable = True, Source = Query)
+                        // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus, IsOptional = False, IsParsable = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
                         if (StringValues.IsNullOrEmpty(p_raw))
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_temp = p_raw.ToString();
-                        if (!Enum.TryParse<global::TodoStatus>(p_temp!, out var p_parsed_temp))
+                        var p_temp = (string?)p_raw;
+                        if (!Enum.TryParse<global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus>(p_temp!, out var p_parsed_temp))
                         {
                             wasParamCheckFailure = true;
                         }
-                        global::TodoStatus p_local = p_parsed_temp!;
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus p_local = p_parsed_temp!;
 
                         if (wasParamCheckFailure)
                         {
@@ -145,24 +145,24 @@ namespace Microsoft.AspNetCore.Http.Generated
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
                         var wasParamCheckFailure = false;
-                        // Endpoint Parameter: p (Type = TodoStatus, IsOptional = False, IsParsable = True, Source = Query)
+                        // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus, IsOptional = False, IsParsable = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
                         if (StringValues.IsNullOrEmpty(p_raw))
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_temp = p_raw.ToString();
-                        if (!Enum.TryParse<global::TodoStatus>(p_temp!, out var p_parsed_temp))
+                        var p_temp = (string?)p_raw;
+                        if (!Enum.TryParse<global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus>(p_temp!, out var p_parsed_temp))
                         {
                             wasParamCheckFailure = true;
                         }
-                        global::TodoStatus p_local = p_parsed_temp!;
+                        global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus p_local = p_parsed_temp!;
 
                         if (wasParamCheckFailure)
                         {
                             httpContext.Response.StatusCode = 400;
                         }
-                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::TodoStatus>(httpContext, p_local));
+                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus>(httpContext, p_local));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 
@@ -269,5 +269,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 10 - 4
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt

@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = string?, IsOptional = True, IsParsable = False, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? (string?)p_raw : null;
                         string p_local = p_temp!;
 
                         if (wasParamCheckFailure)
@@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = string?, IsOptional = True, IsParsable = False, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? (string?)p_raw : null;
                         string p_local = p_temp!;
 
                         if (wasParamCheckFailure)
@@ -253,5 +253,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 10 - 4
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt

@@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_temp = p_raw.ToString();
+                        var p_temp = (string?)p_raw;
                         if (!global::System.TimeOnly.TryParse(p_temp!, CultureInfo.InvariantCulture, out var p_parsed_temp))
                         {
                             wasParamCheckFailure = true;
@@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_temp = p_raw.ToString();
+                        var p_temp = (string?)p_raw;
                         if (!global::System.TimeOnly.TryParse(p_temp!, CultureInfo.InvariantCulture, out var p_parsed_temp))
                         {
                             wasParamCheckFailure = true;
@@ -269,5 +269,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 14 - 8
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt

@@ -120,11 +120,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -174,11 +174,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 24)] = (
+            [(@"TestMapActions.cs", 25)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -228,11 +228,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 25)] = (
+            [(@"TestMapActions.cs", 26)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -282,11 +282,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 26)] = (
+            [(@"TestMapActions.cs", 27)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -435,5 +435,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 12 - 6
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt

@@ -120,11 +120,11 @@ namespace Microsoft.AspNetCore.Http.Generated
 
         private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
         {
-            [(@"TestMapActions.cs", 23)] = (
+            [(@"TestMapActions.cs", 24)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -178,11 +178,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 24)] = (
+            [(@"TestMapActions.cs", 25)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -236,11 +236,11 @@ namespace Microsoft.AspNetCore.Http.Generated
                     var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
                     return new RequestDelegateResult(targetDelegate, metadata);
                 }),
-            [(@"TestMapActions.cs", 25)] = (
+            [(@"TestMapActions.cs", 26)] = (
                 (methodInfo, options) =>
                 {
                     Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25));
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26));
                     return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
                 },
                 (del, options, inferredMetadataResult) =>
@@ -395,5 +395,11 @@ namespace Microsoft.AspNetCore.Http.Generated
             }
             return (false, default);
         }
+
+        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
+            where T : class, IBindableFromHttpContext<T>
+        {
+            return T.BindAsync(context, parameter);
+        }
     }
 }

+ 17 - 104
src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs

@@ -25,6 +25,10 @@ namespace Microsoft.AspNetCore.Http.Generators.Tests;
 
 public abstract class RequestDelegateGeneratorTestBase : LoggedTest
 {
+    // Change this to true and run tests in development to regenerate baseline files.
+    // Then: cp artifacts/bin/Microsoft.AspNetCore.Http.Extensions.Tests/Debug/net8.0/RequestDelegateGenerator/Baselines/* src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines
+    public bool RegenerateBaselines => false;
+
     protected abstract bool IsGeneratorEnabled { get; }
 
     internal async Task<(GeneratorRunResult?, Compilation)> RunGeneratorAsync(string sources, params string[] updatedSources)
@@ -215,6 +219,7 @@ public abstract class RequestDelegateGeneratorTestBase : LoggedTest
 #nullable enable
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Numerics;
 using System.Reflection;
@@ -238,109 +243,6 @@ public static class TestMapActions
         return app;
     }
 }
-
-public enum TodoStatus
-{
-    Trap, // A trap for Enum.TryParse<T>!
-    Done,
-    InProgress,
-    NotDone
-}
-
-public interface ITodo
-{
-    public int Id { get; }
-    public string? Name { get; }
-    public bool IsComplete { get; }
-    public TodoStatus Status { get; }
-}
-
-public class PrecedenceCheckTodo
-{
-    public PrecedenceCheckTodo(int magicValue)
-    {
-        MagicValue = magicValue;
-    }
-    public int MagicValue { get; }
-    public static bool TryParse(string? input, IFormatProvider? provider, out PrecedenceCheckTodo result)
-    {
-        result = new PrecedenceCheckTodo(42);
-        return true;
-    }
-    public static bool TryParse(string? input, out PrecedenceCheckTodo result)
-    {
-        result = new PrecedenceCheckTodo(24);
-        return true;
-    }
-}
-
-public class PrecedenceCheckTodoWithoutFormat
-{
-    public PrecedenceCheckTodoWithoutFormat(int magicValue)
-    {
-        MagicValue = magicValue;
-    }
-    public int MagicValue { get; }
-    public static bool TryParse(string? input, out PrecedenceCheckTodoWithoutFormat result)
-    {
-        result = new PrecedenceCheckTodoWithoutFormat(24);
-        return true;
-    }
-}
-
-public class ParsableTodo : IParsable<ParsableTodo>
-{
-    public int Id { get; set; }
-    public string? Name { get; set; } = "Todo";
-    public bool IsComplete { get; set; }
-    public static ParsableTodo Parse(string s, IFormatProvider? provider)
-    {
-        return new ParsableTodo();
-    }
-    public static bool TryParse(string? input, IFormatProvider? provider, out ParsableTodo result)
-    {
-        if (input == "1")
-        {
-            result = new ParsableTodo
-            {
-            Id = 1,
-            Name = "Knit kitten mittens.",
-            IsComplete = false
-            };
-            return true;
-        }
-        else
-        {
-        result = null!;
-        return false;
-        }
-    }
-}
-
-public class Todo
-{
-    public int Id { get; set; }
-    public string? Name { get; set; } = "Todo";
-    public bool IsComplete { get; set; }
-    public static bool TryParse(string input, out Todo? result)
-    {
-        if (input == "1")
-        {
-            result = new Todo
-            {
-            Id = 1,
-            Name = "Knit kitten mittens.",
-            IsComplete = false
-            };
-            return true;
-        }
-        else
-        {
-        result = null;
-        return false;
-        }
-    }
-}
 """;
     private static Task<Compilation> CreateCompilationAsync(string sources)
     {
@@ -383,9 +285,20 @@ public class Todo
         {
             return;
         }
+
         var baselineFilePath = Path.Combine("RequestDelegateGenerator", "Baselines", $"{callerName}.generated.txt");
         var generatedSyntaxTree = compilation.SyntaxTrees.Last();
         var generatedCode = await generatedSyntaxTree.GetTextAsync();
+
+        if (RegenerateBaselines)
+        {
+            var newSource = generatedCode.ToString()
+                .Replace(RequestDelegateGeneratorSources.GeneratedCodeAttribute, "%GENERATEDCODEATTRIBUTE%")
+                + Environment.NewLine;
+            await File.WriteAllTextAsync(baselineFilePath, newSource);
+            Assert.Fail("RegenerateBaselines=true. Do not merge PRs with this set.");
+        }
+
         var baseline = await File.ReadAllTextAsync(baselineFilePath);
         var expectedLines = baseline
             .TrimEnd() // Trim newlines added by autoformat
@@ -395,7 +308,7 @@ public class Todo
         Assert.True(CompareLines(expectedLines, generatedCode, out var errorMessage), errorMessage);
     }
 
-    private bool CompareLines(string[] expectedLines, SourceText sourceText, out string message)
+    private static bool CompareLines(string[] expectedLines, SourceText sourceText, out string message)
     {
         if (expectedLines.Length != sourceText.Lines.Count)
         {

+ 221 - 10
src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs

@@ -1,23 +1,19 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Collections;
-using System;
 using System.Globalization;
-using System.Net.Sockets;
 using System.Net;
+using System.Net.Sockets;
 using System.Numerics;
-using System.Reflection.Metadata;
 using System.Reflection;
+using System.Reflection.Metadata;
+using System.Text;
+using System.Text.Encodings.Web;
 using System.Text.Json;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
 using Microsoft.Extensions.DependencyInjection;
-using System.Text.Encodings.Web;
-using Microsoft.AspNetCore.WebUtilities;
-using Microsoft.Extensions.WebEncoders.Testing;
+using Microsoft.Extensions.Primitives;
 
 namespace Microsoft.AspNetCore.Http.Generators.Tests;
 
@@ -559,7 +555,7 @@ app.MapGet("/", GetTodo);
         await VerifyResponseBodyAsync(httpContext, expectedBody);
     }
 
-    public static IEnumerable<object[]>  MapAction_NoParam_TaskOfTReturn_Data => new List<object[]>()
+    public static IEnumerable<object[]> MapAction_NoParam_TaskOfTReturn_Data => new List<object[]>()
     {
         new object[] { @"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!" },
         new object[] { @"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item"" }));", """{"id":0,"name":"Test Item","isComplete":false}""" },
@@ -1182,4 +1178,219 @@ app.MapGet("/", HelloName)
 
         await VerifyResponseBodyAsync(httpContext, expectedBody);
     }
+
+    [Fact]
+    public async Task MapAction_InferredTryParse_NonOptional_Provided()
+    {
+        var source = """
+app.MapGet("/", (HttpContext httpContext, int id) =>
+{
+    httpContext.Items["id"] = id;
+});
+""";
+        var (_, compilation) = await RunGeneratorAsync(source);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+        {
+            ["id"] = "42",
+        });
+
+        httpContext.Request.Headers.Referer = "https://example.org";
+        await endpoint.RequestDelegate(httpContext);
+
+        Assert.Equal(42, httpContext.Items["id"]);
+        Assert.Equal(200, httpContext.Response.StatusCode);
+    }
+
+    public static object[][] BindAsyncUriTypesAndOptionalitySupport = new object[][]
+    {
+        new object[] { "MyBindAsyncRecord", false },
+        new object[] { "MyBindAsyncStruct", true },
+        new object[] { "MyNullableBindAsyncStruct", false },
+        new object[] { "MyBothBindAsyncStruct", true },
+        new object[] { "MySimpleBindAsyncRecord", false, },
+        new object[] { "MySimpleBindAsyncStruct", true },
+        new object[] { "BindAsyncFromImplicitStaticAbstractInterface", false },
+        new object[] { "InheritBindAsync", false },
+        new object[] { "BindAsyncFromExplicitStaticAbstractInterface", false },
+        // TODO: Fix this
+        //new object[] { "MyBindAsyncFromInterfaceRecord", false },
+    };
+
+    public static IEnumerable<object[]> BindAsyncUriTypes =>
+        BindAsyncUriTypesAndOptionalitySupport.Select(x => new[] { x[0] });
+
+    [Theory]
+    [MemberData(nameof(BindAsyncUriTypes))]
+    public async Task MapAction_BindAsync_Optional_Provided(string bindAsyncType)
+    {
+        var source = $$"""
+app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}}? myBindAsyncParam) =>
+{
+    httpContext.Items["uri"] = myBindAsyncParam?.Uri;
+});
+""";
+        var (_, compilation) = await RunGeneratorAsync(source);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.Headers.Referer = "https://example.org";
+        await endpoint.RequestDelegate(httpContext);
+
+        Assert.Equal(new Uri("https://example.org"), httpContext.Items["uri"]);
+        Assert.Equal(200, httpContext.Response.StatusCode);
+    }
+
+    [Theory]
+    [MemberData(nameof(BindAsyncUriTypes))]
+    public async Task MapAction_BindAsync_NonOptional_Provided(string bindAsyncType)
+    {
+        var source = $$"""
+app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}} myBindAsyncParam) =>
+{
+    httpContext.Items["uri"] = myBindAsyncParam.Uri;
+});
+""";
+        var (_, compilation) = await RunGeneratorAsync(source);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.Headers.Referer = "https://example.org";
+        await endpoint.RequestDelegate(httpContext);
+
+        Assert.Equal(new Uri("https://example.org"), httpContext.Items["uri"]);
+        Assert.Equal(200, httpContext.Response.StatusCode);
+    }
+
+    [Theory]
+    [MemberData(nameof(BindAsyncUriTypesAndOptionalitySupport))]
+    public async Task MapAction_BindAsync_Optional_NotProvided(string bindAsyncType, bool expectException)
+    {
+        var source = $$"""
+app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}}? myBindAsyncParam) =>
+{
+    httpContext.Items["uri"] = myBindAsyncParam?.Uri;
+});
+""";
+        var (_, compilation) = await RunGeneratorAsync(source);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        var httpContext = CreateHttpContext();
+
+        if (expectException)
+        {
+            // These types simply don't support optional parameters since they cannot return null.
+            var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => endpoint.RequestDelegate(httpContext));
+            Assert.Equal("The request is missing the required Referer header.", ex.Message);
+        }
+        else
+        {
+            await endpoint.RequestDelegate(httpContext);
+
+            Assert.Null(httpContext.Items["uri"]);
+            Assert.Equal(200, httpContext.Response.StatusCode);
+        }
+    }
+
+    [Theory]
+    [MemberData(nameof(BindAsyncUriTypesAndOptionalitySupport))]
+    public async Task MapAction_BindAsync_NonOptional_NotProvided(string bindAsyncType, bool expectException)
+    {
+        var source = $$"""
+app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}} myBindAsyncParam) =>
+{
+    httpContext.Items["uri"] = myBindAsyncParam.Uri;
+});
+""";
+        var (_, compilation) = await RunGeneratorAsync(source);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        var httpContext = CreateHttpContext();
+
+        if (expectException)
+        {
+            var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => endpoint.RequestDelegate(httpContext));
+            Assert.Equal("The request is missing the required Referer header.", ex.Message);
+        }
+        else
+        {
+            await endpoint.RequestDelegate(httpContext);
+
+            Assert.Null(httpContext.Items["uri"]);
+            Assert.Equal(400, httpContext.Response.StatusCode);
+        }
+    }
+
+    [Fact]
+    public async Task MapAction_BindAsync_Snapshot()
+    {
+        var source = new StringBuilder();
+
+        var i = 0;
+        while (i < BindAsyncUriTypesAndOptionalitySupport.Length * 2)
+        {
+            var bindAsyncType = BindAsyncUriTypesAndOptionalitySupport[i / 2][0];
+            source.AppendLine(CultureInfo.InvariantCulture, $$"""app.MapGet("/{{i}}", (HttpContext httpContext, {{bindAsyncType}} myBindAsyncParam) => "Hello world! {{i}}");""");
+            i++;
+            source.AppendLine(CultureInfo.InvariantCulture, $$"""app.MapGet("/{{i}}", ({{bindAsyncType}}? myBindAsyncParam) => "Hello world! {{i}}");""");
+            i++;
+        }
+
+        var (_, compilation) = await RunGeneratorAsync(source.ToString());
+
+        await VerifyAgainstBaselineUsingFile(compilation);
+
+        var endpoints = GetEndpointsFromCompilation(compilation);
+        Assert.Equal(BindAsyncUriTypesAndOptionalitySupport.Length * 2, endpoints.Length);
+
+        for (i = 0; i < BindAsyncUriTypesAndOptionalitySupport.Length * 2; i++)
+        {
+            var httpContext = CreateHttpContext();
+            // Set a referrer header so BindAsync always succeeds and the route handler is always called optional or not.
+            httpContext.Request.Headers.Referer = "https://example.org";
+
+            await endpoints[i].RequestDelegate(httpContext);
+            await VerifyResponseBodyAsync(httpContext, $"Hello world! {i}");
+        }
+    }
+
+    [Fact]
+    public async Task MapAction_BindAsync_ExceptionsAreUncaught()
+    {
+        var source = """
+app.MapGet("/", (HttpContext httpContext, MyBindAsyncTypeThatThrows myBindAsyncParam) => { });
+""";
+        var (_, compilation) = await RunGeneratorAsync(source);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.Headers.Referer = "https://example.org";
+
+        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => endpoint.RequestDelegate(httpContext));
+        Assert.Equal("BindAsync failed", ex.Message);
+    }
+
+    [Theory]
+    [InlineData("BindAsyncWrongType")]
+    [InlineData("BindAsyncFromStaticAbstractInterfaceWrongType")]
+    [InlineData("InheritBindAsyncWrongType")]
+    public async Task MapAction_BindAsync_WithWrongType_IsNotUsed(string bindAsyncType)
+    {
+        var source = $$"""
+app.MapGet("/", ({{bindAsyncType}} myNotBindAsyncParam) => { });
+""";
+        var (result, compilation) = await RunGeneratorAsync(source);
+
+        VerifyStaticEndpointModel(result, endpointModel =>
+        {
+            var parameter = Assert.Single(endpointModel.Parameters);
+            Assert.Equal("myNotBindAsyncParam", parameter.Name);
+            Assert.NotEqual(EndpointParameterSource.BindAsync, parameter.Source);
+        });
+
+        var ex = Assert.Throws<InvalidOperationException>(() => GetEndpointFromCompilation(compilation));
+        Assert.StartsWith($"BindAsync method found on {bindAsyncType} with incorrect format.", ex.Message);
+    }
 }

+ 361 - 1
src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs

@@ -1,9 +1,13 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Reflection;
 using Microsoft.AspNetCore.Http.Metadata;
 
 namespace Microsoft.AspNetCore.Http.Generators.Tests;
 
+#nullable  enable
+
 public class TestService
 {
     public string TestServiceMethod() => "Produced from service!";
@@ -12,11 +16,367 @@ public class TestService
 public class Todo
 {
     public int Id { get; set; }
-    public string Name { get; set; } = "Todo";
+    public string? Name { get; set; } = "Todo";
     public bool IsComplete { get; set; }
+    public static bool TryParse(string input, out Todo? result)
+    {
+        if (input == "1")
+        {
+            result = new Todo
+            {
+                Id = 1,
+                Name = "Knit kitten mittens.",
+                IsComplete = false
+            };
+            return true;
+        }
+        else
+        {
+            result = null;
+            return false;
+        }
+    }
 }
 
 public class CustomFromBodyAttribute : Attribute, IFromBodyMetadata
 {
     public bool AllowEmpty { get; set; }
 }
+
+public enum TodoStatus
+{
+    Trap, // A trap for Enum.TryParse<T>!
+    Done,
+    InProgress,
+    NotDone
+}
+
+public interface ITodo
+{
+    public int Id { get; }
+    public string? Name { get; }
+    public bool IsComplete { get; }
+    public TodoStatus Status { get; }
+}
+
+public class PrecedenceCheckTodo
+{
+    public PrecedenceCheckTodo(int magicValue)
+    {
+        MagicValue = magicValue;
+    }
+    public int MagicValue { get; }
+    public static bool TryParse(string? input, IFormatProvider? provider, out PrecedenceCheckTodo result)
+    {
+        result = new PrecedenceCheckTodo(42);
+        return true;
+    }
+    public static bool TryParse(string? input, out PrecedenceCheckTodo result)
+    {
+        result = new PrecedenceCheckTodo(24);
+        return true;
+    }
+}
+
+public class PrecedenceCheckTodoWithoutFormat
+{
+    public PrecedenceCheckTodoWithoutFormat(int magicValue)
+    {
+        MagicValue = magicValue;
+    }
+    public int MagicValue { get; }
+    public static bool TryParse(string? input, out PrecedenceCheckTodoWithoutFormat result)
+    {
+        result = new PrecedenceCheckTodoWithoutFormat(24);
+        return true;
+    }
+}
+
+public class ParsableTodo : IParsable<ParsableTodo>
+{
+    public int Id { get; set; }
+    public string? Name { get; set; } = "Todo";
+    public bool IsComplete { get; set; }
+    public static ParsableTodo Parse(string s, IFormatProvider? provider)
+    {
+        return new ParsableTodo();
+    }
+    public static bool TryParse(string? input, IFormatProvider? provider, out ParsableTodo result)
+    {
+        if (input == "1")
+        {
+            result = new ParsableTodo
+            {
+            Id = 1,
+            Name = "Knit kitten mittens.",
+            IsComplete = false
+            };
+            return true;
+        }
+        else
+        {
+        result = null!;
+        return false;
+        }
+    }
+}
+
+public record MyBindAsyncRecord(Uri Uri)
+{
+    public static ValueTask<MyBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
+    {
+        if (parameter.ParameterType != typeof(MyBindAsyncRecord))
+        {
+            throw new UnreachableException($"Unexpected parameter type: {parameter.ParameterType}");
+        }
+        if (parameter.Name != "myBindAsyncParam")
+        {
+            throw new UnreachableException("Unexpected parameter name");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(result: null);
+        }
+
+        return new(result: new(uri));
+    }
+
+    // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's
+    // no [FromRoute] or [FromQuery] attributes.
+    public static bool TryParse(string? value, out MyBindAsyncRecord? result) =>
+        throw new NotImplementedException();
+}
+
+public record struct MyNullableBindAsyncStruct(Uri Uri)
+{
+    public static ValueTask<MyNullableBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter)
+    {
+        if (parameter.ParameterType != typeof(MyNullableBindAsyncStruct) && parameter.ParameterType != typeof(MyNullableBindAsyncStruct?))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+        if (parameter.Name != "myBindAsyncParam")
+        {
+            throw new UnreachableException("Unexpected parameter name");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(result: null);
+        }
+
+        return new(result: new(uri));
+    }
+
+    public static bool TryParse(string? value, out MyNullableBindAsyncStruct? result) =>
+        throw new NotImplementedException();
+}
+
+public record struct MyBindAsyncStruct(Uri Uri)
+{
+    public static ValueTask<MyBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
+    {
+        if (parameter.ParameterType != typeof(MyBindAsyncStruct) && parameter.ParameterType != typeof(MyBindAsyncStruct?))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+        if (parameter.Name != "myBindAsyncParam")
+        {
+            throw new UnreachableException("Unexpected parameter name");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            throw new BadHttpRequestException("The request is missing the required Referer header.");
+        }
+
+        return new(result: new(uri));
+    }
+
+    // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's
+    // no [FromRoute] or [FromQuery] attributes.
+    public static bool TryParse(string? value, out MyBindAsyncStruct result) =>
+        throw new NotImplementedException();
+}
+
+public record struct MyBothBindAsyncStruct(Uri Uri)
+{
+    public static ValueTask<MyBothBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
+    {
+        if (parameter.ParameterType != typeof(MyBothBindAsyncStruct) && parameter.ParameterType != typeof(MyBothBindAsyncStruct?))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+        if (parameter.Name != "myBindAsyncParam")
+        {
+            throw new UnreachableException("Unexpected parameter name");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            throw new BadHttpRequestException("The request is missing the required Referer header.");
+        }
+
+        return new(result: new(uri));
+    }
+
+    // BindAsync with ParameterInfo is preferred
+    public static ValueTask<MyBothBindAsyncStruct> BindAsync(HttpContext context) =>
+        throw new NotImplementedException();
+}
+
+public record struct MySimpleBindAsyncStruct(Uri Uri)
+{
+    public static ValueTask<MySimpleBindAsyncStruct> BindAsync(HttpContext context)
+    {
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            throw new BadHttpRequestException("The request is missing the required Referer header.");
+        }
+
+        return new(result: new(uri));
+    }
+
+    public static bool TryParse(string? value, out MySimpleBindAsyncStruct result) =>
+        throw new NotImplementedException();
+}
+
+public record MySimpleBindAsyncRecord(Uri Uri)
+{
+    public static ValueTask<MySimpleBindAsyncRecord?> BindAsync(HttpContext context)
+    {
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(result: null);
+        }
+
+        return new(result: new(uri));
+    }
+
+    public static bool TryParse(string? value, out MySimpleBindAsyncRecord? result) =>
+        throw new NotImplementedException();
+}
+
+public interface IBindAsync<T>
+{
+    static ValueTask<T?> BindAsync(HttpContext context)
+    {
+        if (typeof(T) != typeof(MyBindAsyncFromInterfaceRecord))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(default(T));
+        }
+
+        return new(result: (T)(object)new MyBindAsyncFromInterfaceRecord(uri));
+    }
+}
+
+public class BindAsyncWrongType
+{
+    public static ValueTask<MyBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter) =>
+        throw new UnreachableException("We shouldn't bind from the wrong type!");
+}
+
+public record MyBindAsyncFromInterfaceRecord(Uri Uri) : IBindAsync<MyBindAsyncFromInterfaceRecord>
+{
+}
+
+public interface IHaveUri
+{
+    Uri? Uri { get; set; }
+}
+
+public class BaseBindAsync<T> where T : IHaveUri, new()
+{
+    public static ValueTask<T?> BindAsync(HttpContext context)
+    {
+        if (typeof(T) != typeof(InheritBindAsync))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(default(T));
+        }
+
+        return new(result: new() { Uri = uri });
+    }
+}
+
+public class InheritBindAsync : BaseBindAsync<InheritBindAsync>, IHaveUri
+{
+    public Uri? Uri { get; set; }
+}
+
+// Using wrong T on purpose
+public class InheritBindAsyncWrongType : BaseBindAsync<InheritBindAsync>
+{
+}
+
+public class BindAsyncFromImplicitStaticAbstractInterface : IBindableFromHttpContext<BindAsyncFromImplicitStaticAbstractInterface>
+{
+    public Uri? Uri { get; init; }
+
+    public static ValueTask<BindAsyncFromImplicitStaticAbstractInterface?> BindAsync(HttpContext context, ParameterInfo parameter)
+    {
+        if (parameter.ParameterType != typeof(BindAsyncFromImplicitStaticAbstractInterface))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+        if (parameter.Name != "myBindAsyncParam")
+        {
+            throw new UnreachableException("Unexpected parameter name");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(result: null);
+        }
+
+        return new(result: new() { Uri = uri });
+    }
+}
+
+public class BindAsyncFromExplicitStaticAbstractInterface : IBindableFromHttpContext<BindAsyncFromExplicitStaticAbstractInterface>
+{
+    public Uri? Uri { get; init; }
+
+    static ValueTask<BindAsyncFromExplicitStaticAbstractInterface?> IBindableFromHttpContext<BindAsyncFromExplicitStaticAbstractInterface>.BindAsync(HttpContext context, ParameterInfo parameter)
+    {
+        if (parameter.ParameterType != typeof(BindAsyncFromExplicitStaticAbstractInterface))
+        {
+            throw new UnreachableException("Unexpected parameter type");
+        }
+        if (parameter.Name != "myBindAsyncParam")
+        {
+            throw new UnreachableException("Unexpected parameter name");
+        }
+
+        if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+        {
+            return new(result: null);
+        }
+
+        return new(result: new() { Uri = uri });
+    }
+}
+
+public class BindAsyncFromStaticAbstractInterfaceWrongType : IBindableFromHttpContext<BindAsyncFromImplicitStaticAbstractInterface>
+{
+    public static ValueTask<BindAsyncFromImplicitStaticAbstractInterface?> BindAsync(HttpContext context, ParameterInfo parameter) =>
+        throw new UnreachableException("We shouldn't bind from the wrong interface type!");
+}
+
+public class MyBindAsyncTypeThatThrows
+{
+    public static ValueTask<MyBindAsyncTypeThatThrows?> BindAsync(HttpContext context, ParameterInfo parameter) =>
+        throw new InvalidOperationException("BindAsync failed");
+}
+
+#nullable restore

+ 0 - 14
src/Shared/RoslynUtils/Bindability.cs

@@ -1,14 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
-internal enum Bindability
-{
-    Bindable,
-    NotBindable,
-    InvalidReturnType
-}

+ 0 - 5
src/Shared/RoslynUtils/Parsability.cs

@@ -6,8 +6,3 @@ using System.Collections.Generic;
 using System.Text;
 
 namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
-internal enum Parsability
-{
-    Parsable,
-    NotParsable
-}

+ 66 - 21
src/Shared/RoslynUtils/ParsabilityHelper.cs

@@ -17,7 +17,7 @@ using WellKnownType = WellKnownTypeData.WellKnownType;
 
 internal static class ParsabilityHelper
 {
-    private static bool IsTypeAlwaysParsableOrBindable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out ParsabilityMethod? parsabilityMethod)
+    private static bool IsTypeAlwaysParsable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out ParsabilityMethod? parsabilityMethod)
     {
         // Any enum is valid.
         if (typeSymbol.TypeKind == TypeKind.Enum)
@@ -49,9 +49,9 @@ internal static class ParsabilityHelper
         return GetParsability(typeSymbol, wellKnownTypes, out var _);
     }
 
-    internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, out ParsabilityMethod? parsabilityMethod)
+    internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(false)] out ParsabilityMethod? parsabilityMethod)
     {
-        if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes, out parsabilityMethod))
+        if (IsTypeAlwaysParsable(typeSymbol, wellKnownTypes, out parsabilityMethod))
         {
             return Parsability.Parsable;
         }
@@ -64,7 +64,9 @@ internal static class ParsabilityHelper
         }
 
         // Check if the parameter type has a public static TryParse method.
-        var tryParseMethods = typeSymbol.GetMembers("TryParse").OfType<IMethodSymbol>();
+        var tryParseMethods = typeSymbol.GetThisAndBaseTypes()
+            .SelectMany(t => t.GetMembers("TryParse"))
+            .OfType<IMethodSymbol>();
 
         if (tryParseMethods.Any(m => IsTryParseWithFormat(m, wellKnownTypes)))
         {
@@ -114,13 +116,14 @@ internal static class ParsabilityHelper
     private static bool IsBindableViaIBindableFromHttpContext(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
     {
         var iBindableFromHttpContextTypeSymbol = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_IBindableFromHttpContext_T);
-        var implementsIBindableFromHttpContext = typeSymbol.AllInterfaces.Any(
+        var constructedTypeSymbol = typeSymbol.AllInterfaces.FirstOrDefault(
             i => SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, iBindableFromHttpContextTypeSymbol)
             );
-        return implementsIBindableFromHttpContext;
+        return constructedTypeSymbol != null &&
+            SymbolEqualityComparer.Default.Equals(constructedTypeSymbol.TypeArguments[0].UnwrapTypeSymbol(), typeSymbol);
     }
 
-    private static bool IsBindAsync(IMethodSymbol methodSymbol, INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
+    private static bool IsBindAsync(IMethodSymbol methodSymbol, ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
     {
         return methodSymbol.DeclaredAccessibility == Accessibility.Public &&
             methodSymbol.IsStatic &&
@@ -131,7 +134,7 @@ internal static class ParsabilityHelper
             SymbolEqualityComparer.Default.Equals(returnType.TypeArguments[0], typeSymbol);
     }
 
-    private static bool IsBindAsyncWithParameter(IMethodSymbol methodSymbol, INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
+    private static bool IsBindAsyncWithParameter(IMethodSymbol methodSymbol, ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
     {
         return methodSymbol.DeclaredAccessibility == Accessibility.Public &&
             methodSymbol.IsStatic &&
@@ -139,49 +142,91 @@ internal static class ParsabilityHelper
             SymbolEqualityComparer.Default.Equals(methodSymbol.Parameters[0].Type, wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) &&
             SymbolEqualityComparer.Default.Equals(methodSymbol.Parameters[1].Type, wellKnownTypes.Get(WellKnownType.System_Reflection_ParameterInfo)) &&
             methodSymbol.ReturnType is INamedTypeSymbol returnType &&
-            IsReturningValueTaskOfT(returnType, typeSymbol, wellKnownTypes);
+            IsReturningValueTaskOfTOrNullableT(returnType, typeSymbol, wellKnownTypes);
     }
 
-    private static bool IsReturningValueTaskOfT(INamedTypeSymbol returnType, INamedTypeSymbol containingType, WellKnownTypes wellKnownTypes)
+    private static bool IsReturningValueTaskOfTOrNullableT(INamedTypeSymbol returnType, ITypeSymbol containingType, WellKnownTypes wellKnownTypes)
     {
         return SymbolEqualityComparer.Default.Equals(returnType.ConstructedFrom, wellKnownTypes.Get(WellKnownType.System_Threading_Tasks_ValueTask_T)) &&
-            SymbolEqualityComparer.Default.Equals(returnType.TypeArguments[0], containingType);
+            SymbolEqualityComparer.Default.Equals(returnType.TypeArguments[0].UnwrapTypeSymbol(), containingType);
     }
 
-    internal static Bindability GetBindability(INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
+    internal static Bindability GetBindability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, out BindabilityMethod? bindabilityMethod)
     {
-        if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes, out var _))
-        {
-            return Bindability.Bindable;
-        }
+        bindabilityMethod = null;
 
         if (IsBindableViaIBindableFromHttpContext(typeSymbol, wellKnownTypes))
         {
+            bindabilityMethod = BindabilityMethod.IBindableFromHttpContext;
             return Bindability.Bindable;
         }
 
-        var bindAsyncMethods = typeSymbol.GetMembers("BindAsync").OfType<IMethodSymbol>();
+        // TODO: Search interfaces too. See MyBindAsyncFromInterfaceRecord test as an example.
+        // It's easy to find, but we need to flow the interface back to the emitter to call it.
+        // With parent types, we can continue to pretend we're calling a method directly on the child.
+        var bindAsyncMethods = typeSymbol.GetThisAndBaseTypes()
+            .SelectMany(t => t.GetMembers("BindAsync"))
+            .OfType<IMethodSymbol>();
+
         foreach (var methodSymbol in bindAsyncMethods)
         {
-            if (IsBindAsync(methodSymbol, typeSymbol, wellKnownTypes) || IsBindAsyncWithParameter(methodSymbol, typeSymbol, wellKnownTypes))
+            if (IsBindAsyncWithParameter(methodSymbol, typeSymbol, wellKnownTypes))
+            {
+                bindabilityMethod = BindabilityMethod.BindAsyncWithParameter;
+                break;
+            }
+            if (IsBindAsync(methodSymbol, typeSymbol, wellKnownTypes))
             {
-                return Bindability.Bindable;
+                bindabilityMethod = BindabilityMethod.BindAsync;
             }
         }
 
+        if (bindabilityMethod is not null)
+        {
+            return Bindability.Bindable;
+        }
+
         // See if we can give better guidance on why the BindAsync method is no good.
         if (bindAsyncMethods.Count() == 1)
         {
             var bindAsyncMethod = bindAsyncMethods.Single();
 
-            if (bindAsyncMethod.ReturnType is INamedTypeSymbol returnType && !IsReturningValueTaskOfT(returnType, typeSymbol, wellKnownTypes))
+            if (bindAsyncMethod.ReturnType is INamedTypeSymbol returnType && !IsReturningValueTaskOfTOrNullableT(returnType, typeSymbol, wellKnownTypes))
             {
                 return Bindability.InvalidReturnType;
             }
-
         }
 
         return Bindability.NotBindable;
     }
 }
 
+internal enum Parsability
+{
+    Parsable,
+    NotParsable,
+}
+
+internal enum ParsabilityMethod
+{
+    String,
+    IParsable,
+    Enum,
+    TryParse,
+    TryParseWithFormatProvider,
+    Uri,
+}
+
+internal enum Bindability
+{
+    Bindable,
+    NotBindable,
+    InvalidReturnType,
+}
+
+internal enum BindabilityMethod
+{
+    IBindableFromHttpContext,
+    BindAsync,
+    BindAsyncWithParameter,
+}

+ 0 - 14
src/Shared/RoslynUtils/ParsabilityMethod.cs

@@ -1,14 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
-
-internal enum ParsabilityMethod
-{
-    String,
-    IParsable,
-    Enum,
-    TryParse,
-    TryParseWithFormatProvider,
-    Uri
-}

+ 15 - 4
src/Shared/RoslynUtils/SymbolExtensions.cs

@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
@@ -11,12 +12,12 @@ namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
 
 internal static class SymbolExtensions
 {
-    public static INamedTypeSymbol? UnwrapTypeSymbol(this ITypeSymbol typeSymbol)
+    public static ITypeSymbol UnwrapTypeSymbol(this ITypeSymbol typeSymbol, bool unwrapArray = false)
     {
         INamedTypeSymbol? unwrappedTypeSymbol = null;
 
-        // If it is an array, unwrap it.
-        if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
+        // If it is an array, and unwrapArray = true, unwrap it before unwrapping nullable.
+        if (unwrapArray && typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
         {
             unwrappedTypeSymbol = arrayTypeSymbol.ElementType as INamedTypeSymbol;
         }
@@ -31,7 +32,17 @@ internal static class SymbolExtensions
             unwrappedTypeSymbol = unwrappedTypeSymbol.TypeArguments[0] as INamedTypeSymbol;
         }
 
-        return unwrappedTypeSymbol;
+        return unwrappedTypeSymbol ?? typeSymbol;
+    }
+
+    public static IEnumerable<ITypeSymbol> GetThisAndBaseTypes(this ITypeSymbol? type)
+    {
+        var current = type;
+        while (current != null)
+        {
+            yield return current;
+            current = current.BaseType;
+        }
     }
 
     public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)