Przeglądaj źródła

First pass at basic query string parameters. (#46545)

* First pass at basic string parameters.
Mitch Denny 3 lat temu
rodzic
commit
1a70e36230
25 zmienionych plików z 1386 dodań i 75 usunięć
  1. 2 0
      src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj
  2. 2 0
      src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj
  3. 1 1
      src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs
  4. 43 0
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs
  5. 56 0
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs
  6. 1 1
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs
  7. 23 13
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs
  8. 11 5
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs
  9. 0 45
      src/Http/Http.Extensions/gen/StaticRouteHandlerModel/WellKnownTypeData.cs
  10. 1 0
      src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
  11. 1 0
      src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs
  12. 1 0
      src/Http/Http.Extensions/test/ProblemDetailsServiceCollectionExtensionsTest.cs
  13. 179 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt
  14. 195 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt
  15. 1 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt
  16. 181 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt
  17. 181 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt
  18. 181 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt
  19. 186 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt
  20. 4 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt
  21. 12 8
      src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt
  22. 3 2
      src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs
  23. 117 0
      src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs
  24. 0 0
      src/Shared/RoslynUtils/SymbolExtensions.cs
  25. 4 0
      src/Shared/RoslynUtils/WellKnownTypeData.cs

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

@@ -25,6 +25,8 @@
     <Compile Include="$(SharedSourceRoot)Nullable\NullableAttributes.cs" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\BoundedCacheWithFactory.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypes.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypeData.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)RoslynUtils\SymbolExtensions.cs" LinkBase="Shared" />
   </ItemGroup>
 
 </Project>

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

@@ -21,7 +21,9 @@
     <Compile Include="$(SharedSourceRoot)IsExternalInit.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)HashCode.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\BoundedCacheWithFactory.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypeData.cs" LinkBase="Shared" />
     <Compile Include="$(SharedSourceRoot)RoslynUtils\WellKnownTypes.cs" LinkBase="Shared" />
+    <Compile Include="$(SharedSourceRoot)RoslynUtils\SymbolExtensions.cs" LinkBase="Shared" />
   </ItemGroup>
 
 </Project>

+ 1 - 1
src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs

