Browse Source

TryParse support for request delegate source generator (#46696)

TryParse support for explicit from query scenarios.
Mitch Denny 3 years ago
parent
commit
446e27e0fd
27 changed files with 5506 additions and 105 deletions
  1. 4 0
      src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj
  2. 4 0
      src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj
  3. 1 0
      src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs
  4. 96 73
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs
  5. 96 0
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs
  6. 2 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt
  7. 2 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt
  8. 2 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt
  9. 10 5
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt
  10. 2 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt
  11. 1625 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt
  12. 1625 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt
  13. 6 3
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt
  14. 6 3
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt
  15. 6 3
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt
  16. 6 3
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt
  17. 1625 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt
  18. 2 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt
  19. 2 1
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt
  20. 109 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs
  21. 209 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs
  22. 0 0
      src/Shared/RoslynUtils/Bindability.cs
  23. 0 0
      src/Shared/RoslynUtils/Parsability.cs
  24. 25 9
      src/Shared/RoslynUtils/ParsabilityHelper.cs
  25. 14 0
      src/Shared/RoslynUtils/ParsabilityMethod.cs
  26. 23 0
      src/Shared/RoslynUtils/SymbolExtensions.cs
  27. 4 0
      src/Shared/RoslynUtils/WellKnownTypeData.cs

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

@@ -27,6 +27,10 @@
     <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"/>
   </ItemGroup>
 
 </Project>

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

@@ -24,6 +24,10 @@
     <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"/>
   </ItemGroup>
 
 </Project>

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

@@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;

+ 96 - 73
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Text;
+using Microsoft.CodeAnalysis;
 
 namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
 internal static class EndpointParameterEmitter
@@ -35,7 +36,7 @@ internal static class EndpointParameterEmitter
         if (endpointParameter.IsOptional)
         {
             builder.AppendLine($$"""
-                        var {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.EmitAssigningCodeResult()}}.Count > 0 ? {{endpointParameter.EmitAssigningCodeResult()}}.ToString() : null;
+                        var {{endpointParameter.Name}}_temp = {{endpointParameter.EmitAssigningCodeResult()}}.Count > 0 ? {{endpointParameter.Name}}_raw.ToString() : null;
 """);
         }
         else
@@ -45,144 +46,166 @@ internal static class EndpointParameterEmitter
                         {
                             wasParamCheckFailure = true;
                         }
-                        var {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.EmitAssigningCodeResult()}}.ToString();
+                        var {{endpointParameter.Name}}_temp = {{endpointParameter.EmitAssigningCodeResult()}}.ToString();
 """);
         }
 
+        builder.Append(endpointParameter.EmitParsingBlock());
+
         return builder.ToString();
     }
 
-    internal static string EmitRouteParameterPreparation(this EndpointParameter endpointParameter)
+    internal static string EmitParsingBlock(this EndpointParameter endpointParameter)
     {
         var builder = new StringBuilder();
-        builder.AppendLine($"""
+
+        if (endpointParameter.IsParsable)
+        {
+            var parsingBlock = endpointParameter.ParsingBlockEmitter($"{endpointParameter.Name}_temp", $"{endpointParameter.Name}_parsed_temp");
+            builder.AppendLine($$"""
+{{parsingBlock}}
+                        {{endpointParameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}} {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.Name}}_parsed_temp!;
+""");
+
+        }
+        else
+        {
+            builder.AppendLine($$"""
+                        {{endpointParameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}} {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.Name}}_temp!;
+""");
+
+        }
+
+        return builder.ToString();
+    }
+
+        internal static string EmitRouteParameterPreparation(this EndpointParameter endpointParameter)
+        {
+            var builder = new StringBuilder();
+            builder.AppendLine($"""
                         {endpointParameter.EmitParameterDiagnosticComment()}
 """);
 
-        // Throw an exception of if the route parameter name that was specific in the `FromRoute`
-        // attribute or in the parameter name does not appear in the actual route.
-        builder.AppendLine($$"""
+            // Throw an exception of if the route parameter name that was specific in the `FromRoute`
+            // attribute or in the parameter name does not appear in the actual route.
+            builder.AppendLine($$"""
                         if (options?.RouteParameterNames?.Contains("{{endpointParameter.Name}}", StringComparer.OrdinalIgnoreCase) != true)
                         {
                             throw new InvalidOperationException($"'{{endpointParameter.Name}}' is not a route parameter.");
                         }
 """);
 
-        var assigningCode = $"httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]?.ToString()";
-        builder.AppendLine($$"""
+            var assigningCode = $"httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]?.ToString()";
+            builder.AppendLine($$"""
                         var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}};
 """);
 
-        if (!endpointParameter.IsOptional)
-        {
-            builder.AppendLine($$"""
+            if (!endpointParameter.IsOptional)
+            {
+                builder.AppendLine($$"""
                         if ({{endpointParameter.EmitAssigningCodeResult()}} == null)
                         {
                             wasParamCheckFailure = true;
                         }
 """);
-        }
-        builder.AppendLine($"""
+            }
+            builder.AppendLine($"""
                         var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()};
 """);
 
-        return builder.ToString();
-    }
+            return builder.ToString();
+        }
 
-    internal static string EmitRouteOrQueryParameterPreparation(this EndpointParameter endpointParameter)
-    {
-        var builder = new StringBuilder();
-        builder.AppendLine($"""
+        internal static string EmitRouteOrQueryParameterPreparation(this EndpointParameter endpointParameter)
+        {
+            var builder = new StringBuilder();
+            builder.AppendLine($"""
                         {endpointParameter.EmitParameterDiagnosticComment()}
 """);
 
-        var parameterName = endpointParameter.Name;
-        var assigningCode = $@"options?.RouteParameterNames?.Contains(""{parameterName}"", StringComparer.OrdinalIgnoreCase) == true";
-        assigningCode += $@"? new StringValues(httpContext.Request.RouteValues[$""{parameterName}""]?.ToString())";
-        assigningCode += $@": httpContext.Request.Query[$""{parameterName}""];";
+            var parameterName = endpointParameter.Name;
+            var assigningCode = $@"options?.RouteParameterNames?.Contains(""{parameterName}"", StringComparer.OrdinalIgnoreCase) == true";
+            assigningCode += $@"? new StringValues(httpContext.Request.RouteValues[$""{parameterName}""]?.ToString())";
+            assigningCode += $@": httpContext.Request.Query[$""{parameterName}""];";
 
-        builder.AppendLine($$"""
+            builder.AppendLine($$"""
                         var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}};
 """);
 
-        if (!endpointParameter.IsOptional)
-        {
-            builder.AppendLine($$"""
+            if (!endpointParameter.IsOptional)
+            {
+                builder.AppendLine($$"""
                         if ({{endpointParameter.EmitAssigningCodeResult()}} is StringValues { Count: 0 })
                         {
                             wasParamCheckFailure = true;
                         }
 """);
-        }
+            }
 
-        builder.AppendLine($"""
+            builder.AppendLine($"""
                         var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()};
 """);
 
-        return builder.ToString();
-    }
+            return builder.ToString();
+        }
 
-    internal static string EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter)
-    {
-        var builder = new StringBuilder();
-        builder.AppendLine($"""
+        internal static string EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter)
+        {
+            var builder = new StringBuilder();
+            builder.AppendLine($"""
                         {endpointParameter.EmitParameterDiagnosticComment()}
 """);
 
-        var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})";
-        builder.AppendLine($$"""
+            var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})";
+            builder.AppendLine($$"""
                         var (isSuccessful, {{endpointParameter.EmitHandlerArgument()}}) = {{assigningCode}};
 """);
 
-        // If binding from the JSON body fails, we exit early. Don't
-        // set the status code here because assume it has been set by the
-        // TryResolveBody method.
-        builder.AppendLine("""
+            // If binding from the JSON body fails, we exit early. Don't
+            // set the status code here because assume it has been set by the
+            // TryResolveBody method.
+            builder.AppendLine("""
                         if (!isSuccessful)
                         {
                             return;
                         }
 """);
 
-        return builder.ToString();
-    }
+            return builder.ToString();
+        }
 
-    internal static string EmitServiceParameterPreparation(this EndpointParameter endpointParameter)
-    {
-        var builder = new StringBuilder();
+        internal static string EmitServiceParameterPreparation(this EndpointParameter endpointParameter)
+        {
+            var builder = new StringBuilder();
 
-        // Preamble for diagnostics purposes.
-        builder.AppendLine($"""
+            // Preamble for diagnostics purposes.
+            builder.AppendLine($"""
                         {endpointParameter.EmitParameterDiagnosticComment()}
 """);
 
-        // Requiredness checks for services are handled by the distinction
-        // between GetRequiredService and GetService in the assigningCode.
-        // Unlike other scenarios, this will result in an exception being thrown
-        // at runtime.
-        var assigningCode = endpointParameter.IsOptional ?
-            $"httpContext.RequestServices.GetService<{endpointParameter.Type}>();" :
-            $"httpContext.RequestServices.GetRequiredService<{endpointParameter.Type}>()";
+            // Requiredness checks for services are handled by the distinction
+            // between GetRequiredService and GetService in the assigningCode.
+            // Unlike other scenarios, this will result in an exception being thrown
+            // at runtime.
+            var assigningCode = endpointParameter.IsOptional ?
+                $"httpContext.RequestServices.GetService<{endpointParameter.Type}>();" :
+                $"httpContext.RequestServices.GetRequiredService<{endpointParameter.Type}>()";
 
-        builder.AppendLine($$"""
+            builder.AppendLine($$"""
                         var {{endpointParameter.EmitHandlerArgument()}} = {{assigningCode}};
 """);
 
-        return builder.ToString();
-    }
-
-    private static string EmitParameterDiagnosticComment(this EndpointParameter endpointParameter) =>
-        $"// Endpoint Parameter: {endpointParameter.Name} (Type = {endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}, IsOptional = {endpointParameter.IsOptional}, Source = {endpointParameter.Source})";
+            return builder.ToString();
+        }
 
-    private static string EmitHandlerArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_local";
-    private static string EmitAssigningCodeResult(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_raw";
+        private static string EmitParameterDiagnosticComment(this EndpointParameter endpointParameter) => $"// Endpoint Parameter: {endpointParameter.Name} (Type = {endpointParameter.Type}, IsOptional = {endpointParameter.IsOptional}, IsParsable = {endpointParameter.IsParsable}, Source = {endpointParameter.Source})";
+        private static string EmitHandlerArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_local";
+        private static string EmitAssigningCodeResult(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_raw";
 
-    public static string EmitArgument(this EndpointParameter endpointParameter) => endpointParameter.Source switch
-    {
-        EndpointParameterSource.JsonBody or EndpointParameterSource.Route or EndpointParameterSource.RouteOrQuery => endpointParameter.IsOptional
-            ? endpointParameter.EmitHandlerArgument()
-            : $"{endpointParameter.EmitHandlerArgument()}!",
-        EndpointParameterSource.Unknown => throw new Exception("Unreachable!"),
-        _ => endpointParameter.EmitHandlerArgument()
-    };
-}
+        public static string EmitArgument(this EndpointParameter endpointParameter) => endpointParameter.Source switch
+        {
+            EndpointParameterSource.JsonBody or EndpointParameterSource.Route or EndpointParameterSource.RouteOrQuery => endpointParameter.IsOptional ? endpointParameter.EmitHandlerArgument() : $"{endpointParameter.EmitHandlerArgument()}!",
+            EndpointParameterSource.Unknown => throw new Exception("Unreachable!"),
+            _ => endpointParameter.EmitHandlerArgument()
+        };
+    }

+ 96 - 0
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs

@@ -7,6 +7,9 @@ using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
 using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.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;
 
@@ -34,6 +37,9 @@ internal class EndpointParameter
             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))
         {
@@ -68,6 +74,93 @@ internal class EndpointParameter
         }
     }
 
+    private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)]out Func<string, 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
+        // 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)
+        {
+            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 method was encountered.
+        Func<string, string, string>? preferredTryParseInvocation = parsabilityMethod switch
+        {
+            ParsabilityMethod.IParsable => (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, CultureInfo.InvariantCulture, out var {{outputArgument}})""",
+            ParsabilityMethod.TryParseWithFormatProvider => (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, CultureInfo.InvariantCulture, out var {{outputArgument}})""",
+            ParsabilityMethod.TryParse => (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, out var {{outputArgument}})""",
+            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
+        };
+
+        // Special case handling for specific types
+        if (parameterType.SpecialType == SpecialType.System_Char)
+        {
+            preferredTryParseInvocation = (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, out var {{outputArgument}})""";
+        }
+        else if (parameterType.SpecialType == SpecialType.System_DateTime)
+        {
+            preferredTryParseInvocation = (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces, out var {{outputArgument}})""";
+        }
+        else if (SymbolEqualityComparer.Default.Equals(parameterType, wellKnownTypes.Get(WellKnownType.System_DateTimeOffset)))
+        {
+            preferredTryParseInvocation = (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowWhiteSpaces, out var {{outputArgument}})""";
+        }
+        else if (SymbolEqualityComparer.Default.Equals(parameterType, wellKnownTypes.Get(WellKnownType.System_DateOnly)))
+        {
+            preferredTryParseInvocation = (string inputArgument, string outputArgument) => $$"""{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}.TryParse({{inputArgument}}, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var {{outputArgument}})""";
+        }
+
+        // ... so for strings (null) we bail.
+        if (preferredTryParseInvocation == null)
+        {
+            parsingBlockEmitter = null;
+            return false;
+        }
+
+        if (IsOptional)
+        {
+            parsingBlockEmitter = (inputArgument, outputArgument) => $$"""
+                        {{parameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}} {{outputArgument}} = default;
+                        if ({{preferredTryParseInvocation(inputArgument, $"{inputArgument}_parsed_non_nullable")}})
+                        {
+                            {{outputArgument}} = {{$"{inputArgument}_parsed_non_nullable"}};
+                        }
+                        else if (string.IsNullOrEmpty({{inputArgument}}))
+                        {
+                            {{outputArgument}} = null;
+                        }
+                        else
+                        {
+                            wasParamCheckFailure = true;
+                        }
+""";
+        }
+        else
+        {
+            parsingBlockEmitter = (inputArgument, outputArgument) => $$"""
+                        if (!{{preferredTryParseInvocation(inputArgument, outputArgument)}})
+                        {
+                            wasParamCheckFailure = true;
+                        }
+""";
+        }
+
+        // Wrap the TryParse method call in an if-block and if it doesn't work set param check failure.
+        return true;
+
+    }
+
     public ITypeSymbol Type { get; }
     public EndpointParameterSource Source { get; }
 
@@ -76,6 +169,9 @@ internal class EndpointParameter
     internal string? AssigningCode { get; set; }
     public string Name { get; }
     public bool IsOptional { get; }
+    [MemberNotNull("ParsingBlockEmitter")]
+    public bool IsParsable { get; }
+    public Func<string, 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)

+ 2 - 1
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -1676,4 +1677,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 2 - 1
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt

@@ -93,6 +93,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -1755,4 +1756,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

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

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -1599,4 +1600,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 10 - 5
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -119,7 +120,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p1_local = p1_raw.ToString();
+                        var p1_temp = p1_raw.ToString();
+                        var p1_local = p1_temp;
 
                         // Endpoint Parameter: p2 (Type = global::System.String, IsOptional = False, Source = Query)
                         var p2_raw = httpContext.Request.Query["p2"];
@@ -127,7 +129,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p2_local = p2_raw.ToString();
+                        var p2_temp = p2_raw.ToString();
+                        var p2_local = p2_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -147,7 +150,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p1_local = p1_raw.ToString();
+                        var p1_temp = p1_raw.ToString();
+                        var p1_local = p1_temp;
 
                         // Endpoint Parameter: p2 (Type = global::System.String, IsOptional = False, Source = Query)
                         var p2_raw = httpContext.Request.Query["p2"];
@@ -155,7 +159,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p2_local = p2_raw.ToString();
+                        var p2_temp = p2_raw.ToString();
+                        var p2_local = p2_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -1627,4 +1632,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

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

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -1597,4 +1598,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 1625 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt

@@ -0,0 +1,1625 @@
+//------------------------------------------------------------------------------
+// <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::TestMapActions.Todo, 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.Globalization;
+    using System.Linq;
+    using System.Reflection;
+    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.Metadata;
+    using Microsoft.Extensions.DependencyInjection;
+    using Microsoft.Extensions.FileProviders;
+    using Microsoft.Extensions.Primitives;
+
+    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", 16)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::TestMapActions.Todo, 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<TestMapActions.Todo>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        // Endpoint Parameter: p (Type = TestMapActions.Todo, IsOptional = False, Source = Query)
+                        var p_raw = httpContext.Request.Query["p"];
+                        if (StringValues.IsNullOrEmpty(p_raw))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_temp = p_raw.ToString();
+                        if (!global::TestMapActions.Todo.TryParse(p_temp, out var p_parsed_temp))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_local = p_parsed_temp!;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        // Endpoint Parameter: p (Type = TestMapActions.Todo, IsOptional = False, Source = Query)
+                        var p_raw = httpContext.Request.Query["p"];
+                        if (StringValues.IsNullOrEmpty(p_raw))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_temp = p_raw.ToString();
+                        if (!global::TestMapActions.Todo.TryParse(p_temp, out var p_parsed_temp))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_local = p_parsed_temp!;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(new EndpointFilterInvocationContext<TestMapActions.Todo>(httpContext, p_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 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);
+        }
+    }
+
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+
+        public int Count => 1;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+
+        public int Count => 2;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+
+        public int Count => 3;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+
+        public int Count => 4;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+
+        public int Count => 5;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+
+        public int Count => 6;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+
+        public int Count => 7;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+
+        public int Count => 8;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7, T8> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+            Arg8 = arg8;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                8 => Arg8,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                   case 8:
+                        Arg8 = (T8)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+        public T8 Arg8 { get; set; }
+
+        public int Count => 9;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               8 => (T)(object)Arg8!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+            Arg8 = arg8;
+            Arg9 = arg9;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                8 => Arg8,
+                9 => Arg9,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                   case 8:
+                        Arg8 = (T8)(object?)value!;
+                        break;
+                   case 9:
+                        Arg9 = (T9)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+        public T8 Arg8 { get; set; }
+        public T9 Arg9 { get; set; }
+
+        public int Count => 10;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               8 => (T)(object)Arg8!,
+               9 => (T)(object)Arg9!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}

+ 1625 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt

@@ -0,0 +1,1625 @@
+//------------------------------------------------------------------------------
+// <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::TestMapActions.TodoStatus, 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.Globalization;
+    using System.Linq;
+    using System.Reflection;
+    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.Metadata;
+    using Microsoft.Extensions.DependencyInjection;
+    using Microsoft.Extensions.FileProviders;
+    using Microsoft.Extensions.Primitives;
+
+    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", 16)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::TestMapActions.TodoStatus, 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<TestMapActions.TodoStatus>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        // Endpoint Parameter: p (Type = TestMapActions.TodoStatus, IsOptional = False, 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::TestMapActions.TodoStatus>(p_temp, out var p_parsed_temp))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_local = p_parsed_temp!;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        // Endpoint Parameter: p (Type = TestMapActions.TodoStatus, IsOptional = False, 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::TestMapActions.TodoStatus>(p_temp, out var p_parsed_temp))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_local = p_parsed_temp!;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(new EndpointFilterInvocationContext<TestMapActions.TodoStatus>(httpContext, p_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 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);
+        }
+    }
+
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+
+        public int Count => 1;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+
+        public int Count => 2;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+
+        public int Count => 3;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+
+        public int Count => 4;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+
+        public int Count => 5;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+
+        public int Count => 6;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+
+        public int Count => 7;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+
+        public int Count => 8;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7, T8> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+            Arg8 = arg8;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                8 => Arg8,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                   case 8:
+                        Arg8 = (T8)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+        public T8 Arg8 { get; set; }
+
+        public int Count => 9;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               8 => (T)(object)Arg8!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+            Arg8 = arg8;
+            Arg9 = arg9;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                8 => Arg8,
+                9 => Arg9,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                   case 8:
+                        Arg8 = (T8)(object?)value!;
+                        break;
+                   case 9:
+                        Arg9 = (T9)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+        public T8 Arg8 { get; set; }
+        public T9 Arg9 { get; set; }
+
+        public int Count => 10;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               8 => (T)(object)Arg8!,
+               9 => (T)(object)Arg9!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}

+ 6 - 3
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -115,7 +116,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = global::System.String?, IsOptional = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_local = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -131,7 +133,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = global::System.String?, IsOptional = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_local = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -1603,4 +1606,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 6 - 3
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -115,7 +116,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_local = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -131,7 +133,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_local = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -1610,4 +1613,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 6 - 3
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -115,7 +116,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_local = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -131,7 +133,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         var wasParamCheckFailure = false;
                         // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query)
                         var p_raw = httpContext.Request.Query["p"];
-                        var p_local = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null;
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -1610,4 +1613,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 6 - 3
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt

@@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -119,7 +120,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_local = p_raw.ToString();
+                        var p_temp = p_raw.ToString();
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -139,7 +141,8 @@ namespace Microsoft.AspNetCore.Http.Generated
                         {
                             wasParamCheckFailure = true;
                         }
-                        var p_local = p_raw.ToString();
+                        var p_temp = p_raw.ToString();
+                        var p_local = p_temp;
 
                         if (wasParamCheckFailure)
                         {
@@ -1618,4 +1621,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 1625 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt

@@ -0,0 +1,1625 @@
+//------------------------------------------------------------------------------
+// <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::System.TimeOnly, 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.Globalization;
+    using System.Linq;
+    using System.Reflection;
+    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.Metadata;
+    using Microsoft.Extensions.DependencyInjection;
+    using Microsoft.Extensions.FileProviders;
+    using Microsoft.Extensions.Primitives;
+
+    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", 16)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (Func<global::System.TimeOnly, 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<System.TimeOnly>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        // Endpoint Parameter: p (Type = System.TimeOnly, IsOptional = False, Source = Query)
+                        var p_raw = httpContext.Request.Query["p"];
+                        if (StringValues.IsNullOrEmpty(p_raw))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_temp = p_raw.ToString();
+                        if (!global::System.TimeOnly.TryParse(p_temp, CultureInfo.InvariantCulture, out var p_parsed_temp))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_local = p_parsed_temp!;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var wasParamCheckFailure = false;
+                        // Endpoint Parameter: p (Type = System.TimeOnly, IsOptional = False, Source = Query)
+                        var p_raw = httpContext.Request.Query["p"];
+                        if (StringValues.IsNullOrEmpty(p_raw))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_temp = p_raw.ToString();
+                        if (!global::System.TimeOnly.TryParse(p_temp, CultureInfo.InvariantCulture, out var p_parsed_temp))
+                        {
+                            wasParamCheckFailure = true;
+                        }
+                        var p_local = p_parsed_temp!;
+
+                        if (wasParamCheckFailure)
+                        {
+                            httpContext.Response.StatusCode = 400;
+                        }
+                        var result = await filteredInvocation(new EndpointFilterInvocationContext<System.TimeOnly>(httpContext, p_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 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);
+        }
+    }
+
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+
+        public int Count => 1;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+
+        public int Count => 2;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+
+        public int Count => 3;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+
+        public int Count => 4;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+
+        public int Count => 5;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+
+        public int Count => 6;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+
+        public int Count => 7;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+
+        public int Count => 8;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7, T8> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+            Arg8 = arg8;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                8 => Arg8,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                   case 8:
+                        Arg8 = (T8)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+        public T8 Arg8 { get; set; }
+
+        public int Count => 9;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               8 => (T)(object)Arg8!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+    %GENERATEDCODEATTRIBUTE%
+    file class EndpointFilterInvocationContext<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> : EndpointFilterInvocationContext, IList<object?>
+    {
+        internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
+        {
+            HttpContext = httpContext;
+            Arg0 = arg0;
+            Arg1 = arg1;
+            Arg2 = arg2;
+            Arg3 = arg3;
+            Arg4 = arg4;
+            Arg5 = arg5;
+            Arg6 = arg6;
+            Arg7 = arg7;
+            Arg8 = arg8;
+            Arg9 = arg9;
+        }
+
+        public object? this[int index]
+        {
+            get => index switch
+            {
+                0 => Arg0,
+                1 => Arg1,
+                2 => Arg2,
+                3 => Arg3,
+                4 => Arg4,
+                5 => Arg5,
+                6 => Arg6,
+                7 => Arg7,
+                8 => Arg8,
+                9 => Arg9,
+                _ => new IndexOutOfRangeException()
+            };
+            set
+            {
+                switch (index)
+                {
+                   case 0:
+                        Arg0 = (T0)(object?)value!;
+                        break;
+                   case 1:
+                        Arg1 = (T1)(object?)value!;
+                        break;
+                   case 2:
+                        Arg2 = (T2)(object?)value!;
+                        break;
+                   case 3:
+                        Arg3 = (T3)(object?)value!;
+                        break;
+                   case 4:
+                        Arg4 = (T4)(object?)value!;
+                        break;
+                   case 5:
+                        Arg5 = (T5)(object?)value!;
+                        break;
+                   case 6:
+                        Arg6 = (T6)(object?)value!;
+                        break;
+                   case 7:
+                        Arg7 = (T7)(object?)value!;
+                        break;
+                   case 8:
+                        Arg8 = (T8)(object?)value!;
+                        break;
+                   case 9:
+                        Arg9 = (T9)(object?)value!;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        public override HttpContext HttpContext { get; }
+
+        public override IList<object?> Arguments => this;
+
+        public T0 Arg0 { get; set; }
+        public T1 Arg1 { get; set; }
+        public T2 Arg2 { get; set; }
+        public T3 Arg3 { get; set; }
+        public T4 Arg4 { get; set; }
+        public T5 Arg5 { get; set; }
+        public T6 Arg6 { get; set; }
+        public T7 Arg7 { get; set; }
+        public T8 Arg8 { get; set; }
+        public T9 Arg9 { get; set; }
+
+        public int Count => 10;
+
+        public bool IsReadOnly => false;
+
+        public bool IsFixedSize => true;
+
+        public void Add(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Contains(object? item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(object?[] array, int arrayIndex)
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                array[arrayIndex++] = Arguments[i];
+            }
+        }
+
+        public IEnumerator<object?> GetEnumerator()
+        {
+            for (int i = 0; i < Arguments.Count; i++)
+            {
+                yield return Arguments[i];
+            }
+        }
+
+        public override T GetArgument<T>(int index)
+        {
+            return index switch
+            {
+               0 => (T)(object)Arg0!,
+               1 => (T)(object)Arg1!,
+               2 => (T)(object)Arg2!,
+               3 => (T)(object)Arg3!,
+               4 => (T)(object)Arg4!,
+               5 => (T)(object)Arg5!,
+               6 => (T)(object)Arg6!,
+               7 => (T)(object)Arg7!,
+               8 => (T)(object)Arg8!,
+               9 => (T)(object)Arg9!,
+               _ => throw new IndexOutOfRangeException()
+            };
+        }
+
+        public int IndexOf(object? item)
+        {
+            return Arguments.IndexOf(item);
+        }
+
+        public void Insert(int index, object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(object? item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}

+ 2 - 1
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt

@@ -93,6 +93,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -1792,4 +1793,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 2 - 1
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt

@@ -93,6 +93,7 @@ namespace Microsoft.AspNetCore.Http.Generated
     using System.Collections.Generic;
     using System.Collections.ObjectModel;
     using System.Diagnostics;
+    using System.Globalization;
     using System.Linq;
     using System.Reflection;
     using System.Threading.Tasks;
@@ -1739,4 +1740,4 @@ namespace Microsoft.AspNetCore.Http.Generated
             return GetEnumerator();
         }
     }
-}
+}

+ 109 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs

@@ -190,6 +190,11 @@ public class RequestDelegateGeneratorTestBase : LoggedTest
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Numerics;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Net;
+using System.Net.Sockets;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
@@ -207,6 +212,109 @@ 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)
     {
@@ -248,6 +356,7 @@ public static class TestMapActions
         var baselineFilePath = Path.Combine("RequestDelegateGenerator", "Baselines", $"{callerName}.generated.txt");
         var generatedSyntaxTree = compilation.SyntaxTrees.Last();
         var generatedCode = await generatedSyntaxTree.GetTextAsync();
+        await File.WriteAllTextAsync(baselineFilePath, generatedCode.ToString());
         var baseline = await File.ReadAllTextAsync(baselineFilePath);
         var expectedLines = baseline
             .TrimEnd() // Trim newlines added by autoformat

+ 209 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs

@@ -2,13 +2,22 @@
 // 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.Numerics;
+using System.Reflection.Metadata;
+using System.Reflection;
 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;
 
 namespace Microsoft.AspNetCore.Http.Generators.Tests;
 
@@ -95,6 +104,206 @@ app.MapGet("/", getQueryWithDefault);
         await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode);
     }
 
+    [Fact]
+    public async Task MapAction_SingleTimeOnlyParam_StringReturn()
+    {
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]TimeOnly p) => p.ToString());
+""");
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        Assert.Equal("/hello", endpointModel.RoutePattern);
+        Assert.Equal("MapGet", endpointModel.HttpMethod);
+        var p = Assert.Single(endpointModel.Parameters);
+        Assert.Equal(EndpointParameterSource.Query, p.Source);
+        Assert.Equal("p", p.Name);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.QueryString = new QueryString("?p=13:30");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "1:30 PM");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    [Theory]
+    [InlineData("DateOnly", "2023-02-20")]
+    [InlineData("DateTime", "2023-02-20")]
+    [InlineData("DateTimeOffset", "2023-02-20")]
+    public async Task MapAction_SingleDateLikeParam_StringReturn(string parameterType, string result)
+    {
+        var (results, compilation) = await RunGeneratorAsync($$"""
+app.MapGet("/hello", ([FromQuery]{{parameterType}} p) => p.ToString("yyyy-MM-dd"));
+""");
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        Assert.Equal("/hello", endpointModel.RoutePattern);
+        Assert.Equal("MapGet", endpointModel.HttpMethod);
+        var p = Assert.Single(endpointModel.Parameters);
+        Assert.Equal(EndpointParameterSource.Query, p.Source);
+        Assert.Equal("p", p.Name);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.QueryString = new QueryString($"?p={result}");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, result);
+    }
+
+    public static object[][] TryParsableParameters
+    {
+        get
+        {
+            var now = DateTime.Now;
+
+            return new[]
+            {
+                    //// string is not technically "TryParsable", but it's the special case.
+                    new object[] { "string", "plain string", "plain string" },
+                    new object[] { "int", "-42", -42 },
+                    new object[] { "uint", "42", 42U },
+                    new object[] { "bool", "true", true },
+                    new object[] { "short", "-42", (short)-42 },
+                    new object[] { "ushort", "42", (ushort)42 },
+                    new object[] { "long", "-42", -42L },
+                    new object[] { "ulong", "42", 42UL },
+                    new object[] { "IntPtr", "-42", new IntPtr(-42) },
+                    new object[] { "char", "A", 'A' },
+                    new object[] { "double", "0.5", 0.5 },
+                    new object[] { "float", "0.5", 0.5f },
+                    new object[] { "Half", "0.5", (Half)0.5f },
+                    new object[] { "decimal", "0.5", 0.5m },
+                    new object[] { "Uri", "https://example.org", new Uri("https://example.org") },
+                    new object[] { "DateTime", now.ToString("o"), now.ToUniversalTime() },
+                    new object[] { "DateTimeOffset", "1970-01-01T00:00:00.0000000+00:00", DateTimeOffset.UnixEpoch },
+                    new object[] { "TimeSpan", "00:00:42", TimeSpan.FromSeconds(42) },
+                    new object[] { "Guid", "00000000-0000-0000-0000-000000000000", Guid.Empty },
+                    new object[] { "Version", "6.0.0.42", new Version("6.0.0.42") },
+                    new object[] { "BigInteger", "-42", new BigInteger(-42) },
+                    new object[] { "IPAddress", "127.0.0.1", IPAddress.Loopback },
+                    new object[] { "IPEndPoint", "127.0.0.1:80", new IPEndPoint(IPAddress.Loopback, 80) },
+                    new object[] { "AddressFamily", "Unix", AddressFamily.Unix },
+                    new object[] { "ILOpCode", "Nop", ILOpCode.Nop },
+                    new object[] { "AssemblyFlags", "PublicKey,Retargetable", AssemblyFlags.PublicKey | AssemblyFlags.Retargetable },
+                    new object[] { "int?", "42", 42 },
+                    new object[] { "int?", null, null },
+                };
+        }
+    }
+
+    [Theory]
+    [MemberData(nameof(TryParsableParameters))]
+    public async Task MapAction_SingleParsable_StringReturn(string typeName, string queryStringInput, object expectedParameterValue)
+    {
+        var (results, compilation) = await RunGeneratorAsync($$"""
+app.MapGet("/hello", (HttpContext context, [FromQuery]{{typeName}} p) =>
+{
+    context.Items["tryParsable"] = p;
+});
+""");
+
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+        var httpContext = CreateHttpContext();
+
+        var encodedQueryStringInput = queryStringInput != null ? UrlEncoder.Default.Encode(queryStringInput) : null;
+        httpContext.Request.QueryString = new QueryString($"?p={encodedQueryStringInput}");
+
+        await endpoint.RequestDelegate(httpContext);
+        Assert.Equal(200, httpContext.Response.StatusCode);
+        Assert.Equal(expectedParameterValue, httpContext.Items["tryParsable"]);
+    }
+
+    [Theory]
+    [InlineData("PrecedenceCheckTodoWithoutFormat", "24")]
+    [InlineData("PrecedenceCheckTodo", "42")]
+    public async Task MapAction_TryParsePrecedenceCheck(string parameterType, string result)
+    {
+        var (results, compilation) = await RunGeneratorAsync($$"""
+app.MapGet("/hello", ([FromQuery]{{parameterType}} p) => p.MagicValue);
+""");
+
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        Assert.Equal("/hello", endpointModel.RoutePattern);
+        Assert.Equal("MapGet", endpointModel.HttpMethod);
+        var p = Assert.Single(endpointModel.Parameters);
+        Assert.Equal(EndpointParameterSource.Query, p.Source);
+        Assert.Equal("p", p.Name);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.QueryString = new QueryString("?p=1");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, result);
+    }
+
+    [Fact]
+    public async Task MapAction_SingleComplexTypeParam_StringReturn()
+    {
+        // HACK! Notice the return value of p.Name! - this is because TestMapActions.cs has #nullable enable
+        // set and the compiler is returning when it is simply p.Name:
+        //
+        //     CS8603: Possible null reference return.
+        //
+        // Without source gen this same code isn't a problem.
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]Todo p) => p.Name!);
+""");
+
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        Assert.Equal("/hello", endpointModel.RoutePattern);
+        Assert.Equal("MapGet", endpointModel.HttpMethod);
+        var p = Assert.Single(endpointModel.Parameters);
+        Assert.Equal(EndpointParameterSource.Query, p.Source);
+        Assert.Equal("p", p.Name);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.QueryString = new QueryString("?p=1");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "Knit kitten mittens.");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    [Fact]
+    public async Task MapAction_SingleEnumParam_StringReturn()
+    {
+        // HACK! Notice the return value of p.Name! - this is because TestMapActions.cs has #nullable enable
+        // set and the compiler is returning when it is simply p.Name:
+        //
+        //     CS8603: Possible null reference return.
+        //
+        // Without source gen this same code isn't a problem.
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]TodoStatus p) => p.ToString());
+""");
+
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        Assert.Equal("/hello", endpointModel.RoutePattern);
+        Assert.Equal("MapGet", endpointModel.HttpMethod);
+        var p = Assert.Single(endpointModel.Parameters);
+        Assert.Equal(EndpointParameterSource.Query, p.Source);
+        Assert.Equal("p", p.Name);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.QueryString = new QueryString("?p=Done");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "Done");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    // [Fact]
+    // public async Task MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn()
+
     [Fact]
     public async Task MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn()
     {

+ 0 - 0
src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/Bindability.cs → src/Shared/RoslynUtils/Bindability.cs


+ 0 - 0
src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/Parsability.cs → src/Shared/RoslynUtils/Parsability.cs


+ 25 - 9
src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/ParsabilityHelper.cs → src/Shared/RoslynUtils/ParsabilityHelper.cs

@@ -9,6 +9,7 @@ using System.Linq;
 using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
 using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
 using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
 
@@ -16,32 +17,41 @@ using WellKnownType = WellKnownTypeData.WellKnownType;
 
 internal static class ParsabilityHelper
 {
-    private static bool IsTypeAlwaysParsableOrBindable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
+    private static bool IsTypeAlwaysParsableOrBindable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out ParsabilityMethod? parsabilityMethod)
     {
         // Any enum is valid.
         if (typeSymbol.TypeKind == TypeKind.Enum)
         {
+            parsabilityMethod = ParsabilityMethod.Enum;
             return true;
         }
 
         // Uri is valid.
         if (SymbolEqualityComparer.Default.Equals(typeSymbol, wellKnownTypes.Get(WellKnownType.System_Uri)))
         {
+            parsabilityMethod = ParsabilityMethod.Uri;
             return true;
         }
 
         // Strings are valid.
         if (typeSymbol.SpecialType == SpecialType.System_String)
         {
+            parsabilityMethod = ParsabilityMethod.String;
             return true;
         }
 
+        parsabilityMethod = null;
         return false;
     }
 
     internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
     {
-        if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes))
+        return GetParsability(typeSymbol, wellKnownTypes, out var _);
+    }
+
+    internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, out ParsabilityMethod? parsabilityMethod)
+    {
+        if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes, out parsabilityMethod))
         {
             return Parsability.Parsable;
         }
