// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO.Pipelines;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Endpoints.FormMapping;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Http;

/// <summary>
/// Creates <see cref="RequestDelegate"/> implementations from <see cref="Delegate"/> request handlers.
/// </summary>
[RequiresUnreferencedCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")]
[RequiresDynamicCode("RequestDelegateFactory performs object creation, serialization and deserialization on the delegates and its parameters. This cannot be statically analyzed.")]
public static partial class RequestDelegateFactory
{
    private static readonly MethodInfo ExecuteTaskWithEmptyResultMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskWithEmptyResult), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueTaskWithEmptyResultMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskWithEmptyResult), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteTaskOfTFastMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfTFast), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueTaskOfTFastMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfTFast), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueTaskMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteTaskResultOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteValueResultTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ExecuteAwaitedReturnMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteAwaitedReturn), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo GetRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
    private static readonly MethodInfo GetServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
    private static readonly MethodInfo GetRequiredKeyedServiceMethod = typeof(ServiceProviderKeyedServiceExtensions).GetMethod(nameof(ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider), typeof(object) })!;
    private static readonly MethodInfo GetKeyedServiceMethod = typeof(ServiceProviderKeyedServiceExtensions).GetMethod(nameof(ServiceProviderKeyedServiceExtensions.GetKeyedService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider), typeof(object) })!;
    private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteResultWriteResponse), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteWriteStringResponseAsync), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo StringIsNullOrEmptyMethod = typeof(string).GetMethod(nameof(string.IsNullOrEmpty), BindingFlags.Static | BindingFlags.Public)!;
    private static readonly MethodInfo WrapObjectAsValueTaskMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WrapObjectAsValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo TaskOfTToValueTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(TaskOfTToValueTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ValueTaskOfTToValueTaskOfObjectMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ValueTaskOfTToValueTaskOfObject), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo ArrayEmptyOfObjectMethod = typeof(Array).GetMethod(nameof(Array.Empty), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(new Type[] { typeof(object) });

    private static readonly PropertyInfo QueryIndexerProperty = typeof(IQueryCollection).GetProperty("Item")!;
    private static readonly PropertyInfo RouteValuesIndexerProperty = typeof(RouteValueDictionary).GetProperty("Item")!;
    private static readonly PropertyInfo HeaderIndexerProperty = typeof(IHeaderDictionary).GetProperty("Item")!;
    private static readonly PropertyInfo FormFilesIndexerProperty = typeof(IFormFileCollection).GetProperty("Item")!;
    private static readonly PropertyInfo FormIndexerProperty = typeof(IFormCollection).GetProperty("Item")!;

    private static readonly MethodInfo JsonResultWriteResponseOfTFastAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WriteJsonResponseFast), BindingFlags.NonPublic | BindingFlags.Static)!;
    private static readonly MethodInfo JsonResultWriteResponseOfTAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(WriteJsonResponse), BindingFlags.NonPublic | BindingFlags.Static)!;

    private static readonly MethodInfo LogParameterBindingFailedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, sourceValue, shouldThrow) =>
        Log.ParameterBindingFailed(httpContext, parameterType, parameterName, sourceValue, shouldThrow));
    private static readonly MethodInfo LogRequiredParameterNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, source, shouldThrow) =>
        Log.RequiredParameterNotProvided(httpContext, parameterType, parameterName, source, shouldThrow));
    private static readonly MethodInfo LogImplicitBodyNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, bool>>((httpContext, parameterName, shouldThrow) =>
        Log.ImplicitBodyNotProvided(httpContext, parameterName, shouldThrow));
    private static readonly MethodInfo LogFormMappingFailedMethod = GetMethodInfo<Action<HttpContext, string, string, FormDataMappingException, bool>>((httpContext, parameterName, parameterType, exception, shouldThrow) =>
        Log.FormDataMappingFailed(httpContext, parameterName, parameterType, exception, shouldThrow));

    private static readonly ParameterExpression TargetExpr = Expression.Parameter(typeof(object), "target");
    private static readonly ParameterExpression BodyValueExpr = Expression.Parameter(typeof(object), "bodyValue");
    private static readonly ParameterExpression WasParamCheckFailureExpr = Expression.Variable(typeof(bool), "wasParamCheckFailure");
    private static readonly ParameterExpression BoundValuesArrayExpr = Expression.Parameter(typeof(object[]), "boundValues");

    private static readonly ParameterExpression HttpContextExpr = ParameterBindingMethodCache.SharedExpressions.HttpContextExpr;
    private static readonly MemberExpression RequestServicesExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestServices))!);
    private static readonly MemberExpression HttpRequestExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Request))!);
    private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
    private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestAborted))!);
    private static readonly MemberExpression UserExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.User))!);
    private static readonly MemberExpression RouteValuesExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.RouteValues))!);
    private static readonly MemberExpression QueryExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Query))!);
    private static readonly MemberExpression HeadersExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Headers))!);
    private static readonly MemberExpression FormExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Form))!);
    private static readonly MemberExpression RequestStreamExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Body))!);
    private static readonly MemberExpression RequestPipeReaderExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.BodyReader))!);
    private static readonly MemberExpression FormFilesExpr = Expression.Property(FormExpr, typeof(IFormCollection).GetProperty(nameof(IFormCollection.Files))!);
    private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
    private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
    private static readonly NewExpression EmptyHttpResultValueTaskExpr = Expression.New(typeof(ValueTask<object>).GetConstructor(new[] { typeof(EmptyHttpResult) })!, Expression.Property(null, typeof(EmptyHttpResult), nameof(EmptyHttpResult.Instance)));
    private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.SharedExpressions.TempSourceStringExpr;
    private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
    private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
    private static readonly UnaryExpression TempSourceStringIsNotNullOrEmptyExpr = Expression.Not(Expression.Call(StringIsNullOrEmptyMethod, TempSourceStringExpr));

    private static readonly ConstructorInfo DefaultEndpointFilterInvocationContextConstructor = typeof(DefaultEndpointFilterInvocationContext).GetConstructor(new[] { typeof(HttpContext), typeof(object[]) })!;
    private static readonly MethodInfo EndpointFilterInvocationContextGetArgument = typeof(EndpointFilterInvocationContext).GetMethod(nameof(EndpointFilterInvocationContext.GetArgument))!;
    private static readonly PropertyInfo ListIndexer = typeof(IList<object>).GetProperty("Item")!;
    private static readonly ParameterExpression FilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "context");
    private static readonly MemberExpression FilterContextHttpContextExpr = Expression.Property(FilterContextExpr, typeof(EndpointFilterInvocationContext).GetProperty(nameof(EndpointFilterInvocationContext.HttpContext))!);
    private static readonly MemberExpression FilterContextArgumentsExpr = Expression.Property(FilterContextExpr, typeof(EndpointFilterInvocationContext).GetProperty(nameof(EndpointFilterInvocationContext.Arguments))!);
    private static readonly MemberExpression FilterContextHttpContextResponseExpr = Expression.Property(FilterContextHttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
    private static readonly MemberExpression FilterContextHttpContextStatusCodeExpr = Expression.Property(FilterContextHttpContextResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
    private static readonly ParameterExpression InvokedFilterContextExpr = Expression.Parameter(typeof(EndpointFilterInvocationContext), "filterContext");

    private static readonly ConstructorInfo FormDataReaderConstructor = typeof(FormDataReader).GetConstructor(new[] { typeof(IReadOnlyDictionary<FormKey, StringValues>), typeof(CultureInfo), typeof(Memory<char>), typeof(IFormFileCollection) })!;
    private static readonly MethodInfo ProcessFormMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ProcessForm), BindingFlags.Static | BindingFlags.NonPublic)!;
    private static readonly MethodInfo FormDataMapperMapMethod = typeof(FormDataMapper).GetMethod(nameof(FormDataMapper.Map))!;
    private static readonly MethodInfo AsMemoryMethod = new Func<char[]?, int, int, Memory<char>>(MemoryExtensions.AsMemory).Method;
    private static readonly MethodInfo ArrayPoolSharedReturnMethod = typeof(ArrayPool<char>).GetMethod(nameof(ArrayPool<char>.Shared.Return))!;

    private static readonly string[] DefaultAcceptsAndProducesContentType = new[] { ContentTypeConstants.JsonContentType };
    private static readonly string[] FormFileContentType = new[] { "multipart/form-data" };
    private static readonly string[] FormContentType = new[] { "multipart/form-data", "application/x-www-form-urlencoded" };
    private static readonly string[] PlaintextContentType = new[] { "text/plain" };

    /// <summary>
    /// Returns metadata inferred automatically for the <see cref="RequestDelegate"/> created by <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.
    /// This includes metadata inferred by <see cref="IEndpointMetadataProvider"/> and <see cref="IEndpointParameterMetadataProvider"/> implemented by parameter and return types to the <paramref name="methodInfo"/>.
    /// </summary>
    /// <param name="methodInfo">The <see cref="MethodInfo"/> for the route handler to be passed to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.</param>
    /// <param name="options">The options that will be used when calling <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.</param>
    /// <returns>The <see cref="RequestDelegateMetadataResult"/> to be passed to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/>.</returns>
    public static RequestDelegateMetadataResult InferMetadata(MethodInfo methodInfo, RequestDelegateFactoryOptions? options = null)
    {
        var factoryContext = CreateFactoryContext(options);
        factoryContext.ArgumentExpressions = CreateArgumentsAndInferMetadata(methodInfo, factoryContext);

        return new RequestDelegateMetadataResult
        {
            EndpointMetadata = AsReadOnlyList(factoryContext.EndpointBuilder.Metadata),
            CachedFactoryContext = factoryContext,
        };
    }

    /// <summary>
    /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="handler"/>.
    /// </summary>
    /// <param name="handler">A request handler with any number of custom parameters that often produces a response with its return value.</param>
    /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
    /// <returns>The <see cref="RequestDelegateResult"/>.</returns>
    public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options)
    {
        return Create(handler, options, metadataResult: null);
    }

    /// <summary>
    /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="handler"/>.
    /// </summary>
    /// <param name="handler">A request handler with any number of custom parameters that often produces a response with its return value.</param>
    /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
    /// <param name="metadataResult">
    /// The result returned from <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> if that was used to inferring metadata before creating the final RequestDelegate.
    /// If <see langword="null"/>, this call to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/> method will infer the metadata that
    /// <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> would have inferred for the same <see cref="Delegate.Method"/> and populate <see cref="RequestDelegateFactoryOptions.EndpointBuilder"/>
    /// with that metadata. Otherwise, this metadata inference will be skipped as this step has already been done.
    /// </param>
    /// <returns>The <see cref="RequestDelegateResult"/>.</returns>
    [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
    public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null)
    {
        ArgumentNullException.ThrowIfNull(handler);

        var targetExpression = handler.Target switch
        {
            object => Expression.Convert(TargetExpr, handler.Target.GetType()),
            null => null,
        };

        var factoryContext = CreateFactoryContext(options, metadataResult, handler);

        Expression<Func<HttpContext, object?>> targetFactory = (httpContext) => handler.Target;
        var targetableRequestDelegate = CreateTargetableRequestDelegate(handler.Method, targetExpression, factoryContext, targetFactory);

        RequestDelegate finalRequestDelegate = targetableRequestDelegate switch
        {
            // handler is a RequestDelegate that has not been modified by a filter. Short-circuit and return the original RequestDelegate back.
            // It's possible a filter factory has still modified the endpoint metadata though.
            null => (RequestDelegate)handler,
            _ => httpContext => targetableRequestDelegate(handler.Target, httpContext),
        };

        return CreateRequestDelegateResult(finalRequestDelegate, factoryContext.EndpointBuilder);
    }

    /// <summary>
    /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
    /// </summary>
    /// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
    /// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
    /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
    /// <returns>The <see cref="RequestDelegate"/>.</returns>
    public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory, RequestDelegateFactoryOptions? options)
    {
        return Create(methodInfo, targetFactory, options, metadataResult: null);
    }

    /// <summary>
    /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
    /// </summary>
    /// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
    /// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
    /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
    /// <param name="metadataResult">
    /// The result returned from <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> if that was used to inferring metadata before creating the final RequestDelegate.
    /// If <see langword="null"/>, this call to <see cref="Create(Delegate, RequestDelegateFactoryOptions?, RequestDelegateMetadataResult?)"/> method will infer the metadata that
    /// <see cref="InferMetadata(MethodInfo, RequestDelegateFactoryOptions?)"/> would have inferred for the same <see cref="Delegate.Method"/> and populate <see cref="RequestDelegateFactoryOptions.EndpointBuilder"/>
    /// with that metadata. Otherwise, this metadata inference will be skipped as this step has already been done.
    /// </param>
    /// <returns>The <see cref="RequestDelegate"/>.</returns>
    [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
    public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory = null, RequestDelegateFactoryOptions? options = null, RequestDelegateMetadataResult? metadataResult = null)
    {
        ArgumentNullException.ThrowIfNull(methodInfo);

        if (methodInfo.DeclaringType is null)
        {
            throw new ArgumentException($"{nameof(methodInfo)} does not have a declaring type.");
        }

        var factoryContext = CreateFactoryContext(options, metadataResult);
        RequestDelegate finalRequestDelegate;

        if (methodInfo.IsStatic)
        {
            var untargetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression: null, factoryContext);

            // CreateTargetableRequestDelegate can only return null given a RequestDelegate passed into the other RDF.Create() overload.
            Debug.Assert(untargetableRequestDelegate is not null);

            finalRequestDelegate = httpContext => untargetableRequestDelegate(null, httpContext);
        }
        else
        {
            targetFactory ??= context => Activator.CreateInstance(methodInfo.DeclaringType)!;

            var targetExpression = Expression.Convert(TargetExpr, methodInfo.DeclaringType);
            var targetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression, factoryContext, context => targetFactory(context));

            // CreateTargetableRequestDelegate can only return null given a RequestDelegate passed into the other RDF.Create() overload.
            Debug.Assert(targetableRequestDelegate is not null);

            finalRequestDelegate = httpContext => targetableRequestDelegate(targetFactory(httpContext), httpContext);
        }

        return CreateRequestDelegateResult(finalRequestDelegate, factoryContext.EndpointBuilder);
    }

    private static RequestDelegateFactoryContext CreateFactoryContext(RequestDelegateFactoryOptions? options, RequestDelegateMetadataResult? metadataResult = null, Delegate? handler = null)
    {
        if (metadataResult?.CachedFactoryContext is RequestDelegateFactoryContext cachedFactoryContext)
        {
            cachedFactoryContext.MetadataAlreadyInferred = true;
            // The handler was not passed in to the InferMetadata call that originally created this context.
            cachedFactoryContext.Handler = handler;
            return cachedFactoryContext;
        }

        var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices ?? EmptyServiceProvider.Instance;
        var endpointBuilder = options?.EndpointBuilder ?? new RdfEndpointBuilder(serviceProvider);
        var jsonSerializerOptions = serviceProvider.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
        var formDataMapperOptions = new FormDataMapperOptions();;

        var factoryContext = new RequestDelegateFactoryContext
        {
            Handler = handler,
            ServiceProvider = serviceProvider,
            ServiceProviderIsService = serviceProvider.GetService<IServiceProviderIsService>(),
            RouteParameters = options?.RouteParameterNames,
            ThrowOnBadRequest = options?.ThrowOnBadRequest ?? false,
            DisableInferredFromBody = options?.DisableInferBodyFromParameters ?? false,
            EndpointBuilder = endpointBuilder,
            MetadataAlreadyInferred = metadataResult is not null,
            JsonSerializerOptions = jsonSerializerOptions,
            FormDataMapperOptions = formDataMapperOptions
        };

        return factoryContext;
    }

    private static RequestDelegateResult CreateRequestDelegateResult(RequestDelegate finalRequestDelegate, EndpointBuilder endpointBuilder)
    {
        endpointBuilder.RequestDelegate = finalRequestDelegate;
        return new RequestDelegateResult(finalRequestDelegate, AsReadOnlyList(endpointBuilder.Metadata));
    }

    private static IReadOnlyList<object> AsReadOnlyList(IList<object> metadata)
    {
        if (metadata is IReadOnlyList<object> readOnlyList)
        {
            return readOnlyList;
        }

        return new List<object>(metadata);
    }

    private static Func<object?, HttpContext, Task>? CreateTargetableRequestDelegate(
        MethodInfo methodInfo,
        Expression? targetExpression,
        RequestDelegateFactoryContext factoryContext,
        Expression<Func<HttpContext, object?>>? targetFactory = null)
    {
        // Non void return type

        // Task Invoke(HttpContext httpContext)
        // {
        //     // Action parameters are bound from the request, services, etc... based on attribute and type information.
        //     return ExecuteTask(handler(...), httpContext);
        // }

        // void return type

        // Task Invoke(HttpContext httpContext)
        // {
        //     handler(...);
        //     return default;
        // }

        // If ArgumentExpressions is not null here, it's guaranteed we have already inferred metadata and we can reuse a lot of work.
        // The converse is not true. Metadata may have already been inferred even if ArgumentExpressions is null, but metadata
        // inference is skipped internally if necessary.
        factoryContext.ArgumentExpressions ??= CreateArgumentsAndInferMetadata(methodInfo, factoryContext);

        // Although we can re-use the cached argument expressions for most cases, parameters that are bound
        // using the new form mapping logic are a special exception because we need to account for the `FormOptionsMetadata`
        // added to the builder _during_ the construction of the parameter binding.
        UpdateFormBindingArgumentExpressions(factoryContext);

        factoryContext.MethodCall = CreateMethodCall(methodInfo, targetExpression, factoryContext.ArgumentExpressions);
        EndpointFilterDelegate? filterPipeline = null;
        var returnType = methodInfo.ReturnType;

        // If there are filters registered on the route handler, then we update the method call and
        // return type associated with the request to allow for the filter invocation pipeline.
        if (factoryContext.EndpointBuilder.FilterFactories.Count > 0)
        {
            filterPipeline = CreateFilterPipeline(methodInfo, targetExpression, factoryContext, targetFactory);

            if (filterPipeline is not null)
            {
                Expression<Func<EndpointFilterInvocationContext, ValueTask<object?>>> invokePipeline = (context) => filterPipeline(context);
                returnType = typeof(ValueTask<object?>);
                // var filterContext = new EndpointFilterInvocationContext<string, int>(httpContext, name_local, int_local);
                // invokePipeline.Invoke(filterContext);
                factoryContext.MethodCall = Expression.Block(
                    new[] { InvokedFilterContextExpr },
                    Expression.Assign(
                        InvokedFilterContextExpr,
                        CreateEndpointFilterInvocationContextBase(factoryContext, factoryContext.ArgumentExpressions)),
                        Expression.Invoke(invokePipeline, InvokedFilterContextExpr)
                    );
            }
        }

        // return null for plain RequestDelegates that have not been modified by filters so we can just pass back the original RequestDelegate.
        if (filterPipeline is null && factoryContext.Handler is RequestDelegate)
        {
            return null;
        }

        var responseWritingMethodCall = factoryContext.ParamCheckExpressions.Count > 0 ?
            CreateParamCheckingResponseWritingMethodCall(returnType, factoryContext) :
            AddResponseWritingToMethodCall(factoryContext.MethodCall, returnType, factoryContext);

        if (factoryContext.UsingTempSourceString)
        {
            responseWritingMethodCall = Expression.Block(new[] { TempSourceStringExpr }, responseWritingMethodCall);
        }

        return HandleRequestBodyAndCompileRequestDelegate(responseWritingMethodCall, factoryContext);
    }

    private static Expression[] CreateArgumentsAndInferMetadata(MethodInfo methodInfo, RequestDelegateFactoryContext factoryContext)
    {
        // Add any default accepts metadata. This does a lot of reflection and expression tree building, so the results are cached in RequestDelegateFactoryOptions.FactoryContext
        // For later reuse in Create().
        var args = CreateArguments(methodInfo.GetParameters(), factoryContext);

        if (!factoryContext.MetadataAlreadyInferred)
        {
            if (factoryContext.ReadForm)
            {
                // Add the Accepts metadata when reading from FORM.
                InferFormAcceptsMetadata(factoryContext);
                InferAntiforgeryMetadata(factoryContext);
            }

            PopulateBuiltInResponseTypeMetadata(methodInfo.ReturnType, factoryContext.EndpointBuilder);

            // Add metadata provided by the delegate return type and parameter types next, this will be more specific than inferred metadata from above
            EndpointMetadataPopulator.PopulateMetadata(methodInfo, factoryContext.EndpointBuilder, factoryContext.Parameters);
        }

        return args;
    }

    private static EndpointFilterDelegate? CreateFilterPipeline(MethodInfo methodInfo, Expression? targetExpression, RequestDelegateFactoryContext factoryContext, Expression<Func<HttpContext, object?>>? targetFactory)
    {
        Debug.Assert(factoryContext.EndpointBuilder.FilterFactories.Count > 0);
        // httpContext.Response.StatusCode >= 400
        // ? Task.CompletedTask
        // : {
        //   handlerInvocation
        // }
        // To generate the handler invocation, we first create the
        // target of the handler provided to the route.
        //      target = targetFactory(httpContext);
        // This target is then used to generate the handler invocation like so;
        //      ((Type)target).MethodName(parameters);
        //  When `handler` returns an object, we generate the following wrapper
        //  to convert it to `ValueTask<object?>` as expected in the filter
        //  pipeline.
        //      ValueTask<object?>.FromResult(handler(EndpointFilterInvocationContext.GetArgument<string>(0), EndpointFilterInvocationContext.GetArgument<int>(1)));
        //  When the `handler` is a generic Task or ValueTask we await the task and
        //  create a `ValueTask<object?> from the resulting value.
        //      new ValueTask<object?>(await handler(EndpointFilterInvocationContext.GetArgument<string>(0), EndpointFilterInvocationContext.GetArgument<int>(1)));
        //  When the `handler` returns a void or a void-returning Task, then we return an EmptyHttpResult
        //  to as a ValueTask<object?>
        // }

        var argTypes = factoryContext.ArgumentTypes;
        var contextArgAccess = new Expression[argTypes.Length];

        for (var i = 0; i < argTypes.Length; i++)
        {
            // MakeGenericMethod + value type requires IsDynamicCodeSupported to be true.
            if (RuntimeFeature.IsDynamicCodeSupported)
            {
                // Register expressions containing the boxed and unboxed variants
                // of the route handler's arguments for use in EndpointFilterInvocationContext
                // construction and route handler invocation.
                // context.GetArgument<string>(0)
                // (string, name_local), (int, int_local)
                contextArgAccess[i] = Expression.Call(FilterContextExpr, EndpointFilterInvocationContextGetArgument.MakeGenericMethod(argTypes[i]), Expression.Constant(i));
            }
            else
            {
                // We box if dynamic code isn't supported
                contextArgAccess[i] = Expression.Convert(
                    Expression.Property(FilterContextArgumentsExpr, ListIndexer, Expression.Constant(i)),
                argTypes[i]);
            }
        }

        var handlerReturnMapping = MapHandlerReturnTypeToValueTask(
                        targetExpression is null
                            ? Expression.Call(methodInfo, contextArgAccess)
                            : Expression.Call(targetExpression, methodInfo, contextArgAccess),
                        methodInfo.ReturnType);
        var handlerInvocation = Expression.Block(
                    new[] { TargetExpr },
                    targetFactory == null
                        ? Expression.Empty()
                        : Expression.Assign(TargetExpr, Expression.Invoke(targetFactory, FilterContextHttpContextExpr)),
                    handlerReturnMapping
                );
        var filteredInvocation = Expression.Lambda<EndpointFilterDelegate>(
            Expression.Condition(
                Expression.GreaterThanOrEqual(FilterContextHttpContextStatusCodeExpr, Expression.Constant(400)),
                EmptyHttpResultValueTaskExpr,
                handlerInvocation),
            FilterContextExpr).Compile();
        var routeHandlerContext = new EndpointFilterFactoryContext
        {
            MethodInfo = methodInfo,
            ApplicationServices = factoryContext.EndpointBuilder.ApplicationServices,
        };

        var initialFilteredInvocation = filteredInvocation;

        for (var i = factoryContext.EndpointBuilder.FilterFactories.Count - 1; i >= 0; i--)
        {
            var currentFilterFactory = factoryContext.EndpointBuilder.FilterFactories[i];
            filteredInvocation = currentFilterFactory(routeHandlerContext, filteredInvocation);
        }

        // The filter factories have run without modifying per-request behavior, we can skip running the pipeline.
        // If a plain old RequestDelegate was passed in (with no generic parameter), we can just return it back directly now.
        if (ReferenceEquals(initialFilteredInvocation, filteredInvocation))
        {
            return null;
        }

        return filteredInvocation;
    }

    private static Expression MapHandlerReturnTypeToValueTask(Expression methodCall, Type returnType)
    {
        if (returnType == typeof(void))
        {
            return Expression.Block(methodCall, EmptyHttpResultValueTaskExpr);
        }
        else if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
        {
            if (coercedAwaitableInfo.CoercerResultType is { } coercedType)
            {
                returnType = coercedType;
            }

            if (coercedAwaitableInfo.CoercerExpression is { } coercerExpression)
            {
                methodCall = Expression.Invoke(coercerExpression, methodCall);
            }

            if (returnType == typeof(Task))
            {
                return Expression.Call(ExecuteTaskWithEmptyResultMethod, methodCall);
            }
            else if (returnType == typeof(ValueTask))
            {
                return Expression.Call(ExecuteValueTaskWithEmptyResultMethod, methodCall);
            }
            else if (returnType == typeof(ValueTask<object?>))
            {
                return methodCall;
            }
            else if (returnType.IsGenericType &&
                         returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
            {
                var typeArg = coercedAwaitableInfo.AwaitableInfo.ResultType;
                return Expression.Call(ValueTaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
            }
            else if (returnType.IsGenericType &&
                        returnType.GetGenericTypeDefinition() == typeof(Task<>))
            {
                var typeArg = coercedAwaitableInfo.AwaitableInfo.ResultType;
                return Expression.Call(TaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
            }
        }

        if (returnType.IsValueType)
        {
            return Expression.Call(WrapObjectAsValueTaskMethod, Expression.Convert(methodCall, typeof(object)));
        }

        return Expression.Call(WrapObjectAsValueTaskMethod, methodCall);
    }

    private static ValueTask<object?> ValueTaskOfTToValueTaskOfObject<T>(ValueTask<T> valueTask)
    {
        static async ValueTask<object?> ExecuteAwaited(ValueTask<T> valueTask)
        {
            return await valueTask;
        }

        if (valueTask.IsCompletedSuccessfully)
        {
            return new ValueTask<object?>(valueTask.Result);
        }

        return ExecuteAwaited(valueTask);
    }

    private static ValueTask<object?> TaskOfTToValueTaskOfObject<T>(Task<T> task)
    {
        static async ValueTask<object?> ExecuteAwaited(Task<T> task)
        {
            return await task;
        }

        if (task.IsCompletedSuccessfully)
        {
            return new ValueTask<object?>(task.Result);
        }

        return ExecuteAwaited(task);
    }

    private static Expression CreateEndpointFilterInvocationContextBase(RequestDelegateFactoryContext factoryContext, Expression[] arguments)
    {
        // In the event that a constructor matching the arity of the
        // provided parameters is not found, we fall back to using the
        // non-generic implementation of EndpointFilterInvocationContext.
        Expression paramArray = factoryContext.BoxedArgs.Length > 0
            ? Expression.NewArrayInit(typeof(object), factoryContext.BoxedArgs)
            : Expression.Call(ArrayEmptyOfObjectMethod);
        var fallbackConstruction = Expression.New(
            DefaultEndpointFilterInvocationContextConstructor,
            new Expression[] { HttpContextExpr, paramArray });

        if (!RuntimeFeature.IsDynamicCodeSupported)
        {
            // For AOT platforms it's not possible to support the closed generic arguments that are based on the
            // parameter arguments dynamically (for value types). In that case, fallback to boxing the argument list.
            return fallbackConstruction;
        }

        var expandedArguments = new Expression[arguments.Length + 1];
        expandedArguments[0] = HttpContextExpr;
        arguments.CopyTo(expandedArguments, 1);

        var constructorType = factoryContext.ArgumentTypes?.Length switch
        {
            1 => typeof(EndpointFilterInvocationContext<>),
            2 => typeof(EndpointFilterInvocationContext<,>),
            3 => typeof(EndpointFilterInvocationContext<,,>),
            4 => typeof(EndpointFilterInvocationContext<,,,>),
            5 => typeof(EndpointFilterInvocationContext<,,,,>),
            6 => typeof(EndpointFilterInvocationContext<,,,,,>),
            7 => typeof(EndpointFilterInvocationContext<,,,,,,>),
            8 => typeof(EndpointFilterInvocationContext<,,,,,,,>),
            9 => typeof(EndpointFilterInvocationContext<,,,,,,,,>),
            10 => typeof(EndpointFilterInvocationContext<,,,,,,,,,>),
            _ => typeof(DefaultEndpointFilterInvocationContext)
        };

        if (constructorType.IsGenericType)
        {
            var constructor = constructorType.MakeGenericType(factoryContext.ArgumentTypes!).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).SingleOrDefault();
            if (constructor == null)
            {
                // new EndpointFilterInvocationContext(httpContext, (object)name_local, (object)int_local);
                return fallbackConstruction;
            }

            // new EndpointFilterInvocationContext<string, int>(httpContext, name_local, int_local);
            return Expression.New(constructor, expandedArguments);
        }

        // new EndpointFilterInvocationContext(httpContext, (object)name_local, (object)int_local);
        return fallbackConstruction;
    }

    private static Expression[] CreateArguments(ParameterInfo[]? parameters, RequestDelegateFactoryContext factoryContext)
    {
        if (parameters is null || parameters.Length == 0)
        {
            return Array.Empty<Expression>();
        }

        var args = new Expression[parameters.Length];

        factoryContext.ArgumentTypes = new Type[parameters.Length];
        factoryContext.BoxedArgs = new Expression[parameters.Length];
        factoryContext.Parameters = new List<ParameterInfo>(parameters);

        for (var i = 0; i < parameters.Length; i++)
        {
            args[i] = CreateArgument(parameters[i], factoryContext, out var hasTryParse, out var hasBindAsync, out var isAsParameters);

            if (!isAsParameters)
            {
                factoryContext.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata(
                    name: parameters[i].Name!,
                    parameterInfo: parameters[i],
                    hasTryParse: hasTryParse,
                    hasBindAsync: hasBindAsync,
                    isOptional: IsOptionalParameter(parameters[i], factoryContext)
                ));
            }
            factoryContext.ArgumentTypes[i] = parameters[i].ParameterType;
            factoryContext.BoxedArgs[i] = Expression.Convert(args[i], typeof(object));
        }

        if (factoryContext.HasInferredBody && factoryContext.DisableInferredFromBody)
        {
            var errorMessage = BuildErrorMessageForInferredBodyParameter(factoryContext);
            throw new InvalidOperationException(errorMessage);
        }

        if (factoryContext.JsonRequestBodyParameter is not null &&
            factoryContext.FirstFormRequestBodyParameter is not null)
        {
            var errorMessage = BuildErrorMessageForFormAndJsonBodyParameters(factoryContext);
            throw new InvalidOperationException(errorMessage);
        }
        if (factoryContext.HasMultipleBodyParameters)
        {
            var errorMessage = BuildErrorMessageForMultipleBodyParameters(factoryContext);
            throw new InvalidOperationException(errorMessage);
        }

        return args;
    }

    private static Expression CreateArgument(ParameterInfo parameter, RequestDelegateFactoryContext factoryContext, out bool hasTryParse, out bool hasBindAsync, out bool isAsParameters)
    {
        hasTryParse = false;
        hasBindAsync = false;
        isAsParameters = false;
        if (parameter.Name is null)
        {
            throw new InvalidOperationException($"Encountered a parameter of type '{parameter.ParameterType}' without a name. Parameters must have a name.");
        }

        if (parameter.ParameterType.IsByRef)
        {
            var attribute = "ref";

            if (parameter.Attributes.HasFlag(ParameterAttributes.In))
            {
                attribute = "in";
            }
            else if (parameter.Attributes.HasFlag(ParameterAttributes.Out))
            {
                attribute = "out";
            }

            throw new NotSupportedException($"The by reference parameter '{attribute} {TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false)} {parameter.Name}' is not supported.");
        }

        var parameterCustomAttributes = parameter.GetCustomAttributes();

        if (parameterCustomAttributes.OfType<IFromRouteMetadata>().FirstOrDefault() is { } routeAttribute)
        {
            var routeName = routeAttribute.Name ?? parameter.Name;
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteAttribute);
            if (factoryContext.RouteParameters is { } routeParams && !routeParams.Contains(routeName, StringComparer.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException($"'{routeName}' is not a route parameter.");
            }

            return BindParameterFromProperty(parameter, RouteValuesExpr, RouteValuesIndexerProperty, routeName, factoryContext, "route");
        }
        else if (parameterCustomAttributes.OfType<IFromQueryMetadata>().FirstOrDefault() is { } queryAttribute)
        {
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryAttribute);
            return BindParameterFromProperty(parameter, QueryExpr, QueryIndexerProperty, queryAttribute.Name ?? parameter.Name, factoryContext, "query string");
        }
        else if (parameterCustomAttributes.OfType<IFromHeaderMetadata>().FirstOrDefault() is { } headerAttribute)
        {
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.HeaderAttribute);
            return BindParameterFromProperty(parameter, HeadersExpr, HeaderIndexerProperty, headerAttribute.Name ?? parameter.Name, factoryContext, "header");
        }
        else if (parameterCustomAttributes.OfType<IFromBodyMetadata>().FirstOrDefault() is { } bodyAttribute)
        {
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyAttribute);

            if (parameter.ParameterType == typeof(Stream))
            {
                return RequestStreamExpr;
            }
            else if (parameter.ParameterType == typeof(PipeReader))
            {
                return RequestPipeReaderExpr;
            }

            return BindParameterFromBody(parameter, bodyAttribute.AllowEmpty, factoryContext);
        }
        else if (parameterCustomAttributes.OfType<IFromFormMetadata>().FirstOrDefault() is { } formAttribute)
        {
            if (parameter.ParameterType == typeof(IFormFileCollection))
            {
                if (!string.IsNullOrEmpty(formAttribute.Name))
                {
                    throw new NotSupportedException(
                        $"Assigning a value to the {nameof(IFromFormMetadata)}.{nameof(IFromFormMetadata.Name)} property is not supported for parameters of type {nameof(IFormFileCollection)}.");
                }

                return BindParameterFromFormFiles(parameter, factoryContext);
            }
            else if (parameter.ParameterType == typeof(IFormFile))
            {
                return BindParameterFromFormFile(parameter, formAttribute.Name ?? parameter.Name, factoryContext, RequestDelegateFactoryConstants.FormFileAttribute);
            }
            else if (parameter.ParameterType == typeof(IFormCollection))
            {
                if (!string.IsNullOrEmpty(formAttribute.Name))
                {
                    throw new NotSupportedException(
                        $"Assigning a value to the {nameof(IFromFormMetadata)}.{nameof(IFromFormMetadata.Name)} property is not supported for parameters of type {nameof(IFormCollection)}.");

                }
                return BindParameterFromFormCollection(parameter, factoryContext);
            }
            // Continue to use the simple binding support that exists in RDF/RDG for currently
            // supported scenarios to maintain compatible semantics between versions of RDG.
            // For complex types, leverage the shared form binding infrastructure. For example,
            // shared form binding does not currently only supports types that implement IParsable
            // while RDF's binding implementation supports all TryParse implementations.
            var useSimpleBinding = parameter.ParameterType == typeof(string) ||
                parameter.ParameterType == typeof(StringValues) ||
                parameter.ParameterType == typeof(StringValues?) ||
                ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType) ||
                (parameter.ParameterType.IsArray && ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType.GetElementType()!));
            hasTryParse = useSimpleBinding;
            return useSimpleBinding
                ? BindParameterFromFormItem(parameter, formAttribute.Name ?? parameter.Name, factoryContext)
                : BindComplexParameterFromFormItem(parameter, string.IsNullOrEmpty(formAttribute.Name) ? parameter.Name : formAttribute.Name, factoryContext);
        }
        else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)))
        {
            if (parameterCustomAttributes.OfType<FromKeyedServicesAttribute>().FirstOrDefault() is not null)
            {
                throw new NotSupportedException(
                    $"The {nameof(FromKeyedServicesAttribute)} is not supported on parameters that are also annotated with {nameof(IFromServiceMetadata)}.");
            }
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceAttribute);
            return BindParameterFromService(parameter, factoryContext);
        }
        else if (parameterCustomAttributes.OfType<FromKeyedServicesAttribute>().FirstOrDefault() is { } keyedServicesAttribute)
        {
            if (factoryContext.ServiceProviderIsService is not IServiceProviderIsKeyedService)
            {
                throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services.");
            }
            var key = keyedServicesAttribute.Key;
            return BindParameterFromKeyedService(parameter, key, factoryContext);
        }
        else if (parameterCustomAttributes.OfType<AsParametersAttribute>().Any())
        {
            isAsParameters = true;
            if (parameter is PropertyAsParameterInfo)
            {
                throw new NotSupportedException(
                    $"Nested {nameof(AsParametersAttribute)} is not supported and should be used only for handler parameters.");
            }

            return BindParameterFromProperties(parameter, factoryContext);
        }
        else if (parameter.ParameterType == typeof(HttpContext))
        {
            return HttpContextExpr;
        }
        else if (parameter.ParameterType == typeof(HttpRequest))
        {
            return HttpRequestExpr;
        }
        else if (parameter.ParameterType == typeof(HttpResponse))
        {
            return HttpResponseExpr;
        }
        else if (parameter.ParameterType == typeof(ClaimsPrincipal))
        {
            return UserExpr;
        }
        else if (parameter.ParameterType == typeof(CancellationToken))
        {
            return RequestAbortedExpr;
        }
        else if (parameter.ParameterType == typeof(IFormCollection))
        {
            return BindParameterFromFormCollection(parameter, factoryContext);
        }
        else if (parameter.ParameterType == typeof(IFormFileCollection))
        {
            return BindParameterFromFormFiles(parameter, factoryContext);
        }
        else if (parameter.ParameterType == typeof(IFormFile))
        {
            return BindParameterFromFormFile(parameter, parameter.Name, factoryContext, RequestDelegateFactoryConstants.FormFileParameter);
        }
        else if (parameter.ParameterType == typeof(Stream))
        {
            return RequestStreamExpr;
        }
        else if (parameter.ParameterType == typeof(PipeReader))
        {
            return RequestPipeReaderExpr;
        }
        else if (ParameterBindingMethodCache.Instance.HasBindAsyncMethod(parameter))
        {
            hasBindAsync = true;
            return BindParameterFromBindAsync(parameter, factoryContext);
        }
        else if (parameter.ParameterType == typeof(string) || ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType))
        {
            hasTryParse = true;
            // 1. We bind from route values only, if route parameters are non-null and the parameter name is in that set.
            // 2. We bind from query only, if route parameters are non-null and the parameter name is NOT in that set.
            // 3. Otherwise, we fallback to route or query if route parameters is null (it means we don't know what route parameters are defined). This case only happens
            // when RDF.Create is manually invoked.
            if (factoryContext.RouteParameters is { } routeParams)
            {
                if (routeParams.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase))
                {
                    // We're in the fallback case and we have a parameter and route parameter match so don't fallback
                    // to query string in this case
                    factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteParameter);
                    return BindParameterFromProperty(parameter, RouteValuesExpr, RouteValuesIndexerProperty, parameter.Name, factoryContext, "route");
                }
                else
                {
                    factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryStringParameter);
                    return BindParameterFromProperty(parameter, QueryExpr, QueryIndexerProperty, parameter.Name, factoryContext, "query string");
                }
            }

            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteOrQueryStringParameter);
            return BindParameterFromRouteValueOrQueryString(parameter, parameter.Name, factoryContext);
        }
        else if (factoryContext.DisableInferredFromBody && (
            parameter.ParameterType == typeof(string[]) ||
                 parameter.ParameterType == typeof(StringValues) ||
                 parameter.ParameterType == typeof(StringValues?) ||
                (parameter.ParameterType.IsArray && ParameterBindingMethodCache.Instance.HasTryParseMethod(parameter.ParameterType.GetElementType()!))))
        {
            // We only infer parameter types if you have an array of TryParsables/string[]/StringValues/StringValues?, and DisableInferredFromBody is true
            hasTryParse = true;
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryStringParameter);
            return BindParameterFromProperty(parameter, QueryExpr, QueryIndexerProperty, parameter.Name, factoryContext, "query string");
        }
        else
        {
            if (factoryContext.ServiceProviderIsService is IServiceProviderIsService serviceProviderIsService)
            {
                if (serviceProviderIsService.IsService(parameter.ParameterType))
                {
                    factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceParameter);
                    return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
                }
            }

            factoryContext.HasInferredBody = true;
            factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyParameter);
            return BindParameterFromBody(parameter, allowEmpty: false, factoryContext);
        }
    }

    private static Expression CreateMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments) =>
        target is null ?
            Expression.Call(methodInfo, arguments) :
            Expression.Call(target, methodInfo, arguments);

    private static ValueTask<object?> WrapObjectAsValueTask(object? obj)
    {
        return ValueTask.FromResult<object?>(obj);
    }

    // If we're calling TryParse or validating parameter optionality and
    // wasParamCheckFailure indicates it failed, set a 400 StatusCode instead of calling the method.
    private static Expression CreateParamCheckingResponseWritingMethodCall(Type returnType, RequestDelegateFactoryContext factoryContext)
    {
        // {
        //     string tempSourceString;
        //     bool wasParamCheckFailure = false;
        //
        //     // Assume "int param1" is the first parameter, "[FromRoute] int? param2 = 42" is the second parameter ...
        //     int param1_local;
        //     int? param2_local;
        //     // ...
        //
        //     tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
        //
        //     if (tempSourceString != null)
        //     {
        //         if (!int.TryParse(tempSourceString, out param1_local))
        //         {
        //             wasParamCheckFailure = true;
        //             Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
        //         }
        //     }
        //
        //     tempSourceString = httpContext.RouteValue["param2"];
        //     // ...
        //
        //     return wasParamCheckFailure ?
        //         {
        //              httpContext.Response.StatusCode = 400;
        //              return Task.CompletedTask;
        //         } :
        //         {
        //             // Logic generated by AddResponseWritingToMethodCall() that calls handler(param1_local, param2_local, ...)
        //         };
        // }

        var localVariables = new ParameterExpression[factoryContext.ExtraLocals.Count + 1];
        var checkParamAndCallMethod = new Expression[factoryContext.ParamCheckExpressions.Count + 1];

        for (var i = 0; i < factoryContext.ExtraLocals.Count; i++)
        {
            localVariables[i] = factoryContext.ExtraLocals[i];
        }

        for (var i = 0; i < factoryContext.ParamCheckExpressions.Count; i++)
        {
            checkParamAndCallMethod[i] = factoryContext.ParamCheckExpressions[i];
        }

        localVariables[factoryContext.ExtraLocals.Count] = WasParamCheckFailureExpr;

        // If filters have been registered, we set the `wasParamCheckFailure` property
        // but do not return from the invocation to allow the filters to run.
        if (factoryContext.EndpointBuilder.FilterFactories.Count > 0)
        {
            // if (wasParamCheckFailure)
            // {
            //   httpContext.Response.StatusCode = 400;
            // }
            // return RequestDelegateFactory.ExecuteObjectReturn(invocationPipeline.Invoke(context) as object);
            var checkWasParamCheckFailureWithFilters = Expression.Block(
                Expression.IfThen(
                    WasParamCheckFailureExpr,
                    Expression.Assign(StatusCodeExpr, Expression.Constant(400))),
                AddResponseWritingToMethodCall(factoryContext.MethodCall!, returnType, factoryContext)
            );

            checkParamAndCallMethod[factoryContext.ParamCheckExpressions.Count] = checkWasParamCheckFailureWithFilters;
        }
        else
        {
            // wasParamCheckFailure ? {
            //  httpContext.Response.StatusCode = 400;
            //  return Task.CompletedTask;
            // } : {
            //  return RequestDelegateFactory.ExecuteObjectReturn(invocationPipeline.Invoke(context) as object);
            // }
            var checkWasParamCheckFailure = Expression.Condition(
                WasParamCheckFailureExpr,
                Expression.Block(
                    Expression.Assign(StatusCodeExpr, Expression.Constant(400)),
                    CompletedTaskExpr),
                AddResponseWritingToMethodCall(factoryContext.MethodCall!, returnType, factoryContext));
            checkParamAndCallMethod[factoryContext.ParamCheckExpressions.Count] = checkWasParamCheckFailure;
        }

        return Expression.Block(localVariables, checkParamAndCallMethod);
    }

    private static void PopulateBuiltInResponseTypeMetadata(Type returnType, EndpointBuilder builder)
    {
        if (returnType.IsByRefLike)
        {
            throw GetUnsupportedReturnTypeException(returnType);
        }

        var isAwaitable = false;
        if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
        {
            returnType = coercedAwaitableInfo.AwaitableInfo.ResultType;
            isAwaitable = true;
        }

        // Skip void returns and IResults. IResults might implement IEndpointMetadataProvider but otherwise we don't know what it might do.
        if (!isAwaitable && (returnType == typeof(void) || typeof(IResult).IsAssignableFrom(returnType)))
        {
            return;
        }

        if (returnType == typeof(string))
        {
            builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(type: typeof(string), statusCode: 200, PlaintextContentType));
        }
        else if (returnType == typeof(void))
        {
            builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(returnType, statusCode: 200, PlaintextContentType));
        }
        else
        {
            builder.Metadata.Add(ProducesResponseTypeMetadata.CreateUnvalidated(returnType, statusCode: 200, DefaultAcceptsAndProducesContentType));
        }
    }

    private static Expression AddResponseWritingToMethodCall(Expression methodCall, Type returnType, RequestDelegateFactoryContext factoryContext)
    {
        // Exact request delegate match
        if (returnType == typeof(void))
        {
            return Expression.Block(methodCall, CompletedTaskExpr);
        }
        else if (returnType == typeof(object))
        {
            return Expression.Call(
                ExecuteAwaitedReturnMethod,
                methodCall,
                HttpContextExpr,
                Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
        }
        else if (returnType == typeof(ValueTask<object>))
        {
            return Expression.Call(ExecuteValueTaskOfObjectMethod,
                methodCall,
                HttpContextExpr,
                Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
        }
        else if (returnType == typeof(Task<object>))
        {
            return Expression.Call(ExecuteTaskOfObjectMethod,
                methodCall,
                HttpContextExpr,
                Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
        }
        else if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
        {
            if (coercedAwaitableInfo.CoercerResultType is { } coercedType)
            {
                returnType = coercedType;
            }

            if (coercedAwaitableInfo.CoercerExpression is { } coercerExpression)
            {
                methodCall = Expression.Invoke(coercerExpression, methodCall);
            }

            if (returnType == typeof(Task))
            {
                return methodCall;
            }
            else if (returnType == typeof(ValueTask))
            {
                return Expression.Call(
                    ExecuteValueTaskMethod,
                    methodCall);
            }
            else if (returnType.IsGenericType &&
                     returnType.GetGenericTypeDefinition() == typeof(Task<>))
            {
                var typeArg = returnType.GetGenericArguments()[0];

                if (typeof(IResult).IsAssignableFrom(typeArg))
                {
                    return Expression.Call(
                        ExecuteTaskResultOfTMethod.MakeGenericMethod(typeArg),
                        methodCall,
                        HttpContextExpr);
                }
                // ExecuteTask<T>(handler(..), httpContext);
                else if (typeArg == typeof(string))
                {
                    return Expression.Call(
                        ExecuteTaskOfStringMethod,
                        methodCall,
                        HttpContextExpr);
                }
                else if (typeArg == typeof(object))
                {
                    return Expression.Call(
                        ExecuteTaskOfObjectMethod,
                        methodCall,
                        HttpContextExpr,
                        Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
                }
                else
                {
                    var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg);

                    if (jsonTypeInfo.HasKnownPolymorphism())
                    {
                        return Expression.Call(
                            ExecuteTaskOfTFastMethod.MakeGenericMethod(typeArg),
                            methodCall,
                            HttpContextExpr,
                            Expression.Constant(jsonTypeInfo, typeof(JsonTypeInfo<>).MakeGenericType(typeArg)));
                    }

                    return Expression.Call(
                        ExecuteTaskOfTMethod.MakeGenericMethod(typeArg),
                        methodCall,
                        HttpContextExpr,
                        Expression.Constant(jsonTypeInfo, typeof(JsonTypeInfo<>).MakeGenericType(typeArg)));
                }
            }
            else if (returnType.IsGenericType &&
                     returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
            {
                var typeArg = returnType.GetGenericArguments()[0];

                if (typeof(IResult).IsAssignableFrom(typeArg))
                {
                    return Expression.Call(
                        ExecuteValueResultTaskOfTMethod.MakeGenericMethod(typeArg),
                        methodCall,
                        HttpContextExpr);
                }
                // ExecuteTask<T>(handler(..), httpContext);
                else if (typeArg == typeof(string))
                {
                    return Expression.Call(
                        ExecuteValueTaskOfStringMethod,
                        methodCall,
                        HttpContextExpr);
                }
                else if (typeArg == typeof(object))
                {
                    return Expression.Call(
                        ExecuteValueTaskOfObjectMethod,
                        methodCall,
                        HttpContextExpr,
                        Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
                }
                else
                {
                    var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg);

                    if (jsonTypeInfo.HasKnownPolymorphism())
                    {
                        return Expression.Call(
                            ExecuteValueTaskOfTFastMethod.MakeGenericMethod(typeArg),
                            methodCall,
                            HttpContextExpr,
                            Expression.Constant(jsonTypeInfo, typeof(JsonTypeInfo<>).MakeGenericType(typeArg)));
                    }

                    return Expression.Call(
                        ExecuteValueTaskOfTMethod.MakeGenericMethod(typeArg),
                        methodCall,
                        HttpContextExpr,
                        Expression.Constant(jsonTypeInfo, typeof(JsonTypeInfo<>).MakeGenericType(typeArg)));
                }
            }
            else
            {
                // TODO: Handle custom awaitables
                throw GetUnsupportedReturnTypeException(returnType);
            }
        }
        else if (typeof(IResult).IsAssignableFrom(returnType))
        {
            if (returnType.IsValueType)
            {
                var box = Expression.TypeAs(methodCall, typeof(IResult));
                return Expression.Call(ResultWriteResponseAsyncMethod, box, HttpContextExpr);
            }
            return Expression.Call(ResultWriteResponseAsyncMethod, methodCall, HttpContextExpr);
        }
        else if (returnType == typeof(string))
        {
            return Expression.Call(StringResultWriteResponseAsyncMethod, HttpContextExpr, methodCall);
        }
        else if (returnType.IsByRefLike)
        {
            throw GetUnsupportedReturnTypeException(returnType);
        }
        else
        {
            var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(returnType);

            if (jsonTypeInfo.HasKnownPolymorphism())
            {
                return Expression.Call(
                    JsonResultWriteResponseOfTFastAsyncMethod.MakeGenericMethod(returnType),
                    HttpResponseExpr,
                    methodCall,
                    Expression.Constant(jsonTypeInfo, typeof(JsonTypeInfo<>).MakeGenericType(returnType)));

            }

            return Expression.Call(
                JsonResultWriteResponseOfTAsyncMethod.MakeGenericMethod(returnType),
                HttpResponseExpr,
                methodCall,
                Expression.Constant(jsonTypeInfo, typeof(JsonTypeInfo<>).MakeGenericType(returnType)));
        }
    }

    private static Func<object?, HttpContext, Task> HandleRequestBodyAndCompileRequestDelegate(Expression responseWritingMethodCall, RequestDelegateFactoryContext factoryContext)
    {
        if (factoryContext.JsonRequestBodyParameter is null && !factoryContext.ReadForm)
        {
            if (factoryContext.ParameterBinders.Count > 0)
            {
                // We need to generate the code for reading from the custom binders calling into the delegate
                var continuation = Expression.Lambda<Func<object?, HttpContext, object?[], Task>>(
                    responseWritingMethodCall, TargetExpr, HttpContextExpr, BoundValuesArrayExpr).Compile();

                // Looping over arrays is faster
                var binders = factoryContext.ParameterBinders.ToArray();
                var count = binders.Length;

                return async (target, httpContext) =>
                {
                    var boundValues = new object?[count];

                    for (var i = 0; i < count; i++)
                    {
                        boundValues[i] = await binders[i](httpContext);
                    }

                    await continuation(target, httpContext, boundValues);
                };
            }

            return Expression.Lambda<Func<object?, HttpContext, Task>>(
                responseWritingMethodCall, TargetExpr, HttpContextExpr).Compile();
        }

        if (factoryContext.ReadForm)
        {
            return HandleRequestBodyAndCompileRequestDelegateForForm(responseWritingMethodCall, factoryContext);
        }
        else
        {
            return HandleRequestBodyAndCompileRequestDelegateForJson(responseWritingMethodCall, factoryContext);
        }
    }

    private static Func<object?, HttpContext, Task> HandleRequestBodyAndCompileRequestDelegateForJson(Expression responseWritingMethodCall, RequestDelegateFactoryContext factoryContext)
    {
        Debug.Assert(factoryContext.JsonRequestBodyParameter is not null, "factoryContext.JsonRequestBodyParameter is null for a JSON body.");

        var bodyType = factoryContext.JsonRequestBodyParameter.ParameterType;
        var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(bodyType);
        var parameterTypeName = TypeNameHelper.GetTypeDisplayName(factoryContext.JsonRequestBodyParameter.ParameterType, fullName: false);
        var parameterName = factoryContext.JsonRequestBodyParameter.Name;

        Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");

        if (factoryContext.ParameterBinders.Count > 0)
        {
            // We need to generate the code for reading from the body before calling into the delegate
            var continuation = Expression.Lambda<Func<object?, HttpContext, object?, object?[], Task>>(
            responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr, BoundValuesArrayExpr).Compile();

            // Looping over arrays is faster
            var binders = factoryContext.ParameterBinders.ToArray();
            var count = binders.Length;

            return async (target, httpContext) =>
            {
                // Run these first so that they can potentially read and rewind the body
                var boundValues = new object?[count];

                for (var i = 0; i < count; i++)
                {
                    boundValues[i] = await binders[i](httpContext);
                }

                var (bodyValue, successful) = await TryReadBodyAsync(
                    httpContext,
                    bodyType,
                    parameterTypeName,
                    parameterName,
                    factoryContext.AllowEmptyRequestBody,
                    factoryContext.ThrowOnBadRequest,
                    jsonTypeInfo);

                if (!successful)
                {
                    return;
                }

                await continuation(target, httpContext, bodyValue, boundValues);
            };
        }
        else
        {
            // We need to generate the code for reading from the body before calling into the delegate
            var continuation = Expression.Lambda<Func<object?, HttpContext, object?, Task>>(
            responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile();

            return async (target, httpContext) =>
            {
                var (bodyValue, successful) = await TryReadBodyAsync(
                    httpContext,
                    bodyType,
                    parameterTypeName,
                    parameterName,
                    factoryContext.AllowEmptyRequestBody,
                    factoryContext.ThrowOnBadRequest,
                    jsonTypeInfo);

                if (!successful)
                {
                    return;
                }

                await continuation(target, httpContext, bodyValue);
            };
        }

        static async Task<(object? FormValue, bool Successful)> TryReadBodyAsync(
            HttpContext httpContext,
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type bodyType,
            string parameterTypeName,
            string parameterName,
            bool allowEmptyRequestBody,
            bool throwOnBadRequest,
            JsonTypeInfo jsonTypeInfo)
        {
            object? defaultBodyValue = null;

            if (allowEmptyRequestBody && bodyType.IsValueType)
            {
                defaultBodyValue = CreateValueType(bodyType);
            }

            var bodyValue = defaultBodyValue;
            var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();

            if (feature?.CanHaveBody == true)
            {
                if (!httpContext.Request.HasJsonContentType())
                {
                    Log.UnexpectedJsonContentType(httpContext, httpContext.Request.ContentType, throwOnBadRequest);
                    httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
                    return (null, false);
                }
                try
                {
                    bodyValue = await httpContext.Request.ReadFromJsonAsync(jsonTypeInfo);
                }
                catch (BadHttpRequestException ex)
                {
                    Log.RequestBodyIOException(httpContext, ex);
                    httpContext.Response.StatusCode = ex.StatusCode;
                    return (null, false);
                }
                catch (IOException ex)
                {
                    Log.RequestBodyIOException(httpContext, ex);
                    httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                    return (null, false);
                }
                catch (JsonException ex)
                {
                    Log.InvalidJsonRequestBody(httpContext, parameterTypeName, parameterName, ex, throwOnBadRequest);
                    httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                    return (null, false);
                }
            }

            return (bodyValue, true);
        }
    }

    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
        Justification = "CreateValueType is only called on a ValueType. You can always create an instance of a ValueType.")]
    private static object? CreateValueType(Type t) => RuntimeHelpers.GetUninitializedObject(t);

    private static Func<object?, HttpContext, Task> HandleRequestBodyAndCompileRequestDelegateForForm(
        Expression responseWritingMethodCall,
        RequestDelegateFactoryContext factoryContext)
    {
        Debug.Assert(factoryContext.FirstFormRequestBodyParameter is not null, "factoryContext.FirstFormRequestBodyParameter is null for a form body.");

        // If there are multiple parameters associated with the form, just use the name of
        // the first one to report the failure to bind the parameter if reading the form fails.
        var parameterTypeName = TypeNameHelper.GetTypeDisplayName(factoryContext.FirstFormRequestBodyParameter.ParameterType, fullName: false);
        var parameterName = factoryContext.FirstFormRequestBodyParameter.Name;

        Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");

        if (factoryContext.ParameterBinders.Count > 0)
        {
            // We need to generate the code for reading from the body or form before calling into the delegate
            var continuation = Expression.Lambda<Func<object?, HttpContext, object?, object?[], Task>>(
            responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr, BoundValuesArrayExpr).Compile();

            // Looping over arrays is faster
            var binders = factoryContext.ParameterBinders.ToArray();
            var count = binders.Length;

            return async (target, httpContext) =>
            {
                // Run these first so that they can potentially read and rewind the body
                var boundValues = new object?[count];

                for (var i = 0; i < count; i++)
                {
                    boundValues[i] = await binders[i](httpContext);
                }

                var (formValue, successful) = await TryReadFormAsync(
                    httpContext,
                    parameterTypeName,
                    parameterName,
                    factoryContext.ThrowOnBadRequest);

                if (!successful)
                {
                    return;
                }

                await continuation(target, httpContext, formValue, boundValues);
            };
        }
        else
        {
            // We need to generate the code for reading from the form before calling into the delegate
            var continuation = Expression.Lambda<Func<object?, HttpContext, object?, Task>>(
            responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile();

            return async (target, httpContext) =>
            {
                var (formValue, successful) = await TryReadFormAsync(
                    httpContext,
                    parameterTypeName,
                    parameterName,
                    factoryContext.ThrowOnBadRequest);

                if (!successful)
                {
                    return;
                }

                await continuation(target, httpContext, formValue);
            };
        }

        static async Task<(object? FormValue, bool Successful)> TryReadFormAsync(
            HttpContext httpContext,
            string parameterTypeName,
            string parameterName,
            bool throwOnBadRequest)
        {
            object? formValue = null;
            var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();

            if (feature?.CanHaveBody == false)
            {
                Log.UnexpectedRequestWithoutBody(httpContext, parameterTypeName, parameterName, throwOnBadRequest);
                httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                return (null, false);
            }

            if (httpContext.Features.Get<IAntiforgeryValidationFeature>() is { IsValid: false } antiforgeryValidationFeature)
            {
                Log.InvalidAntiforgeryToken(httpContext, parameterTypeName, parameterName, antiforgeryValidationFeature.Error!, throwOnBadRequest);
                httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                return (null, false);
            }

            if (!httpContext.Request.HasFormContentType)
            {
                Log.UnexpectedNonFormContentType(httpContext, httpContext.Request.ContentType, throwOnBadRequest);
                httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
                return (null, false);
            }

            try
            {
                formValue = await httpContext.Request.ReadFormAsync();
            }
            catch (BadHttpRequestException ex)
            {
                Log.RequestBodyIOException(httpContext, ex);
                httpContext.Response.StatusCode = ex.StatusCode;
                return (null, false);
            }
            catch (IOException ex)
            {
                Log.RequestBodyIOException(httpContext, ex);
                httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                return (null, false);
            }
            catch (InvalidDataException ex)
            {
                Log.InvalidFormRequestBody(httpContext, parameterTypeName, parameterName, ex, throwOnBadRequest);
                httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                return (null, false);
            }

            return (formValue, true);
        }
    }

    private static Expression GetValueFromProperty(MemberExpression sourceExpression, PropertyInfo itemProperty, string key, Type? returnType = null)
    {
        var indexArguments = new[] { Expression.Constant(key) };
        var indexExpression = Expression.MakeIndex(sourceExpression, itemProperty, indexArguments);
        return Expression.Convert(indexExpression, returnType ?? typeof(string));
    }

    private static Expression BindParameterFromProperties(ParameterInfo parameter, RequestDelegateFactoryContext factoryContext)
    {
        var parameterType = parameter.ParameterType;
        var isNullable = Nullable.GetUnderlyingType(parameterType) != null ||
            factoryContext.NullabilityContext.Create(parameter)?.ReadState == NullabilityState.Nullable;

        if (isNullable)
        {
            throw new InvalidOperationException($"The nullable type '{TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false)}' is not supported, mark the parameter as non-nullable.");
        }

        var argumentExpression = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");
        var (constructor, parameters) = ParameterBindingMethodCache.Instance.FindConstructor(parameterType);

        Expression initExpression;

        if (constructor is not null && parameters is { Length: > 0 })
        {
            //  arg_local = new T(....)

            var constructorArguments = new Expression[parameters.Length];

            for (var i = 0; i < parameters.Length; i++)
            {
                var parameterInfo =
                    new PropertyAsParameterInfo(parameters[i].PropertyInfo, parameters[i].ParameterInfo, factoryContext.NullabilityContext);
                Debug.Assert(parameterInfo.Name != null, "Parameter name must be set for parameters resolved from properties.");
                constructorArguments[i] = CreateArgument(parameterInfo, factoryContext, out var hasTryParse, out var hasBindAsync, out var _);
                factoryContext.Parameters.Add(parameterInfo);
                factoryContext.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata(parameterInfo.Name, parameterInfo, hasTryParse: hasTryParse, hasBindAsync: hasBindAsync, isOptional: parameterInfo.IsOptional));
            }

            initExpression = Expression.New(constructor, constructorArguments);
        }
        else
        {
            //  arg_local = new T()
            //  {
            //      arg_local.Property[0] = expression[0],
            //      arg_local.Property[n] = expression[n],
            //  }

            var properties = parameterType.GetProperties();
            var bindings = new List<MemberBinding>(properties.Length);

            for (var i = 0; i < properties.Length; i++)
            {
                // For parameterless ctor we will init only writable properties.
                if (properties[i].CanWrite && properties[i].GetSetMethod(nonPublic: false) != null)
                {
                    var parameterInfo = new PropertyAsParameterInfo(properties[i], factoryContext.NullabilityContext);
                    Debug.Assert(parameterInfo.Name != null, "Parameter name must be set for parameters resolved from properties.");
                    bindings.Add(Expression.Bind(properties[i], CreateArgument(parameterInfo, factoryContext, out var hasTryParse, out var hasBindAsync, out var _)));
                    factoryContext.Parameters.Add(parameterInfo);
                    factoryContext.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata(parameterInfo.Name, parameterInfo, hasTryParse: hasTryParse, hasBindAsync: hasBindAsync, isOptional: parameterInfo.IsOptional));
                }
            }

            var newExpression = constructor is null ?
                Expression.New(parameterType) :
                Expression.New(constructor);

            initExpression = Expression.MemberInit(newExpression, bindings);
        }

        factoryContext.ParamCheckExpressions.Add(
            Expression.Assign(argumentExpression, initExpression));

        factoryContext.TrackedParameters.Add(parameter.Name!, RequestDelegateFactoryConstants.PropertyAsParameter);
        factoryContext.ExtraLocals.Add(argumentExpression);

        return argumentExpression;
    }

    private static Expression BindParameterFromService(ParameterInfo parameter, RequestDelegateFactoryContext factoryContext)
    {
        var isOptional = IsOptionalParameter(parameter, factoryContext);

        if (isOptional)
        {
            return Expression.Call(GetServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
        }
        return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
    }

    private static Expression BindParameterFromKeyedService(ParameterInfo parameter, object key, RequestDelegateFactoryContext factoryContext)
    {
        var isOptional = IsOptionalParameter(parameter, factoryContext);

        if (isOptional)
        {
            return Expression.Call(GetKeyedServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr, Expression.Convert(
                Expression.Constant(key),
                typeof(object)));
        }
        return Expression.Call(GetRequiredKeyedServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr, Expression.Convert(
            Expression.Constant(key),
            typeof(object)));
    }

    private static Expression BindParameterFromValue(ParameterInfo parameter, Expression valueExpression, RequestDelegateFactoryContext factoryContext, string source)
    {
        if (parameter.ParameterType == typeof(string) || parameter.ParameterType == typeof(string[])
            || parameter.ParameterType == typeof(StringValues) || parameter.ParameterType == typeof(StringValues?))
        {
            return BindParameterFromExpression(parameter, valueExpression, factoryContext, source);
        }

        var isOptional = IsOptionalParameter(parameter, factoryContext);
        var argument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");

        var parameterTypeNameConstant = Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false));
        var parameterNameConstant = Expression.Constant(parameter.Name);
        var sourceConstant = Expression.Constant(source);

        factoryContext.UsingTempSourceString = true;

        var targetParseType = parameter.ParameterType.IsArray ? parameter.ParameterType.GetElementType()! : parameter.ParameterType;

        var underlyingNullableType = Nullable.GetUnderlyingType(targetParseType);
        var isNotNullable = underlyingNullableType is null;

        var nonNullableParameterType = underlyingNullableType ?? targetParseType;
        var tryParseMethodCall = ParameterBindingMethodCache.Instance.FindTryParseMethod(nonNullableParameterType);

        if (tryParseMethodCall is null)
        {
            var typeName = TypeNameHelper.GetTypeDisplayName(targetParseType, fullName: false);
            throw new InvalidOperationException($"{parameter.Name} must have a valid TryParse method to support converting from a string. No public static bool {typeName}.TryParse(string, out {typeName}) method found for {parameter.Name}.");
        }

        // string tempSourceString;
        // bool wasParamCheckFailure = false;
        //
        // // Assume "int param1" is the first parameter and "[FromRoute] int? param2 = 42" is the second parameter.
        // int param1_local;
        // int? param2_local;
        //
        // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
        //
        // if (tempSourceString != null)
        // {
        //     if (!int.TryParse(tempSourceString, out param1_local))
        //     {
        //         wasParamCheckFailure = true;
        //         Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
        //     }
        // }
        //
        // tempSourceString = httpContext.RouteValue["param2"];
        //
        // if (tempSourceString != null)
        // {
        //     if (int.TryParse(tempSourceString, out int parsedValue))
        //     {
        //         param2_local = parsedValue;
        //     }
        //     else
        //     {
        //         wasParamCheckFailure = true;
        //         Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
        //     }
        // }
        // else
        // {
        //     param2_local = 42;
        // }

        // string[]? values = httpContext.Request.Query["param1"].ToArray();
        // int[] param_local = values.Length > 0 ? new int[values.Length] : Array.Empty<int>();

        // if (values != null)
        // {
        //     int index = 0;
        //     while (index < values.Length)
        //     {
        //         tempSourceString = values[i];
        //         if (int.TryParse(tempSourceString, out var parsedValue))
        //         {
        //             param_local[i] = parsedValue;
        //         }
        //         else
        //         {
        //             wasParamCheckFailure = true;
        //             Log.ParameterBindingFailed(httpContext, "Int32[]", "param1", tempSourceString);
        //             break;
        //         }
        //
        //         index++
        //     }
        // }

        // If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot use the parameter directly.
        var parsedValue = Expression.Variable(nonNullableParameterType, "parsedValue");

        var failBlock = Expression.Block(
            Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
            Expression.Call(LogParameterBindingFailedMethod,
                HttpContextExpr, parameterTypeNameConstant, parameterNameConstant,
                TempSourceStringExpr, Expression.Constant(factoryContext.ThrowOnBadRequest)));

        var tryParseCall = tryParseMethodCall(parsedValue, Expression.Constant(CultureInfo.InvariantCulture));

        // The following code is generated if the parameter is required and
        // the method should not be matched.
        //
        // if (tempSourceString == null)
        // {
        //      wasParamCheckFailure = true;
        //      Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
        // }
        var checkRequiredParaseableParameterBlock = Expression.Block(
            Expression.IfThen(TempSourceStringNullExpr,
                Expression.Block(
                    Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
                    Expression.Call(LogRequiredParameterNotProvidedMethod,
                        HttpContextExpr, parameterTypeNameConstant, parameterNameConstant, sourceConstant,
                        Expression.Constant(factoryContext.ThrowOnBadRequest))
                )
            )
        );

        var index = Expression.Variable(typeof(int), "index");

        // If the parameter is nullable, we need to assign the "parsedValue" local to the nullable parameter on success.
        var tryParseExpression = Expression.Block(new[] { parsedValue },
                Expression.IfThenElse(tryParseCall,
                    Expression.Assign(parameter.ParameterType.IsArray ? Expression.ArrayAccess(argument, index) : argument, Expression.Convert(parsedValue, targetParseType)),
                    failBlock));

        var ifNotNullTryParse = !parameter.HasDefaultValue
            ? Expression.IfThen(TempSourceStringNotNullExpr, tryParseExpression)
            : Expression.IfThenElse(TempSourceStringNotNullExpr, tryParseExpression,
                Expression.Assign(argument,
                Expression.Constant(parameter.DefaultValue, parameter.ParameterType)));

        var loopExit = Expression.Label();

        // REVIEW: We can reuse this like we reuse temp source string
        var stringArrayExpr = parameter.ParameterType.IsArray ? Expression.Variable(typeof(string[]), "tempStringArray") : null;
        var elementTypeNullabilityInfo = parameter.ParameterType.IsArray ? factoryContext.NullabilityContext.Create(parameter)?.ElementType : null;

        // Determine optionality of the element type of the array
        var elementTypeOptional = !isNotNullable || (elementTypeNullabilityInfo?.ReadState != NullabilityState.NotNull);

        // The loop that populates the resulting array values
        var arrayLoop = parameter.ParameterType.IsArray ? Expression.Block(
                        // param_local = new int[values.Length];
                        Expression.Assign(argument, Expression.NewArrayBounds(parameter.ParameterType.GetElementType()!, Expression.ArrayLength(stringArrayExpr!))),
                        // index = 0
                        Expression.Assign(index, Expression.Constant(0)),
                        // while (index < values.Length)
                        Expression.Loop(
                            Expression.Block(
                                Expression.IfThenElse(
                                    Expression.LessThan(index, Expression.ArrayLength(stringArrayExpr!)),
                                        // tempSourceString = values[index];
                                        Expression.Block(
                                            Expression.Assign(TempSourceStringExpr, Expression.ArrayIndex(stringArrayExpr!, index)),
                                            elementTypeOptional ? Expression.IfThen(TempSourceStringIsNotNullOrEmptyExpr, tryParseExpression)
                                                                : tryParseExpression
                                        ),
                                       // else break
                                       Expression.Break(loopExit)
                                 ),
                                 // index++
                                 Expression.PostIncrementAssign(index)
                            )
                        , loopExit)
                    ) : null;

        var fullParamCheckBlock = (parameter.ParameterType.IsArray, isOptional) switch
        {
            // (isArray: true, optional: true)
            (true, true) =>

            Expression.Block(
                new[] { index, stringArrayExpr! },
                // values = httpContext.Request.Query["id"];
                Expression.Assign(stringArrayExpr!, valueExpression),
                Expression.IfThen(
                    Expression.NotEqual(stringArrayExpr!, Expression.Constant(null)),
                    arrayLoop!
                )
            ),

            // (isArray: true, optional: false)
            (true, false) =>

            Expression.Block(
                new[] { index, stringArrayExpr! },
                // values = httpContext.Request.Query["id"];
                Expression.Assign(stringArrayExpr!, valueExpression),
                Expression.IfThenElse(
                    Expression.NotEqual(stringArrayExpr!, Expression.Constant(null)),
                    arrayLoop!,
                    failBlock
                )
            ),

            // (isArray: false, optional: false)
            (false, false) =>

            Expression.Block(
                // tempSourceString = httpContext.RequestValue["id"];
                Expression.Assign(TempSourceStringExpr, valueExpression),
                // if (tempSourceString == null) { ... } only produced when parameter is required
                checkRequiredParaseableParameterBlock,
                // if (tempSourceString != null) { ... }
                ifNotNullTryParse),

            // (isArray: false, optional: true)
            (false, true) =>

            Expression.Block(
                // tempSourceString = httpContext.RequestValue["id"];
                Expression.Assign(TempSourceStringExpr, valueExpression),
                // if (tempSourceString != null) { ... }
                ifNotNullTryParse)
        };

        factoryContext.ExtraLocals.Add(argument);
        factoryContext.ParamCheckExpressions.Add(fullParamCheckBlock);

        return argument;
    }

    private static Expression BindParameterFromExpression(
        ParameterInfo parameter,
        Expression valueExpression,
        RequestDelegateFactoryContext factoryContext,
        string source)
    {
        var nullability = factoryContext.NullabilityContext.Create(parameter);
        var isOptional = IsOptionalParameter(parameter, factoryContext);

        var argument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");

        var parameterTypeNameConstant = Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false));
        var parameterNameConstant = Expression.Constant(parameter.Name);
        var sourceConstant = Expression.Constant(source);

        if (!isOptional)
        {
            // The following is produced if the parameter is required:
            //
            // argument = value["param1"];
            // if (argument == null)
            // {
            //      wasParamCheckFailure = true;
            //      Log.RequiredParameterNotProvided(httpContext, "TypeOfValue", "param1");
            // }
            var checkRequiredStringParameterBlock = Expression.Block(
                Expression.Assign(argument, valueExpression),
                Expression.IfThen(Expression.Equal(argument, Expression.Constant(null)),
                    Expression.Block(
                        Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
                        Expression.Call(LogRequiredParameterNotProvidedMethod,
                            HttpContextExpr, parameterTypeNameConstant, parameterNameConstant, sourceConstant,
                            Expression.Constant(factoryContext.ThrowOnBadRequest))
                    )
                )
            );

            // NOTE: when StringValues is used as a parameter, value["some_unpresent_parameter"] returns StringValue.Empty, and it's equivalent to (string?)null

            factoryContext.ExtraLocals.Add(argument);
            factoryContext.ParamCheckExpressions.Add(checkRequiredStringParameterBlock);
            return argument;
        }

        // Allow nullable parameters that don't have a default value
        if (nullability.ReadState != NullabilityState.NotNull && !parameter.HasDefaultValue)
        {
            if (parameter.ParameterType == typeof(StringValues?))
            {
                // when Nullable<StringValues> is used and the actual value is StringValues.Empty, we should pass in a Nullable<StringValues>
                return Expression.Block(
                    Expression.Condition(Expression.Equal(valueExpression, Expression.Convert(Expression.Constant(StringValues.Empty), parameter.ParameterType)),
                            Expression.Convert(Expression.Constant(null), parameter.ParameterType),
                            valueExpression
                        )
                    );
            }
            return valueExpression;
        }

        // The following is produced if the parameter is optional. Note that we convert the
        // default value to the target ParameterType to address scenarios where the user is
        // is setting null as the default value in a context where nullability is disabled.
        //
        // param1_local = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
        // param1_local != null ? param1_local : Convert(null, Int32)
        return Expression.Block(
            Expression.Condition(Expression.NotEqual(valueExpression, Expression.Constant(null)),
                valueExpression,
                Expression.Convert(Expression.Constant(parameter.DefaultValue), parameter.ParameterType)));
    }

    private static Expression BindParameterFromProperty(ParameterInfo parameter, MemberExpression property, PropertyInfo itemProperty, string key, RequestDelegateFactoryContext factoryContext, string source) =>
        BindParameterFromValue(parameter, GetValueFromProperty(property, itemProperty, key, GetExpressionType(parameter.ParameterType)), factoryContext, source);

    private static Type? GetExpressionType(Type type) =>
        type.IsArray ? typeof(string[]) :
        type == typeof(StringValues) ? typeof(StringValues) :
        type == typeof(StringValues?) ? typeof(StringValues?) :
        null;

    private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo parameter, string key, RequestDelegateFactoryContext factoryContext)
    {
        var routeValue = GetValueFromProperty(RouteValuesExpr, RouteValuesIndexerProperty, key);
        var queryValue = GetValueFromProperty(QueryExpr, QueryIndexerProperty, key);
        return BindParameterFromValue(parameter, Expression.Coalesce(routeValue, queryValue), factoryContext, "route or query string");
    }

    private static Expression BindParameterFromBindAsync(ParameterInfo parameter, RequestDelegateFactoryContext factoryContext)
    {
        // We reference the boundValues array by parameter index here
        var isOptional = IsOptionalParameter(parameter, factoryContext);

        // Get the BindAsync method for the type.
        var bindAsyncMethod = ParameterBindingMethodCache.Instance.FindBindAsyncMethod(parameter);
        // We know BindAsync exists because there's no way to opt-in without defining the method on the type.
        Debug.Assert(bindAsyncMethod.Expression is not null);

        // Compile the delegate to the BindAsync method for this parameter index
        var bindAsyncDelegate = Expression.Lambda<Func<HttpContext, ValueTask<object?>>>(bindAsyncMethod.Expression, HttpContextExpr).Compile();
        factoryContext.ParameterBinders.Add(bindAsyncDelegate);

        // boundValues[index]
        var boundValueExpr = Expression.ArrayIndex(BoundValuesArrayExpr, Expression.Constant(factoryContext.ParameterBinders.Count - 1));

        if (!isOptional)
        {
            var typeName = TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false);
            var message = bindAsyncMethod.ParamCount == 2 ? $"{typeName}.BindAsync(HttpContext, ParameterInfo)" : $"{typeName}.BindAsync(HttpContext)";
            var checkRequiredBodyBlock = Expression.Block(
                    Expression.IfThen(
                    Expression.Equal(boundValueExpr, Expression.Constant(null)),
                        Expression.Block(
                            Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
                            Expression.Call(LogRequiredParameterNotProvidedMethod,
                                    HttpContextExpr,
                                    Expression.Constant(typeName),
                                    Expression.Constant(parameter.Name),
                                    Expression.Constant(message),
                                    Expression.Constant(factoryContext.ThrowOnBadRequest))
                        )
                    )
                );

            factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
        }

        // (ParameterType)boundValues[i]
        return Expression.Convert(boundValueExpr, parameter.ParameterType);
    }

    private static void AddInferredAcceptsMetadata(RequestDelegateFactoryContext factoryContext, Type type, string[] contentTypes)
    {
        if (factoryContext.MetadataAlreadyInferred)
        {
            return;
        }

        factoryContext.EndpointBuilder.Metadata.Add(new AcceptsMetadata(contentTypes, type, factoryContext.AllowEmptyRequestBody));
    }

    private static void InferFormAcceptsMetadata(RequestDelegateFactoryContext factoryContext)
    {
        if (factoryContext.ReadFormFile)
        {
            AddInferredAcceptsMetadata(factoryContext, factoryContext.FirstFormRequestBodyParameter!.ParameterType, FormFileContentType);
        }
        else
        {
            AddInferredAcceptsMetadata(factoryContext, factoryContext.FirstFormRequestBodyParameter!.ParameterType, FormContentType);
        }
    }

    private static void InferAntiforgeryMetadata(RequestDelegateFactoryContext factoryContext)
    {
        if (factoryContext.MetadataAlreadyInferred)
        {
            return;
        }

        factoryContext.EndpointBuilder.Metadata.Add(AntiforgeryMetadata.ValidationRequired);
    }

    private static Expression BindParameterFromFormCollection(
        ParameterInfo parameter,
        RequestDelegateFactoryContext factoryContext)
    {
        factoryContext.FirstFormRequestBodyParameter ??= parameter;
        factoryContext.TrackedParameters.Add(parameter.Name!, RequestDelegateFactoryConstants.FormCollectionParameter);
        factoryContext.ReadForm = true;

        return BindParameterFromExpression(
            parameter,
            FormExpr,
            factoryContext,
            "body");
    }

    private static Expression BindParameterFromFormItem(
        ParameterInfo parameter,
        string key,
        RequestDelegateFactoryContext factoryContext)
    {
        var valueExpression = GetValueFromProperty(FormExpr, FormIndexerProperty, key, GetExpressionType(parameter.ParameterType));

        factoryContext.FirstFormRequestBodyParameter ??= parameter;
        factoryContext.TrackedParameters.Add(key, RequestDelegateFactoryConstants.FormAttribute);
        factoryContext.ReadForm = true;

        return BindParameterFromValue(
            parameter,
            valueExpression,
            factoryContext,
            "form");
    }

    private static void UpdateFormBindingArgumentExpressions(RequestDelegateFactoryContext factoryContext)
    {
        if (factoryContext.ArgumentExpressions == null || factoryContext.ArgumentExpressions.Length == 0)
        {
            return;
        }

        for (var i = 0; i < factoryContext.ArgumentExpressions.Length; i++)
        {
            var parameter = factoryContext.Parameters[i];
            var formAttribute = parameter.GetCustomAttributes().OfType<IFromFormMetadata>().FirstOrDefault();
            var key = formAttribute == null || string.IsNullOrEmpty(formAttribute.Name) ? parameter.Name! : formAttribute.Name;
            if (factoryContext.TrackedParameters.TryGetValue(key, out var trackedParameter) && trackedParameter == RequestDelegateFactoryConstants.FormBindingAttribute)
            {
                factoryContext.ArgumentExpressions[i] = BindComplexParameterFromFormItem(parameter, key, factoryContext, true);
            }
        }
    }

    private static Expression BindComplexParameterFromFormItem(
        ParameterInfo parameter,
        string key,
        RequestDelegateFactoryContext factoryContext,
        bool setExpressions = false)
    {
        factoryContext.FirstFormRequestBodyParameter ??= parameter;
        factoryContext.TrackedParameters.TryAdd(key, RequestDelegateFactoryConstants.FormBindingAttribute);
        factoryContext.ReadForm = true;

        // var name_local;
        var formArgument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");

        // Delay setting the generated LINQ expressions until
        // metadata has already been inferred so that we can read from `FormMappingOptionsMetadata`.
        if (!setExpressions)
        {
            return formArgument;
        }

        var formDataMapperOptions = factoryContext.FormDataMapperOptions;
        var formMappingOptionsMetadatas = factoryContext.EndpointBuilder.Metadata.OfType<FormMappingOptionsMetadata>();
        foreach (var formMappingOptionsMetadata in formMappingOptionsMetadatas)
        {
            formDataMapperOptions.MaxRecursionDepth = formMappingOptionsMetadata.MaxRecursionDepth ?? formDataMapperOptions.MaxRecursionDepth;
            formDataMapperOptions.MaxCollectionSize = formMappingOptionsMetadata.MaxCollectionSize ?? formDataMapperOptions.MaxCollectionSize;
            formDataMapperOptions.MaxKeyBufferSize = formMappingOptionsMetadata.MaxKeySize ?? formDataMapperOptions.MaxKeyBufferSize;
        }

        // var name_reader;
        // var form_dict;
        // var form_buffer;
        var formReader = Expression.Variable(typeof(FormDataReader), $"{parameter.Name}_reader");
        var formDict = Expression.Variable(typeof(IReadOnlyDictionary<FormKey, StringValues>), "form_dict");
        var formBuffer = Expression.Variable(typeof(char[]), "form_buffer");
        var formDataMappingException = Expression.Variable(typeof(FormDataMappingException), "form_exception");

        // ProcessForm(context.Request.Form, form_dict, form_buffer);
        var processFormExpr = Expression.Call(ProcessFormMethod, FormExpr, Expression.Constant(formDataMapperOptions.MaxKeyBufferSize), formDict, formBuffer);
        // name_reader = new FormDataReader(form_dict, CultureInfo.InvariantCulture, form_buffer.AsMemory(0, formDataMapperOptions.MaxKeyBufferSize), httpContext.Request.Form.Files);
        var initializeReaderExpr = Expression.Assign(
            formReader,
            Expression.New(FormDataReaderConstructor,
                formDict,
                Expression.Constant(CultureInfo.InvariantCulture),
                Expression.Call(AsMemoryMethod, formBuffer, Expression.Constant(0), Expression.Constant(formDataMapperOptions.MaxKeyBufferSize)),
                FormFilesExpr));
        // name_reader.MaxRecursionDepth = formDataMapperOptions.MaxRecursionDepth;
        var setMaxRecursionDepthExpr = Expression.Assign(
            Expression.Property(formReader, nameof(FormDataReader.MaxRecursionDepth)),
            Expression.Constant(formDataMapperOptions.MaxRecursionDepth));
        // FormDataMapper.Map<string>(name_reader, FormDataMapperOptions);
        var invokeMapMethodExpr = Expression.Call(
            FormDataMapperMapMethod.MakeGenericMethod(parameter.ParameterType),
            formReader,
            Expression.Constant(formDataMapperOptions));
        // if (form_buffer != null)
        // {
        //   ArrayPool<char>.Shared.Return(form_buffer, false);
        // }
        var returnBufferExpr = Expression.Call(
            Expression.Property(null, typeof(ArrayPool<char>).GetProperty(nameof(ArrayPool<char>.Shared))!),
            ArrayPoolSharedReturnMethod,
            formBuffer,
            Expression.Constant(false));
        var conditionalReturnBufferExpr = Expression.IfThen(
            Expression.NotEqual(formBuffer, Expression.Constant(null)),
            returnBufferExpr);

        var parameterTypeNameConstant = Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false));
        var parameterNameConstant = Expression.Constant(parameter.Name);

        // try
        // {
        //   ProcessForm(context.Request.Form, form_dict, form_buffer);
        //   name_reader = new FormDataReader(form_dict, CultureInfo.InvariantCulture, form_buffer.AsMemory(0, FormDataMapperOptions.MaxKeyBufferSize));
        //   name_reader.MaxRecursionDepth = formDataMapperOptions.MaxRecursionDepth;
        //   name_local = FormDataMapper.Map<string>(name_reader, FormDataMapperOptions);
        // }
        // catch (FormDataMappingException e)
        // {
        //    wasParamCheckFailure = true;
        //    LogFormMappingFailedMethod(httpContext, "string", "name", ex, factoryContext.ThrowOnBadRequest);
        // }
        // finally
        // {
        //   if (form_buffer != null)
        //   {
        //     ArrayPool<char>.Shared.Return(form_buffer, false);
        //   }
        // }
        var bindAndCheckForm = Expression.Block(
            new[] { formReader, formDict, formBuffer, formDataMappingException },
            Expression.TryCatchFinally(
                Expression.Block(
                    typeof(void),
                    processFormExpr,
                    initializeReaderExpr,
                    setMaxRecursionDepthExpr,
                    Expression.Assign(formArgument, invokeMapMethodExpr)),
                conditionalReturnBufferExpr,
                Expression.Catch(formDataMappingException, Expression.Block(
                    typeof(void),
                    Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
                    Expression.Call(
                        LogFormMappingFailedMethod,
                        HttpContextExpr,
                        parameterTypeNameConstant,
                        parameterNameConstant,
                        formDataMappingException,
                        Expression.Constant(factoryContext.ThrowOnBadRequest))
                )))
        );

        factoryContext.ParamCheckExpressions.Add(bindAndCheckForm);
        factoryContext.ExtraLocals.Add(formArgument);

        return formArgument;
    }

    private static void ProcessForm(IFormCollection form, int maxKeyBufferSize, ref IReadOnlyDictionary<FormKey, StringValues> formDictionary, ref char[] buffer)
    {
        var dictionary = new Dictionary<FormKey, StringValues>();
        foreach (var (key, value) in form)
        {
            dictionary.Add(new FormKey(key.AsMemory()), value);
        }
        formDictionary = dictionary.AsReadOnly();
        buffer = ArrayPool<char>.Shared.Rent(maxKeyBufferSize);
    }

    private static Expression BindParameterFromFormFiles(
        ParameterInfo parameter,
        RequestDelegateFactoryContext factoryContext)
    {
        factoryContext.FirstFormRequestBodyParameter ??= parameter;
        factoryContext.TrackedParameters.Add(parameter.Name!, RequestDelegateFactoryConstants.FormFileParameter);
        factoryContext.ReadForm = true;
        factoryContext.ReadFormFile = true;

        return BindParameterFromExpression(
            parameter,
            FormFilesExpr,
            factoryContext,
            "body");
    }

    private static Expression BindParameterFromFormFile(
        ParameterInfo parameter,
        string key,
        RequestDelegateFactoryContext factoryContext,
        string trackedParameterSource)
    {
        var valueExpression = GetValueFromProperty(FormFilesExpr, FormFilesIndexerProperty, key, typeof(IFormFile));

        factoryContext.FirstFormRequestBodyParameter ??= parameter;
        factoryContext.TrackedParameters.Add(key, trackedParameterSource);
        factoryContext.ReadForm = true;
        factoryContext.ReadFormFile = true;

        return BindParameterFromExpression(
            parameter,
            valueExpression,
            factoryContext,
            "form file");
    }

    private static Expression BindParameterFromBody(ParameterInfo parameter, bool allowEmpty, RequestDelegateFactoryContext factoryContext)
    {
        if (factoryContext.JsonRequestBodyParameter is not null)
        {
            factoryContext.HasMultipleBodyParameters = true;
            var parameterName = parameter.Name;

            Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");

            if (factoryContext.TrackedParameters.ContainsKey(parameterName))
            {
                factoryContext.TrackedParameters.Remove(parameterName);
                factoryContext.TrackedParameters.Add(parameterName, "UNKNOWN");
            }
        }

        var isOptional = IsOptionalParameter(parameter, factoryContext);

        factoryContext.JsonRequestBodyParameter = parameter;
        factoryContext.AllowEmptyRequestBody = allowEmpty || isOptional;
        AddInferredAcceptsMetadata(factoryContext, parameter.ParameterType, DefaultAcceptsAndProducesContentType);

        if (!factoryContext.AllowEmptyRequestBody)
        {
            if (factoryContext.HasInferredBody)
            {
                // if (bodyValue == null)
                // {
                //    wasParamCheckFailure = true;
                //    Log.ImplicitBodyNotProvided(httpContext, "todo", ThrowOnBadRequest);
                // }
                factoryContext.ParamCheckExpressions.Add(Expression.Block(
                    Expression.IfThen(
                        Expression.Equal(BodyValueExpr, Expression.Constant(null)),
                        Expression.Block(
                            Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
                            Expression.Call(LogImplicitBodyNotProvidedMethod,
                                HttpContextExpr,
                                Expression.Constant(parameter.Name),
                                Expression.Constant(factoryContext.ThrowOnBadRequest)
                            )
                        )
                    )
                ));
            }
            else
            {
                // If the parameter is required or the user has not explicitly
                // set allowBody to be empty then validate that it is required.
                //
                // if (bodyValue == null)
                // {
                //      wasParamCheckFailure = true;
                //      Log.RequiredParameterNotProvided(httpContext, "Todo", "todo", "body", ThrowOnBadRequest);
                // }
                var checkRequiredBodyBlock = Expression.Block(
                    Expression.IfThen(
                    Expression.Equal(BodyValueExpr, Expression.Constant(null)),
                        Expression.Block(
                            Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
                            Expression.Call(LogRequiredParameterNotProvidedMethod,
                                HttpContextExpr,
                                Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false)),
                                Expression.Constant(parameter.Name),
                                Expression.Constant("body"),
                                Expression.Constant(factoryContext.ThrowOnBadRequest))
                        )
                    )
                );
                factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
            }
        }
        else if (parameter.HasDefaultValue)
        {
            // Convert(bodyValue ?? SomeDefault, Todo)
            return Expression.Convert(
                Expression.Coalesce(BodyValueExpr, Expression.Constant(parameter.DefaultValue)),
                parameter.ParameterType);
        }

        // Convert(bodyValue, Todo)
        return Expression.Convert(BodyValueExpr, parameter.ParameterType);
    }

    private static bool IsOptionalParameter(ParameterInfo parameter, RequestDelegateFactoryContext factoryContext)
    {
        if (parameter is PropertyAsParameterInfo argument)
        {
            return argument.IsOptional;
        }

        // - Parameters representing value or reference types with a default value
        // under any nullability context are treated as optional.
        // - Value type parameters without a default value in an oblivious
        // nullability context are required.
        // - Reference type parameters without a default value in an oblivious
        // nullability context are optional.
        var nullabilityInfo = factoryContext.NullabilityContext.Create(parameter);
        return parameter.HasDefaultValue
            || nullabilityInfo.ReadState != NullabilityState.NotNull;
    }

    private static MethodInfo GetMethodInfo<T>(Expression<T> expr)
    {
        var mc = (MethodCallExpression)expr.Body;
        return mc.Method;
    }

    private static MemberInfo GetMemberInfo<T>(Expression<T> expr)
    {
        var mc = (MemberExpression)expr.Body;
        return mc.Member;
    }

    // The result of the method is null so we fallback to some runtime logic.
    // First we check if the result is IResult, Task<IResult> or ValueTask<IResult>. If
    // it is, we await if necessary then execute the result.
    // Then we check to see if it's Task<object> or ValueTask<object>. If it is, we await
    // if necessary and restart the cycle until we've reached a terminal state (unknown type).
    // We currently don't handle Task<unknown> or ValueTask<unknown>. We can support this later if this
    // ends up being a common scenario.
    private static Task ExecuteValueTaskOfObject(ValueTask<object> valueTask, HttpContext httpContext, JsonTypeInfo<object> jsonTypeInfo)
    {
        static async Task ExecuteAwaited(ValueTask<object> valueTask, HttpContext httpContext, JsonTypeInfo<object> jsonTypeInfo)
        {
            await ExecuteAwaitedReturn(await valueTask, httpContext, jsonTypeInfo);
        }

        if (valueTask.IsCompletedSuccessfully)
        {
            return ExecuteAwaitedReturn(valueTask.GetAwaiter().GetResult(), httpContext, jsonTypeInfo);
        }

        return ExecuteAwaited(valueTask, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteTaskOfObject(Task<object> task, HttpContext httpContext, JsonTypeInfo<object> jsonTypeInfo)
    {
        static async Task ExecuteAwaited(Task<object> task, HttpContext httpContext, JsonTypeInfo<object> jsonTypeInfo)
        {
            await ExecuteAwaitedReturn(await task, httpContext, jsonTypeInfo);
        }

        if (task.IsCompletedSuccessfully)
        {
            return ExecuteAwaitedReturn(task.GetAwaiter().GetResult(), httpContext, jsonTypeInfo);
        }

        return ExecuteAwaited(task, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteAwaitedReturn(object obj, HttpContext httpContext, JsonTypeInfo<object> jsonTypeInfo)
    {
        return ExecuteHandlerHelper.ExecuteReturnAsync(obj, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteTaskOfTFast<T>(Task<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
    {
        EnsureRequestTaskNotNull(task);

        static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
        {
            await WriteJsonResponseFast(httpContext.Response, await task, jsonTypeInfo);
        }

        if (task.IsCompletedSuccessfully)
        {
            return WriteJsonResponseFast(httpContext.Response, task.GetAwaiter().GetResult(), jsonTypeInfo);
        }

        return ExecuteAwaited(task, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteTaskOfT<T>(Task<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
    {
        EnsureRequestTaskNotNull(task);

        static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
        {
            await WriteJsonResponse(httpContext.Response, await task, jsonTypeInfo);
        }

        if (task.IsCompletedSuccessfully)
        {
            return WriteJsonResponse(httpContext.Response, task.GetAwaiter().GetResult(), jsonTypeInfo);
        }

        return ExecuteAwaited(task, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteTaskOfString(Task<string?> task, HttpContext httpContext)
    {
        ExecuteHandlerHelper.SetPlaintextContentType(httpContext);
        EnsureRequestTaskNotNull(task);

        static async Task ExecuteAwaited(Task<string> task, HttpContext httpContext)
        {
            await httpContext.Response.WriteAsync(await task);
        }

        if (task.IsCompletedSuccessfully)
        {
            return httpContext.Response.WriteAsync(task.GetAwaiter().GetResult()!);
        }

        return ExecuteAwaited(task!, httpContext);
    }

    private static Task ExecuteWriteStringResponseAsync(HttpContext httpContext, string text)
    {
        ExecuteHandlerHelper.SetPlaintextContentType(httpContext);
        return httpContext.Response.WriteAsync(text);
    }

    private static Task ExecuteValueTask(ValueTask task)
    {
        static async Task ExecuteAwaited(ValueTask task)
        {
            await task;
        }

        if (task.IsCompletedSuccessfully)
        {
            task.GetAwaiter().GetResult();
            return Task.CompletedTask;
        }

        return ExecuteAwaited(task);
    }

    private static ValueTask<object?> ExecuteTaskWithEmptyResult(Task task)
    {
        static async ValueTask<object?> ExecuteAwaited(Task task)
        {
            await task;
            return EmptyHttpResult.Instance;
        }

        if (task.IsCompletedSuccessfully)
        {
            return new ValueTask<object?>(EmptyHttpResult.Instance);
        }

        return ExecuteAwaited(task);
    }

    private static ValueTask<object?> ExecuteValueTaskWithEmptyResult(ValueTask valueTask)
    {
        static async ValueTask<object?> ExecuteAwaited(ValueTask task)
        {
            await task;
            return EmptyHttpResult.Instance;
        }

        if (valueTask.IsCompletedSuccessfully)
        {
            valueTask.GetAwaiter().GetResult();
            return new ValueTask<object?>(EmptyHttpResult.Instance);
        }

        return ExecuteAwaited(valueTask);
    }

    private static Task ExecuteValueTaskOfTFast<T>(ValueTask<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
    {
        static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
        {
            await WriteJsonResponseFast(httpContext.Response, await task, jsonTypeInfo);
        }

        if (task.IsCompletedSuccessfully)
        {
            return WriteJsonResponseFast(httpContext.Response, task.GetAwaiter().GetResult(), jsonTypeInfo);
        }

        return ExecuteAwaited(task, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteValueTaskOfT<T>(ValueTask<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
    {
        static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo)
        {
            await WriteJsonResponse(httpContext.Response, await task, jsonTypeInfo);
        }

        if (task.IsCompletedSuccessfully)
        {
            return WriteJsonResponse(httpContext.Response, task.GetAwaiter().GetResult(), jsonTypeInfo);
        }

        return ExecuteAwaited(task, httpContext, jsonTypeInfo);
    }

    private static Task ExecuteValueTaskOfString(ValueTask<string?> task, HttpContext httpContext)
    {
        ExecuteHandlerHelper.SetPlaintextContentType(httpContext);

        static async Task ExecuteAwaited(ValueTask<string> task, HttpContext httpContext)
        {
            await httpContext.Response.WriteAsync(await task);
        }

        if (task.IsCompletedSuccessfully)
        {
            return httpContext.Response.WriteAsync(task.GetAwaiter().GetResult()!);
        }

        return ExecuteAwaited(task!, httpContext);
    }

    private static Task ExecuteValueTaskResult<T>(ValueTask<T?> task, HttpContext httpContext) where T : IResult
    {
        static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext)
        {
            await EnsureRequestResultNotNull(await task).ExecuteAsync(httpContext);
        }

        if (task.IsCompletedSuccessfully)
        {
            return EnsureRequestResultNotNull(task.GetAwaiter().GetResult()).ExecuteAsync(httpContext);
        }

        return ExecuteAwaited(task!, httpContext);
    }

    private static async Task ExecuteTaskResult<T>(Task<T?> task, HttpContext httpContext) where T : IResult
    {
        EnsureRequestTaskOfNotNull(task);

        await EnsureRequestResultNotNull(await task).ExecuteAsync(httpContext);
    }

    private static async Task ExecuteResultWriteResponse(IResult? result, HttpContext httpContext)
    {
        await EnsureRequestResultNotNull(result).ExecuteAsync(httpContext);
    }

    // This method will not check for polymorphism and will
    // leverage the STJ polymorphism support.
    private static Task WriteJsonResponseFast<T>(HttpResponse response, T value, JsonTypeInfo<T> jsonTypeInfo)
        => HttpResponseJsonExtensions.WriteAsJsonAsync(response, value, jsonTypeInfo, default);

    private static Task WriteJsonResponse<T>(HttpResponse response, T? value, JsonTypeInfo<T> jsonTypeInfo)
    {
        return ExecuteHandlerHelper.WriteJsonResponseAsync(response, value, jsonTypeInfo);
    }

    private static NotSupportedException GetUnsupportedReturnTypeException(Type returnType)
    {
        return new NotSupportedException($"Unsupported return type: {TypeNameHelper.GetTypeDisplayName(returnType)}");
    }

    private static class RequestDelegateFactoryConstants
    {
        public const string RouteAttribute = "Route (Attribute)";
        public const string QueryAttribute = "Query (Attribute)";
        public const string HeaderAttribute = "Header (Attribute)";
        public const string BodyAttribute = "Body (Attribute)";
        public const string ServiceAttribute = "Service (Attribute)";
        public const string FormFileAttribute = "Form File (Attribute)";
        public const string FormAttribute = "Form (Attribute)";
        public const string FormBindingAttribute = "Form Binding (Attribute)";
        public const string RouteParameter = "Route (Inferred)";
        public const string QueryStringParameter = "Query String (Inferred)";
        public const string ServiceParameter = "Services (Inferred)";
        public const string BodyParameter = "Body (Inferred)";
        public const string RouteOrQueryStringParameter = "Route or Query String (Inferred)";
        public const string FormFileParameter = "Form File (Inferred)";
        public const string FormCollectionParameter = "Form Collection (Inferred)";
        public const string PropertyAsParameter = "As Parameter (Attribute)";
    }

    private static partial class Log
    {
        // This doesn't take a shouldThrow parameter because an IOException indicates an aborted request rather than a "bad" request so
        // a BadHttpRequestException feels wrong. The client shouldn't be able to read the Developer Exception Page at any rate.
        public static void RequestBodyIOException(HttpContext httpContext, IOException exception)
            => RequestBodyIOException(GetLogger(httpContext), exception);

        [LoggerMessage(RequestDelegateCreationLogging.RequestBodyIOExceptionEventId, LogLevel.Debug, RequestDelegateCreationLogging.RequestBodyIOExceptionMessage, EventName = RequestDelegateCreationLogging.RequestBodyIOExceptionEventName)]
        private static partial void RequestBodyIOException(ILogger logger, IOException exception);

        public static void InvalidJsonRequestBody(HttpContext httpContext, string parameterTypeName, string parameterName, Exception exception, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.InvalidJsonRequestBodyExceptionMessage, parameterTypeName, parameterName);
                throw new BadHttpRequestException(message, exception);
            }

            InvalidJsonRequestBody(GetLogger(httpContext), parameterTypeName, parameterName, exception);
        }

        [LoggerMessage(RequestDelegateCreationLogging.InvalidJsonRequestBodyEventId, LogLevel.Debug, RequestDelegateCreationLogging.InvalidJsonRequestBodyLogMessage, EventName = RequestDelegateCreationLogging.InvalidJsonRequestBodyEventName)]
        private static partial void InvalidJsonRequestBody(ILogger logger, string parameterType, string parameterName, Exception exception);

        public static void ParameterBindingFailed(HttpContext httpContext, string parameterTypeName, string parameterName, string sourceValue, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.ParameterBindingFailedExceptionMessage, parameterTypeName, parameterName, sourceValue);
                throw new BadHttpRequestException(message);
            }

            ParameterBindingFailed(GetLogger(httpContext), parameterTypeName, parameterName, sourceValue);
        }

        [LoggerMessage(RequestDelegateCreationLogging.ParameterBindingFailedEventId, LogLevel.Debug, RequestDelegateCreationLogging.ParameterBindingFailedLogMessage, EventName = RequestDelegateCreationLogging.ParameterBindingFailedEventName)]
        private static partial void ParameterBindingFailed(ILogger logger, string parameterType, string parameterName, string sourceValue);

        public static void RequiredParameterNotProvided(HttpContext httpContext, string parameterTypeName, string parameterName, string source, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.RequiredParameterNotProvidedExceptionMessage, parameterTypeName, parameterName, source);
                throw new BadHttpRequestException(message);
            }

            RequiredParameterNotProvided(GetLogger(httpContext), parameterTypeName, parameterName, source);
        }

        [LoggerMessage(RequestDelegateCreationLogging.RequiredParameterNotProvidedEventId, LogLevel.Debug, RequestDelegateCreationLogging.RequiredParameterNotProvidedLogMessage, EventName = RequestDelegateCreationLogging.RequiredParameterNotProvidedEventName)]
        private static partial void RequiredParameterNotProvided(ILogger logger, string parameterType, string parameterName, string source);

        public static void ImplicitBodyNotProvided(HttpContext httpContext, string parameterName, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.ImplicitBodyNotProvidedExceptionMessage, parameterName);
                throw new BadHttpRequestException(message);
            }

            ImplicitBodyNotProvided(GetLogger(httpContext), parameterName);
        }

        [LoggerMessage(RequestDelegateCreationLogging.ImplicitBodyNotProvidedEventId, LogLevel.Debug, RequestDelegateCreationLogging.ImplicitBodyNotProvidedLogMessage, EventName = RequestDelegateCreationLogging.ImplicitBodyNotProvidedEventName)]
        private static partial void ImplicitBodyNotProvided(ILogger logger, string parameterName);

        public static void UnexpectedJsonContentType(HttpContext httpContext, string? contentType, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.UnexpectedJsonContentTypeExceptionMessage, contentType);
                throw new BadHttpRequestException(message, StatusCodes.Status415UnsupportedMediaType);
            }

            UnexpectedJsonContentType(GetLogger(httpContext), contentType ?? "(none)");
        }

        [LoggerMessage(RequestDelegateCreationLogging.UnexpectedJsonContentTypeEventId, LogLevel.Debug, RequestDelegateCreationLogging.UnexpectedJsonContentTypeLogMessage, EventName = RequestDelegateCreationLogging.UnexpectedJsonContentTypeEventName)]
        private static partial void UnexpectedJsonContentType(ILogger logger, string contentType);

        public static void UnexpectedNonFormContentType(HttpContext httpContext, string? contentType, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.UnexpectedFormContentTypeExceptionMessage, contentType);
                throw new BadHttpRequestException(message, StatusCodes.Status415UnsupportedMediaType);
            }

            UnexpectedNonFormContentType(GetLogger(httpContext), contentType ?? "(none)");
        }

        [LoggerMessage(RequestDelegateCreationLogging.UnexpectedFormContentTypeEventId, LogLevel.Debug, RequestDelegateCreationLogging.UnexpectedFormContentTypeLogMessage, EventName = RequestDelegateCreationLogging.UnexpectedFormContentTypeLogEventName)]
        private static partial void UnexpectedNonFormContentType(ILogger logger, string contentType);

        public static void InvalidFormRequestBody(HttpContext httpContext, string parameterTypeName, string parameterName, Exception exception, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.InvalidFormRequestBodyExceptionMessage, parameterTypeName, parameterName);
                throw new BadHttpRequestException(message, exception);
            }

            InvalidFormRequestBody(GetLogger(httpContext), parameterTypeName, parameterName, exception);
        }

        [LoggerMessage(RequestDelegateCreationLogging.InvalidFormRequestBodyEventId, LogLevel.Debug, RequestDelegateCreationLogging.InvalidFormRequestBodyLogMessage, EventName = RequestDelegateCreationLogging.InvalidFormRequestBodyEventName)]
        private static partial void InvalidFormRequestBody(ILogger logger, string parameterType, string parameterName, Exception exception);

        public static void InvalidAntiforgeryToken(HttpContext httpContext, string parameterTypeName, string parameterName, Exception exception, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.InvalidAntiforgeryTokenExceptionMessage, parameterTypeName, parameterName);
                throw new BadHttpRequestException(message, exception);
            }

            InvalidAntiforgeryToken(GetLogger(httpContext), parameterTypeName, parameterName, exception);
        }

        [LoggerMessage(RequestDelegateCreationLogging.InvalidAntiforgeryTokenEventId, LogLevel.Debug, RequestDelegateCreationLogging.InvalidAntiforgeryTokenLogMessage, EventName = RequestDelegateCreationLogging.InvalidAntiforgeryTokenEventName)]
        private static partial void InvalidAntiforgeryToken(ILogger logger, string parameterType, string parameterName, Exception exception);

        public static void FormDataMappingFailed(HttpContext httpContext, string parameterTypeName, string parameterName, FormDataMappingException exception, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, exception.Error.Message.Format, exception.Error.Message.GetArguments());
                throw new BadHttpRequestException(message, exception);
            }

            FormDataMappingFailed(GetLogger(httpContext), parameterTypeName, parameterName, exception);
        }

        [LoggerMessage(RequestDelegateCreationLogging.FormDataMappingFailedEventId, LogLevel.Debug, RequestDelegateCreationLogging.FormDataMappingFailedLogMessage, EventName = RequestDelegateCreationLogging.FormDataMappingFailedEventName)]
        private static partial void FormDataMappingFailed(ILogger logger, string parameterType, string parameterName, Exception exception);

        public static void UnexpectedRequestWithoutBody(HttpContext httpContext, string parameterTypeName, string parameterName, bool shouldThrow)
        {
            if (shouldThrow)
            {
                var message = string.Format(CultureInfo.InvariantCulture, RequestDelegateCreationLogging.UnexpectedRequestWithoutBodyExceptionMessage, parameterTypeName, parameterName);
                throw new BadHttpRequestException(message);
            }

            UnexpectedRequestWithoutBody(GetLogger(httpContext), parameterTypeName, parameterName);
        }

        [LoggerMessage(RequestDelegateCreationLogging.UnexpectedRequestWithoutBodyEventId, LogLevel.Debug, RequestDelegateCreationLogging.UnexpectedRequestWithoutBodyLogMessage, EventName = RequestDelegateCreationLogging.UnexpectedRequestWithoutBodyEventName)]
        private static partial void UnexpectedRequestWithoutBody(ILogger logger, string parameterType, string parameterName);

        private static ILogger GetLogger(HttpContext httpContext)
        {
            var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
            return loggerFactory.CreateLogger(typeof(RequestDelegateFactory));
        }
    }

    private static void EnsureRequestTaskOfNotNull<T>(Task<T?> task) where T : IResult
    {
        if (task is null)
        {
            throw new InvalidOperationException("The IResult in Task<IResult> response must not be null.");
        }
    }

    private static void EnsureRequestTaskNotNull(Task? task)
    {
        if (task is null)
        {
            throw new InvalidOperationException("The Task returned by the Delegate must not be null.");
        }
    }

    private static IResult EnsureRequestResultNotNull(IResult? result)
    {
        if (result is null)
        {
            throw new InvalidOperationException("The IResult returned by the Delegate must not be null.");
        }

        return result;
    }

    private static string BuildErrorMessageForMultipleBodyParameters(RequestDelegateFactoryContext factoryContext)
    {
        var errorMessage = new StringBuilder();
        errorMessage.AppendLine("Failure to infer one or more parameters.");
        errorMessage.AppendLine("Below is the list of parameters that we found: ");
        errorMessage.AppendLine();
        errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
        errorMessage.AppendLine("---------------------------------------------------------------------------------");

        FormatTrackedParameters(factoryContext, errorMessage);

        errorMessage.AppendLine().AppendLine();
        errorMessage.AppendLine("Did you mean to register the \"UNKNOWN\" parameters as a Service?")
            .AppendLine();
        return errorMessage.ToString();
    }

    private static string BuildErrorMessageForInferredBodyParameter(RequestDelegateFactoryContext factoryContext)
    {
        var errorMessage = new StringBuilder();
        errorMessage.AppendLine("Body was inferred but the method does not allow inferred body parameters.");
        errorMessage.AppendLine("Below is the list of parameters that we found: ");
        errorMessage.AppendLine();
        errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
        errorMessage.AppendLine("---------------------------------------------------------------------------------");

        FormatTrackedParameters(factoryContext, errorMessage);

        errorMessage.AppendLine().AppendLine();
        errorMessage.AppendLine("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromServices] or [FromBody] attribute?")
            .AppendLine();
        return errorMessage.ToString();
    }

    private static string BuildErrorMessageForFormAndJsonBodyParameters(RequestDelegateFactoryContext factoryContext)
    {
        var errorMessage = new StringBuilder();
        errorMessage.AppendLine("An action cannot use both form and JSON body parameters.");
        errorMessage.AppendLine("Below is the list of parameters that we found: ");
        errorMessage.AppendLine();
        errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
        errorMessage.AppendLine("---------------------------------------------------------------------------------");

        FormatTrackedParameters(factoryContext, errorMessage);

        return errorMessage.ToString();
    }

    private static void FormatTrackedParameters(RequestDelegateFactoryContext factoryContext, StringBuilder errorMessage)
    {
        foreach (var kv in factoryContext.TrackedParameters)
        {
            errorMessage.AppendLine(FormattableString.Invariant($"{kv.Key,-19} | {kv.Value,-15}"));
        }
    }

    private sealed class RdfEndpointBuilder : EndpointBuilder
    {
        public RdfEndpointBuilder(IServiceProvider applicationServices)
        {
            ApplicationServices = applicationServices;
        }

        public override Endpoint Build()
        {
            throw new NotSupportedException();
        }
    }

    private sealed class EmptyServiceProvider : IServiceProvider
    {
        public static EmptyServiceProvider Instance { get; } = new EmptyServiceProvider();
        public object? GetService(Type serviceType) => null;
    }
}