@@ -120,7 +120,7 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
                     lineNumber);
         }
 """);
-                }
+               }
 
                 return code.ToString();
             });

+ 43 - 0
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs

@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
+internal static class EndpointEmitter
+{
+    internal static string EmitParameterPreparation(this Endpoint endpoint)
+    {
+        var parameterPreparationBuilder = new StringBuilder();
+
+        for (var parameterIndex = 0; parameterIndex < endpoint.Parameters.Length; parameterIndex++)
+        {
+            var parameter = endpoint.Parameters[parameterIndex];
+
+            var parameterPreparationCode = parameter switch
+            {
+                {
+                    Source: EndpointParameterSource.SpecialType
+                } => parameter.EmitSpecialParameterPreparation(),
+                {
+                    Source: EndpointParameterSource.Query,
+                } => parameter.EmitQueryParameterPreparation(),
+                _ => throw new Exception("Unreachable!")
+            };
+
+            // To avoid having two newlines after the block of parameter handling code.
+            if (parameterIndex < endpoint.Parameters.Length - 1)
+            {
+                parameterPreparationBuilder.AppendLine(parameterPreparationCode);
+            }
+            else
+            {
+                parameterPreparationBuilder.Append(parameterPreparationCode);
+            }
+        }
+
+        return parameterPreparationBuilder.ToString();
+    }
+}

+ 56 - 0
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs

@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
+internal static class EndpointParameterEmitter
+{
+    internal static string EmitSpecialParameterPreparation(this EndpointParameter endpointParameter)
+    {
+        return $"""
+                        var {endpointParameter.Name}_local = {endpointParameter.AssigningCode};
+""";
+    }
+
+    internal static string EmitQueryParameterPreparation(this EndpointParameter endpointParameter)
+    {
+        var builder = new StringBuilder();
+
+        // Preamble for diagnostics purposes.
+        builder.AppendLine($$"""
+                        // Endpoint Parameter: {{endpointParameter.Name}} (Type = {{endpointParameter.Type}}, IsOptional = {{endpointParameter.IsOptional}}, Source = {{endpointParameter.Source}})
+""");
+
+        // Grab raw input from HttpContext.
+        builder.AppendLine($$"""
+                        var {{endpointParameter.Name}}_raw = {{endpointParameter.AssigningCode}};
+""");
+
+        // If we are not optional, then at this point we can just assign the string value to the handler argument,
+        // otherwise we need to detect whether no value is provided and set the handler argument to null to
+        // preserve consistency with RDF behavior. We don't want to emit the conditional block to avoid
+        // compiler errors around null handling.
+        if (endpointParameter.IsOptional)
+        {
+            builder.AppendLine($$"""
+                        var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.Count > 0 ? {{endpointParameter.Name}}_raw.ToString() : null;
+""");
+        }
+        else
+        {
+            builder.AppendLine($$"""
+                        if (StringValues.IsNullOrEmpty({{endpointParameter.Name}}_raw))
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }                             
+                        var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.ToString();
+""");
+        }
+
+        return builder.ToString();
+    }
+}

+ 1 - 1
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs

@@ -88,7 +88,7 @@ internal class Endpoint
 
         for (var i = 0; i < a.Parameters.Length; i++)
         {
-            if (a.Parameters[i].Equals(b.Parameters[i]))
+            if (!a.Parameters[i].Equals(b.Parameters[i]))
             {
                 return false;
             }

+ 23 - 13
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs

@@ -3,8 +3,10 @@
 
 using System;
 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.CodeAnalysis.CSharp.Syntax;
 
 namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
 
@@ -15,11 +17,25 @@ internal class EndpointParameter
         Type = parameter.Type;
         Name = parameter.Name;
         Source = EndpointParameterSource.Unknown;
+        HandlerArgument = $"{parameter.Name}_local";
 
-        if (GetSpecialTypeCallingCode(Type, wellKnownTypes) is string callingCode)
+        var fromQueryMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata);
+
+        if (GetSpecialTypeAssigningCode(Type, wellKnownTypes) is string assigningCode)
         {
             Source = EndpointParameterSource.SpecialType;
-            CallingCode = callingCode;
+            AssigningCode = assigningCode;
+        }
+        else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType))
+        {
+            Source = EndpointParameterSource.Query;
+            AssigningCode = $"httpContext.Request.Query[\"{parameter.Name}\"]";
+            IsOptional = parameter.Type is INamedTypeSymbol parameterType && parameterType.NullableAnnotation == NullableAnnotation.Annotated;
+        }
+        else
+        {
+            // TODO: Inferencing rules go here - but for now:
+            Source = EndpointParameterSource.Unknown;
         }
     }
 
@@ -28,23 +44,17 @@ internal class EndpointParameter
 
     // TODO: If the parameter has [FromRoute("AnotherName")] or similar, prefer that.
     public string Name { get; }
-    public string? CallingCode { get; }
+    public string? AssigningCode { get; }
+    public string HandlerArgument { get; }
+    public bool IsOptional { get; }
 
     public string EmitArgument()
     {
-        switch (Source)
-        {
-            case EndpointParameterSource.SpecialType:
-                return CallingCode!;
-            default:
-                // Eventually there should be know unknown parameter sources, but in the meantime we don't expect them to get this far.
-                // The netstandard2.0 target means there is no UnreachableException.
-                throw new Exception("Unreachable!");
-        }
+        return HandlerArgument;
     }
 
     // TODO: Handle special form types like IFormFileCollection that need special body-reading logic.
-    private static string? GetSpecialTypeCallingCode(ITypeSymbol type, WellKnownTypes wellKnownTypes)
+    private static string? GetSpecialTypeAssigningCode(ITypeSymbol type, WellKnownTypes wellKnownTypes)
     {
         if (SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_HttpContext)))
         {

+ 11 - 5
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs

@@ -2,9 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
+using System.Net.Cache;
 using System.Text;
+using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
 using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
 
 namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
 
@@ -61,14 +65,18 @@ internal static class StaticRouteHandlerModelEmitter
         var resultAssignment = endpoint.Response.IsVoid ? string.Empty : "var result = ";
         var awaitHandler = endpoint.Response.IsAwaitable ? "await " : string.Empty;
         var setContentType = endpoint.Response.IsVoid ? string.Empty : $@"httpContext.Response.ContentType ??= ""{endpoint.Response.ContentType}"";";
-        return $$"""
+
+        var requestHandlerSource = $$"""
                     {{handlerSignature}}
                     {
+{{endpoint.EmitParameterPreparation()}}
                         {{setContentType}}
                         {{resultAssignment}}{{awaitHandler}}handler({{endpoint.EmitArgumentList()}});
                         {{(endpoint.Response.IsVoid ? "return Task.CompletedTask;" : endpoint.EmitResponseWritingCall())}}
                     }
 """;