@@ -49,17 +59,23 @@ internal static class ParsabilityHelper
         // MyType : IParsable<MyType>()
         if (IsParsableViaIParsable(typeSymbol, wellKnownTypes))
         {
+            parsabilityMethod = ParsabilityMethod.IParsable;
             return Parsability.Parsable;
         }
 
         // Check if the parameter type has a public static TryParse method.
         var tryParseMethods = typeSymbol.GetMembers("TryParse").OfType<IMethodSymbol>();
-        foreach (var tryParseMethodSymbol in tryParseMethods)
+
+        if (tryParseMethods.Any(m => IsTryParseWithFormat(m, wellKnownTypes)))
         {
-            if (IsTryParse(tryParseMethodSymbol) || IsTryParseWithFormat(tryParseMethodSymbol, wellKnownTypes))
-            {
-                return Parsability.Parsable;
-            }
+            parsabilityMethod = ParsabilityMethod.TryParseWithFormatProvider;
+            return Parsability.Parsable;
+        }
+
+        if (tryParseMethods.Any(IsTryParse))
+        {
+            parsabilityMethod = ParsabilityMethod.TryParse;
+            return Parsability.Parsable;
         }
 
         return Parsability.NotParsable;
@@ -86,7 +102,7 @@ internal static class ParsabilityHelper
             methodSymbol.Parameters[2].RefKind == RefKind.Out;
     }
 