+
+        return requestHandlerSource;
     }
 
     private static string EmitResponseWritingCall(this Endpoint endpoint)
@@ -108,14 +116,12 @@ internal static class StaticRouteHandlerModelEmitter
      * can be used to reduce the boxing that happens at runtime when constructing
      * the context object.
      */
-    public static string EmitFilteredRequestHandler(this Endpoint endpoint)
+    public static string EmitFilteredRequestHandler(this Endpoint _)
     {
-        var argumentList = endpoint.Parameters.Length == 0 ? string.Empty : $", {endpoint.EmitArgumentList()}";
-
         return $$"""
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
-                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext{{argumentList}}));
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 """;

+ 0 - 45
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/WellKnownTypeData.cs

@@ -1,45 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.App.Analyzers.Infrastructure;
-
-internal static class WellKnownTypeData
-{
-    public enum WellKnownType
-    {
-        Microsoft_AspNetCore_Http_HttpContext,
-        Microsoft_AspNetCore_Http_HttpRequest,
-        Microsoft_AspNetCore_Http_HttpResponse,
-        Microsoft_AspNetCore_Http_IFormCollection,
-        Microsoft_AspNetCore_Http_IFormFileCollection,
-        Microsoft_AspNetCore_Http_IFormFile,
-        Microsoft_AspNetCore_Http_IResult,
-        System_IO_Pipelines_PipeReader,
-        System_IO_Stream,
-        System_Security_Claims_ClaimsPrincipal,
-        System_Threading_CancellationToken,
-        System_Threading_Tasks_Task,
-        System_Threading_Tasks_Task_T,
-        System_Threading_Tasks_ValueTask,
-        System_Threading_Tasks_ValueTask_T,
-    }
-
-    public static readonly string[] WellKnownTypeNames = new[]
-    {
-        "Microsoft.AspNetCore.Http.HttpContext",
-        "Microsoft.AspNetCore.Http.HttpRequest",
-        "Microsoft.AspNetCore.Http.HttpResponse",
-        "Microsoft.AspNetCore.Http.IFormCollection",
-        "Microsoft.AspNetCore.Http.IFormFileCollection",
-        "Microsoft.AspNetCore.Http.IFormFile",
-        "Microsoft.AspNetCore.Http.IResult",
-        "System.IO.Pipelines.PipeReader",
-        "System.IO.Stream",
-        "System.Security.Claims.ClaimsPrincipal",
-        "System.Threading.CancellationToken",
-        "System.Threading.Tasks.Task",
-        "System.Threading.Tasks.Task`1",
-        "System.Threading.Tasks.ValueTask",
-        "System.Threading.Tasks.ValueTask`1",
-    };
-}

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

@@ -16,6 +16,7 @@
     <Reference Include="Microsoft.AspNetCore.Http" />
     <Reference Include="Microsoft.AspNetCore.Http.Results" />
     <Reference Include="Microsoft.AspNetCore.Http.Extensions" />
+    <Reference Include="Microsoft.AspNetCore.Mvc.Core" />
     <Reference Include="Microsoft.CodeAnalysis.CSharp" />
     <Reference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
     <Reference Include="Microsoft.Extensions.DependencyInjection" />

+ 1 - 0
src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs

@@ -11,6 +11,7 @@ using System.Text.Json.Serialization.Metadata;
 using Microsoft.AspNetCore.Http.Json;
 using System.Text.Json.Serialization;
 using Microsoft.CodeAnalysis;
+using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions;
 
 namespace Microsoft.AspNetCore.Http.Extensions.Tests;
 

+ 1 - 0
src/Http/Http.Extensions/test/ProblemDetailsServiceCollectionExtensionsTest.cs

@@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
 using Microsoft.Extensions.Options;
 using Moq;
+using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions;
 
 namespace Microsoft.AspNetCore.Http.Extensions.Tests;
 

+ 179 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt

@@ -0,0 +1,179 @@
+//------------------------------------------------------------------------------
+// <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<Microsoft.AspNetCore.Http.HttpRequest, Microsoft.AspNetCore.Http.HttpResponse, 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.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", 15)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (System.Func<Microsoft.AspNetCore.Http.HttpRequest, Microsoft.AspNetCore.Http.HttpResponse, 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<Microsoft.AspNetCore.Http.HttpRequest>(0), ic.GetArgument<Microsoft.AspNetCore.Http.HttpResponse>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        var req_local = httpContext.Request;
+                        var res_local = httpContext.Response;
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(req_local, res_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
+                        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);
+            }
+        }
+    }
+}

+ 195 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt

@@ -0,0 +1,195 @@
+//------------------------------------------------------------------------------
+// <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<string, string, 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.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", 15)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (System.Func<string, string, 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<string>(0), ic.GetArgument<string>(1)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        // Endpoint Parameter: p1 (Type = string, IsOptional = False, Source = Query)
+                        var p1_raw = httpContext.Request.Query["p1"];
+                        if (StringValues.IsNullOrEmpty(p1_raw))
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }
+                        var p1_local = p1_raw.ToString();
+
+                        // Endpoint Parameter: p2 (Type = string, IsOptional = False, Source = Query)
+                        var p2_raw = httpContext.Request.Query["p2"];
+                        if (StringValues.IsNullOrEmpty(p2_raw))
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }
+                        var p2_local = p2_raw.ToString();
+
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p1_local, p2_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
+                        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);
+            }
+        }
+    }
+}

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

@@ -112,6 +112,7 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     Task RequestHandler(HttpContext httpContext)
                     {
+
                         httpContext.Response.ContentType ??= "text/plain";
                         var result = handler();
                         return httpContext.Response.WriteAsync(result);

+ 181 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt

@@ -0,0 +1,181 @@
+//------------------------------------------------------------------------------
+// <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<string?, 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.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", 15)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (System.Func<string?, 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<string?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        // 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;
+
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
+                        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);
+            }
+        }
+    }
+}

+ 181 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt

@@ -0,0 +1,181 @@
+//------------------------------------------------------------------------------
+// <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<string?, 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.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", 15)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (System.Func<string?, 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<string?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        // 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;
+
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
+                        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);
+            }
+        }
+    }
+}

+ 181 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt

@@ -0,0 +1,181 @@
+//------------------------------------------------------------------------------
+// <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<string?, 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.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", 15)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (System.Func<string?, 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<string?>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        // 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;
+
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
+                        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);
+            }
+        }
+    }
+}

+ 186 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt

@@ -0,0 +1,186 @@
+//------------------------------------------------------------------------------
+// <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<string, 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.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", 15)] = (
+               (methodInfo, options) =>
+                {
+                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
+                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15));
+                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
+                },
+                (del, options, inferredMetadataResult) =>
+                {
+                    var handler = (System.Func<string, 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<string>(0)));
+                        },
+                        options.EndpointBuilder,
+                        handler.Method);
+                    }
+
+                    Task RequestHandler(HttpContext httpContext)
+                    {
+                        // Endpoint Parameter: p (Type = string, IsOptional = False, Source = Query)
+                        var p_raw = httpContext.Request.Query["p"];
+                        if (StringValues.IsNullOrEmpty(p_raw))
+                        {
+                            httpContext.Response.StatusCode = 400;
+                            return Task.CompletedTask;
+                        }
+                        var p_local = p_raw.ToString();
+
+                        httpContext.Response.ContentType ??= "text/plain";
+                        var result = handler(p_local);
+                        return httpContext.Response.WriteAsync(result);
+                    }
+                    async Task RequestHandlerFiltered(HttpContext httpContext)
+                    {
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
+                        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);
+            }
+        }
+    }
+}