-    private static bool IsParsableViaIParsable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
+    internal static bool IsParsableViaIParsable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
     {
         var iParsableTypeSymbol = wellKnownTypes.Get(WellKnownType.System_IParsable_T);
         var implementsIParsable = typeSymbol.AllInterfaces.Any(
@@ -134,7 +150,7 @@ internal static class ParsabilityHelper
 
     internal static Bindability GetBindability(INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes)
     {
-        if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes))
+        if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes, out var _))
         {
             return Bindability.Bindable;
         }

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

@@ -0,0 +1,14 @@
+// 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
+}

+ 23 - 0
src/Shared/RoslynUtils/SymbolExtensions.cs

@@ -11,6 +11,29 @@ namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
 
 internal static class SymbolExtensions
 {
+    public static INamedTypeSymbol? UnwrapTypeSymbol(this ITypeSymbol typeSymbol)
+    {
+        INamedTypeSymbol? unwrappedTypeSymbol = null;
+
+        // If it is an array, unwrap it.
+        if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
+        {
+            unwrappedTypeSymbol = arrayTypeSymbol.ElementType as INamedTypeSymbol;
+        }
+        else if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
+        {
+            unwrappedTypeSymbol = namedTypeSymbol;
+        }
+
+        // If it is nullable, unwrap it.
+        if (unwrappedTypeSymbol!.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
+        {
+            unwrappedTypeSymbol = unwrappedTypeSymbol.TypeArguments[0] as INamedTypeSymbol;
+        }
+
+        return unwrappedTypeSymbol;
+    }
+
     public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
     {
         foreach (var attributeData in symbol.GetAttributes())

+ 4 - 0
src/Shared/RoslynUtils/WellKnownTypeData.cs

@@ -28,6 +28,8 @@ internal static class WellKnownTypeData
         System_Security_Claims_ClaimsPrincipal,
         Microsoft_AspNetCore_Http_IFormFileCollection,
         Microsoft_AspNetCore_Http_IFormFile,
+        System_DateOnly,
+        System_DateTimeOffset,
         System_IO_Stream,
         System_IO_Pipelines_PipeReader,
         System_IFormatProvider,
@@ -129,6 +131,8 @@ internal static class WellKnownTypeData
         "System.Security.Claims.ClaimsPrincipal",
         "Microsoft.AspNetCore.Http.IFormFileCollection",
         "Microsoft.AspNetCore.Http.IFormFile",
+        "System.DateOnly",
+        "System.DateTimeOffset",
         "System.IO.Stream",
         "System.IO.Pipelines.PipeReader",
         "System.IFormatProvider",