+ 4 - 0
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt

@@ -142,6 +142,7 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     Task RequestHandler(HttpContext httpContext)
                     {
+
                         httpContext.Response.ContentType ??= "text/plain";
                         var result = handler();
                         return httpContext.Response.WriteAsync(result);
@@ -184,6 +185,7 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     Task RequestHandler(HttpContext httpContext)
                     {
+
                         httpContext.Response.ContentType ??= "text/plain";
                         var result = handler();
                         return httpContext.Response.WriteAsync(result);
@@ -226,6 +228,7 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     async Task RequestHandler(HttpContext httpContext)
                     {
+
                         httpContext.Response.ContentType ??= "application/json";
                         var result = await handler();
                         await httpContext.Response.WriteAsync(result);
@@ -268,6 +271,7 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     async Task RequestHandler(HttpContext httpContext)
                     {
+
                         httpContext.Response.ContentType ??= "application/json";
                         var result = await handler();
                         await httpContext.Response.WriteAsync(result);

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

@@ -10,7 +10,7 @@
 
 namespace Microsoft.AspNetCore.Builder
 {
-    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.Generators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+    %GENERATEDCODEATTRIBUTE%
     internal class SourceKey
     {
         public string Path { get; init; }
@@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Builder
     // 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.
-    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.Generators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+    %GENERATEDCODEATTRIBUTE%
     internal static class GenerateRouteBuilderEndpoints
     {
         private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get };
@@ -142,13 +142,14 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     Task RequestHandler(HttpContext httpContext)
                     {
+                        var req_local = httpContext.Request;
                         httpContext.Response.ContentType ??= "text/plain";
-                        var result = handler(httpContext.Request);
+                        var result = handler(req_local);
                         return httpContext.Response.WriteAsync(result);
                     }
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
-                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext, httpContext.Request));
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 
@@ -184,13 +185,14 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     Task RequestHandler(HttpContext httpContext)
                     {
+                        var res_local = httpContext.Response;
                         httpContext.Response.ContentType ??= "text/plain";
-                        var result = handler(httpContext.Response);
+                        var result = handler(res_local);
                         return httpContext.Response.WriteAsync(result);
                     }
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
-                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext, httpContext.Response));
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 
@@ -226,13 +228,15 @@ namespace Microsoft.AspNetCore.Http.Generated
 
                     Task RequestHandler(HttpContext httpContext)
                     {
+                        var req_local = httpContext.Request;
+                        var res_local = httpContext.Response;
                         httpContext.Response.ContentType ??= "text/plain";
-                        var result = handler(httpContext.Request, httpContext.Response);
+                        var result = handler(req_local, res_local);
                         return httpContext.Response.WriteAsync(result);
                     }
                     async Task RequestHandlerFiltered(HttpContext httpContext)
                     {
-                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext, httpContext.Request, httpContext.Response));
+                        var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext));
                         await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
                     }
 

+ 3 - 2
src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs

@@ -237,14 +237,15 @@ public static class TestMapActions
     internal async Task VerifyAgainstBaselineUsingFile(Compilation compilation, [CallerMemberName] string callerName = "")
     {
         var baselineFilePath = Path.Combine("RequestDelegateGenerator", "Baselines", $"{callerName}.generated.txt");
-        var generatedCode = compilation.SyntaxTrees.Last();
+        var generatedSyntaxTree = compilation.SyntaxTrees.Last();
+        var generatedCode = generatedSyntaxTree.GetText();
         var baseline = await File.ReadAllTextAsync(baselineFilePath);
         var expectedLines = baseline
             .TrimEnd() // Trim newlines added by autoformat
             .Replace("%GENERATEDCODEATTRIBUTE%", RequestDelegateGeneratorSources.GeneratedCodeAttribute)
             .Split(Environment.NewLine);
 
-        Assert.True(CompareLines(expectedLines, generatedCode.GetText(), out var errorMessage), errorMessage);
+        Assert.True(CompareLines(expectedLines, generatedCode, out var errorMessage), errorMessage);
     }
 
     private bool CompareLines(string[] expectedLines, SourceText sourceText, out string message)

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

@@ -31,6 +31,122 @@ public class RequestDelegateGeneratorTests : RequestDelegateGeneratorTestBase
         await VerifyResponseBodyAsync(httpContext, expectedBody);
     }
 
+    [Fact]
+    public async Task MapAction_SingleStringParam_StringReturn()
+    {
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]string p) => p);
+""");
+
+        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=Hello%20world!");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "Hello world!");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    [Fact]
+    public async Task MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn()
+    {
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]string? p) => p ?? "Error!");
+""");
+
+        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=Hello%20world!");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "Hello world!");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    [Fact]
+    public async Task MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn()
+    {
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]string? p) => p ?? "Was null!");
+""");
+
+        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();
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "Was null!");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    [Fact]
+    public async Task MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn()
+    {
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]string? p) => p == string.Empty ? "No value, but not null!" : "Was null!");
+""");
+
+        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=");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "No value, but not null!");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
+    [Fact]
+    public async Task MapAction_MultipleStringParam_StringReturn()
+    {
+        var (results, compilation) = await RunGeneratorAsync("""
+app.MapGet("/hello", ([FromQuery]string p1, [FromQuery]string p2) => $"{p1} {p2}");
+""");
+
+        var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep);
+        var endpoint = GetEndpointFromCompilation(compilation);
+
+        Assert.Equal("/hello", endpointModel.RoutePattern);
+        Assert.Equal("MapGet", endpointModel.HttpMethod);
+
+        var httpContext = CreateHttpContext();
+        httpContext.Request.QueryString = new QueryString("?p1=Hello&p2=world!");
+
+        await endpoint.RequestDelegate(httpContext);
+        await VerifyResponseBodyAsync(httpContext, "Hello world!");
+        await VerifyAgainstBaselineUsingFile(compilation);
+    }
+
     [Theory]
     [InlineData("HttpContext")]
     [InlineData("HttpRequest")]
@@ -87,6 +203,7 @@ app.MapGet("/hello", (HttpRequest req, HttpResponse res) => req is null || res i
         var httpContext = CreateHttpContext();
         await endpoint.RequestDelegate(httpContext);
         await VerifyResponseBodyAsync(httpContext, "Hello world!");
+        await VerifyAgainstBaselineUsingFile(compilation);
     }
 
     [Fact]

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


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

@@ -48,7 +48,9 @@ internal static class WellKnownTypeData
         Microsoft_AspNetCore_Mvc_IActionResult,
         Microsoft_AspNetCore_Mvc_Infrastructure_IConvertToActionResult,
         Microsoft_AspNetCore_Http_RequestDelegate,
+        System_Threading_Tasks_Task,
         System_Threading_Tasks_Task_T,
+        System_Threading_Tasks_ValueTask,
         System_Threading_Tasks_ValueTask_T,
         System_Reflection_ParameterInfo,
         Microsoft_AspNetCore_Http_IBindableFromHttpContext_T,
@@ -146,7 +148,9 @@ internal static class WellKnownTypeData
         "Microsoft.AspNetCore.Mvc.IActionResult",
         "Microsoft.AspNetCore.Mvc.Infrastructure.IConvertToActionResult",
         "Microsoft.AspNetCore.Http.RequestDelegate",
+        "System.Threading.Tasks.Task",
         "System.Threading.Tasks.Task`1",
+        "System.Threading.Tasks.ValueTask",
         "System.Threading.Tasks.ValueTask`1",
         "System.Reflection.ParameterInfo",
         "Microsoft.AspNetCore.Http.IBindableFromHttpContext`1",