From 64a94f01ecb7fadeaffd242e159ae593e117c23c Mon Sep 17 00:00:00 2001
From: Stephen Halter <halter73@gmail.com>
Date: Mon, 29 Mar 2021 17:21:36 -0700
Subject: [PATCH] Make RequestDelegateFactory public (was
 MapActionExpressionTreeBuilder) (#31171)

* Make RequestDelegateBuilder public

- Formerly known as MapActionExpressionTreeBuilder

* Add new BuildRequestDelegate overloads

* Address PR feedback

* RequestDelegateBuilder -> RequestDelegateFactory

* Build -> Create

* Address final review feedback.
---
 ...icrosoft.AspNetCore.Http.Extensions.csproj |   1 +
 .../src/PublicAPI.Unshipped.txt               |   4 +
 .../src/RequestDelegateFactory.cs}            | 146 ++++++++++++-----
 .../test/RequestDelegateFactoryTests.cs}      | 153 ++++++++++++++----
 ...MapActionEndpointRouteBuilderExtensions.cs |   4 +-
 .../src/Microsoft.AspNetCore.Routing.csproj   |   1 -
 6 files changed, 240 insertions(+), 69 deletions(-)
 rename src/Http/{Routing/src/Internal/MapActionExpressionTreeBuilder.cs => Http.Extensions/src/RequestDelegateFactory.cs} (79%)
 rename src/Http/{Routing/test/UnitTests/Internal/MapActionExpressionTreeBuilderTest.cs => Http.Extensions/test/RequestDelegateFactoryTests.cs} (85%)

diff --git a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
index 123ae96be7a..94a3e73732a 100644
--- a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
+++ b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
@@ -11,6 +11,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" />
     <Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" Link="StreamCopyOperationInternal.cs" />
   </ItemGroup>
 
diff --git a/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
index c82589d1209..33854973715 100644
--- a/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
+++ b/src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
@@ -152,6 +152,7 @@ Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Set(string! name, object? valu
 Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetCookie.get -> System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue!>!
 Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetCookie.set -> void
 Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetList<T>(string! name, System.Collections.Generic.IList<T>? values) -> void
+Microsoft.AspNetCore.Http.RequestDelegateFactory
 override Microsoft.AspNetCore.Http.Extensions.QueryBuilder.Equals(object? obj) -> bool
 override Microsoft.AspNetCore.Http.Extensions.QueryBuilder.ToString() -> string!
 static Microsoft.AspNetCore.Http.Extensions.HttpRequestMultipartExtensions.GetMultipartBoundary(this Microsoft.AspNetCore.Http.HttpRequest! request) -> string!
@@ -168,6 +169,9 @@ static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.AppendList<T>(th
 static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.GetTypedHeaders(this Microsoft.AspNetCore.Http.HttpRequest! request) -> Microsoft.AspNetCore.Http.Headers.RequestHeaders!
 static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.GetTypedHeaders(this Microsoft.AspNetCore.Http.HttpResponse! response) -> Microsoft.AspNetCore.Http.Headers.ResponseHeaders!
 static Microsoft.AspNetCore.Http.HttpContextServerVariableExtensions.GetServerVariable(this Microsoft.AspNetCore.Http.HttpContext! context, string! variableName) -> string?
+static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Delegate! action) -> Microsoft.AspNetCore.Http.RequestDelegate!
+static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Reflection.MethodInfo! methodInfo) -> Microsoft.AspNetCore.Http.RequestDelegate!
+static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Reflection.MethodInfo! methodInfo, System.Func<Microsoft.AspNetCore.Http.HttpContext!, object!>! targetFactory) -> Microsoft.AspNetCore.Http.RequestDelegate!
 static Microsoft.AspNetCore.Http.ResponseExtensions.Clear(this Microsoft.AspNetCore.Http.HttpResponse! response) -> void
 static Microsoft.AspNetCore.Http.ResponseExtensions.Redirect(this Microsoft.AspNetCore.Http.HttpResponse! response, string! location, bool permanent, bool preserveMethod) -> void
 static Microsoft.AspNetCore.Http.SendFileResponseExtensions.SendFileAsync(this Microsoft.AspNetCore.Http.HttpResponse! response, Microsoft.Extensions.FileProviders.IFileInfo! file, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
diff --git a/src/Http/Routing/src/Internal/MapActionExpressionTreeBuilder.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
similarity index 79%
rename from src/Http/Routing/src/Internal/MapActionExpressionTreeBuilder.cs
rename to src/Http/Http.Extensions/src/RequestDelegateFactory.cs
index 1e83d92c349..948c77fd094 100644
--- a/src/Http/Routing/src/Internal/MapActionExpressionTreeBuilder.cs
+++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
@@ -11,24 +11,26 @@ using System.Linq.Expressions;
 using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Metadata;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Internal;
 using Microsoft.Extensions.Logging;
 
-namespace Microsoft.AspNetCore.Routing.Internal
+namespace Microsoft.AspNetCore.Http
 {
-    internal static class MapActionExpressionTreeBuilder
+    /// <summary>
+    /// Creates <see cref="RequestDelegate"/> implementations from <see cref="Delegate"/> request handlers.
+    /// </summary>
+    public static class RequestDelegateFactory
     {
         private static readonly MethodInfo ChangeTypeMethodInfo = GetMethodInfo<Func<object, Type, object>>((value, type) => Convert.ChangeType(value, type, CultureInfo.InvariantCulture));
-        private static readonly MethodInfo ExecuteTaskOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteTask), BindingFlags.NonPublic | BindingFlags.Static)!;
-        private static readonly MethodInfo ExecuteTaskOfStringMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
-        private static readonly MethodInfo ExecuteValueTaskOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
-        private static readonly MethodInfo ExecuteValueTaskMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
-        private static readonly MethodInfo ExecuteValueTaskOfStringMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
-        private static readonly MethodInfo ExecuteTaskResultOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
-        private static readonly MethodInfo ExecuteValueResultTaskOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteTaskOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTask), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteTaskOfStringMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteValueTaskOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteValueTaskMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteValueTaskOfStringMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteTaskResultOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
+        private static readonly MethodInfo ExecuteValueResultTaskOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
         private static readonly MethodInfo GetRequiredServiceMethodInfo = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
         private static readonly MethodInfo ResultWriteResponseAsync = typeof(IResult).GetMethod(nameof(IResult.ExecuteAsync), BindingFlags.Public | BindingFlags.Instance)!;
         private static readonly MethodInfo StringResultWriteResponseAsync = GetMethodInfo<Func<HttpResponse, string, Task>>((response, text) => HttpResponseWritingExtensions.WriteAsync(response, text, default));
@@ -44,7 +46,85 @@ namespace Microsoft.AspNetCore.Routing.Internal
         private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextParameter, nameof(HttpContext.Response));
         private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextParameter, nameof(HttpContext.RequestAborted));
 
-        public static RequestDelegate BuildRequestDelegate(Delegate action)
+        /// <summary>
+        /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="action"/>.
+        /// </summary>
+        /// <param name="action">A request handler with any number of custom parameters that often produces a response with its return value.</param>
+        /// <returns>The <see cref="RequestDelegate"/>.</returns>
+        public static RequestDelegate Create(Delegate action)
+        {
+            if (action is null)
+            {
+                throw new ArgumentNullException(nameof(action));
+            }
+
+            var targetExpression = action.Target switch
+            {
+                object => Expression.Convert(TargetArg, action.Target.GetType()),
+                null => null,
+            };
+
+            var untargetedRequestDelegate = CreateRequestDelegate(action.Method, targetExpression);
+
+            return httpContext =>
+            {
+                return untargetedRequestDelegate(action.Target, httpContext);
+            };
+        }
+
+        /// <summary>
+        /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
+        /// </summary>
+        /// <param name="methodInfo">A static request handler with any number of custom parameters that often produces a response with its return value.</param>
+        /// <returns>The <see cref="RequestDelegate"/>.</returns>
+        public static RequestDelegate Create(MethodInfo methodInfo)
+        {
+            if (methodInfo is null)
+            {
+                throw new ArgumentNullException(nameof(methodInfo));
+            }
+
+            var untargetedRequestDelegate = CreateRequestDelegate(methodInfo, targetExpression: null);
+
+            return httpContext =>
+            {
+                return untargetedRequestDelegate(null, httpContext);
+            };
+        }
+
+        /// <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>
+        /// <returns>The <see cref="RequestDelegate"/>.</returns>
+        public static RequestDelegate Create(MethodInfo methodInfo, Func<HttpContext, object> targetFactory)
+        {
+            if (methodInfo is null)
+            {
+                throw new ArgumentNullException(nameof(methodInfo));
+            }
+
+            if (targetFactory is null)
+            {
+                throw new ArgumentNullException(nameof(targetFactory));
+            }
+
+            if (methodInfo.DeclaringType is null)
+            {
+                throw new ArgumentException($"A {nameof(targetFactory)} was provided, but {nameof(methodInfo)} does not have a Declaring type.");
+            }
+
+            var targetExpression = Expression.Convert(TargetArg, methodInfo.DeclaringType);
+            var untargetedRequestDelegate = CreateRequestDelegate(methodInfo, targetExpression);
+
+            return httpContext =>
+            {
+                return untargetedRequestDelegate(targetFactory(httpContext), httpContext);
+            };
+        }
+
+        private static Func<object?, HttpContext, Task> CreateRequestDelegate(MethodInfo methodInfo, Expression? targetExpression)
         {
             // Non void return type
 
@@ -62,8 +142,6 @@ namespace Microsoft.AspNetCore.Routing.Internal
             //     return default;
             // }
 
-            var method = action.Method;
-
             var consumeBodyDirectly = false;
             var consumeBodyAsForm = false;
             Type? bodyType = null;
@@ -72,7 +150,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             // This argument represents the deserialized body returned from IHttpRequestReader
             // when the method has a FromBody attribute declared
 
-            var methodParameters = method.GetParameters();
+            var methodParameters = methodInfo.GetParameters();
             var args = new List<Expression>(methodParameters.Length);
 
             foreach (var parameter in methodParameters)
@@ -156,18 +234,17 @@ namespace Microsoft.AspNetCore.Routing.Internal
 
             MethodCallExpression methodCall;
 
-            if (action.Target is null)
+            if (targetExpression is null)
             {
-                methodCall = Expression.Call(method, args);
+                methodCall = Expression.Call(methodInfo, args);
             }
             else
             {
-                var castedTarget = Expression.Convert(TargetArg, action.Target.GetType());
-                methodCall = Expression.Call(castedTarget, method, args);
+                methodCall = Expression.Call(targetExpression, methodInfo, args);
             }
 
             // Exact request delegate match
-            if (method.ReturnType == typeof(void))
+            if (methodInfo.ReturnType == typeof(void))
             {
                 var bodyExpressions = new List<Expression>
                 {
@@ -177,22 +254,22 @@ namespace Microsoft.AspNetCore.Routing.Internal
 
                 body = Expression.Block(bodyExpressions);
             }
-            else if (AwaitableInfo.IsTypeAwaitable(method.ReturnType, out var info))
+            else if (AwaitableInfo.IsTypeAwaitable(methodInfo.ReturnType, out var info))
             {
-                if (method.ReturnType == typeof(Task))
+                if (methodInfo.ReturnType == typeof(Task))
                 {
                     body = methodCall;
                 }
-                else if (method.ReturnType == typeof(ValueTask))
+                else if (methodInfo.ReturnType == typeof(ValueTask))
                 {
                     body = Expression.Call(
                                         ExecuteValueTaskMethodInfo,
                                         methodCall);
                 }
-                else if (method.ReturnType.IsGenericType &&
-                         method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
+                else if (methodInfo.ReturnType.IsGenericType &&
+                         methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
                 {
-                    var typeArg = method.ReturnType.GetGenericArguments()[0];
+                    var typeArg = methodInfo.ReturnType.GetGenericArguments()[0];
 
                     if (typeof(IResult).IsAssignableFrom(typeArg))
                     {
@@ -220,10 +297,10 @@ namespace Microsoft.AspNetCore.Routing.Internal
                         }
                     }
                 }
-                else if (method.ReturnType.IsGenericType &&
-                         method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
+                else if (methodInfo.ReturnType.IsGenericType &&
+                         methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
                 {
-                    var typeArg = method.ReturnType.GetGenericArguments()[0];
+                    var typeArg = methodInfo.ReturnType.GetGenericArguments()[0];
 
                     if (typeof(IResult).IsAssignableFrom(typeArg))
                     {
@@ -254,18 +331,18 @@ namespace Microsoft.AspNetCore.Routing.Internal
                 else
                 {
                     // TODO: Handle custom awaitables
-                    throw new NotSupportedException($"Unsupported return type: {method.ReturnType}");
+                    throw new NotSupportedException($"Unsupported return type: {methodInfo.ReturnType}");
                 }
             }
-            else if (typeof(IResult).IsAssignableFrom(method.ReturnType))
+            else if (typeof(IResult).IsAssignableFrom(methodInfo.ReturnType))
             {
                 body = Expression.Call(methodCall, ResultWriteResponseAsync, HttpContextParameter);
             }
-            else if (method.ReturnType == typeof(string))
+            else if (methodInfo.ReturnType == typeof(string))
             {
                 body = Expression.Call(StringResultWriteResponseAsync, HttpResponseExpr, methodCall, Expression.Constant(CancellationToken.None));
             }
-            else if (method.ReturnType.IsValueType)
+            else if (methodInfo.ReturnType.IsValueType)
             {
                 var box = Expression.TypeAs(methodCall, typeof(object));
                 body = Expression.Call(JsonResultWriteResponseAsync, HttpResponseExpr, box, Expression.Constant(CancellationToken.None));
@@ -357,10 +434,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
                 requestDelegate = invoker;
             }
 
-            return httpContext =>
-            {
-                return requestDelegate(action.Target, httpContext);
-            };
+            return requestDelegate;
         }
 
         private static ILogger GetLogger(HttpContext httpContext)
diff --git a/src/Http/Routing/test/UnitTests/Internal/MapActionExpressionTreeBuilderTest.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
similarity index 85%
rename from src/Http/Routing/test/UnitTests/Internal/MapActionExpressionTreeBuilderTest.cs
rename to src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
index 39372600077..36d4004cfbe 100644
--- a/src/Http/Routing/test/UnitTests/Internal/MapActionExpressionTreeBuilderTest.cs
+++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
@@ -7,6 +7,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Reflection;
 using System.Text;
 using System.Text.Json;
 using System.Threading;
@@ -22,7 +23,7 @@ using Xunit;
 
 namespace Microsoft.AspNetCore.Routing.Internal
 {
-    public class MapActionExpressionTreeBuilderTest
+    public class RequestDelegateFactoryTests
     {
         public static IEnumerable<object[]> NoResult
         {
@@ -85,13 +86,105 @@ namespace Microsoft.AspNetCore.Routing.Internal
         {
             var httpContext = new DefaultHttpContext();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
             Assert.True(httpContext.Items["invoked"] as bool?);
         }
 
+        private static void StaticTestActionBasicReflection(HttpContext httpContext)
+        {
+            httpContext.Items.Add("invoked", true);
+        }
+
+        [Fact]
+        public async Task StaticMethodInfoOverloadWorksWithBasicReflection()
+        {
+            var methodInfo = typeof(RequestDelegateFactoryTests).GetMethod(
+                nameof(StaticTestActionBasicReflection),
+                BindingFlags.NonPublic | BindingFlags.Static,
+                new[] { typeof(HttpContext) });
+
+            var requestDelegate = RequestDelegateFactory.Create(methodInfo!);
+
+            var httpContext = new DefaultHttpContext();
+
+            await requestDelegate(httpContext);
+
+            Assert.True(httpContext.Items["invoked"] as bool?);
+        }
+
+        private class TestNonStaticActionClass
+        {
+            private readonly object _invokedValue;
+
+            public TestNonStaticActionClass(object invokedValue)
+            {
+                _invokedValue = invokedValue;
+            }
+
+            private void NonStaticTestAction(HttpContext httpContext)
+            {
+                httpContext.Items.Add("invoked", _invokedValue);
+            }
+        }
+
+        [Fact]
+        public async Task NonStaticMethodInfoOverloadWorksWithBasicReflection()
+        {
+            var methodInfo = typeof(TestNonStaticActionClass).GetMethod(
+                "NonStaticTestAction",
+                BindingFlags.NonPublic | BindingFlags.Instance,
+                new[] { typeof(HttpContext) });
+
+            var invoked = false;
+
+            object GetTarget()
+            {
+                if (!invoked)
+                {
+                    invoked = true;
+                    return new TestNonStaticActionClass(1);
+                }
+
+                return new TestNonStaticActionClass(2);
+            }
+
+            var requestDelegate = RequestDelegateFactory.Create(methodInfo!, _ => GetTarget());
+
+            var httpContext = new DefaultHttpContext();
+
+            await requestDelegate(httpContext);
+
+            Assert.Equal(1, httpContext.Items["invoked"]);
+
+            httpContext = new DefaultHttpContext();
+
+            await requestDelegate(httpContext);
+
+            Assert.Equal(2, httpContext.Items["invoked"]);
+        }
+
+        [Fact]
+        public void BuildRequestDelegateThrowsArgumentNullExceptions()
+        {
+            var methodInfo = typeof(RequestDelegateFactoryTests).GetMethod(
+                nameof(StaticTestActionBasicReflection),
+                BindingFlags.NonPublic | BindingFlags.Static,
+                new[] { typeof(HttpContext) });
+
+            var exNullAction = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(action: null!));
+            var exNullMethodInfo1 = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(methodInfo: null!));
+            var exNullMethodInfo2 = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(methodInfo: null!, _ => 0));
+            var exNullTargetFactory = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(methodInfo!, targetFactory: null!));
+
+            Assert.Equal("action", exNullAction.ParamName);
+            Assert.Equal("methodInfo", exNullMethodInfo1.ParamName);
+            Assert.Equal("methodInfo", exNullMethodInfo2.ParamName);
+            Assert.Equal("targetFactory", exNullTargetFactory.ParamName);
+        }
+
         public static IEnumerable<object[]> FromRouteResult
         {
             get
@@ -136,7 +229,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.RouteValues[paramName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -179,7 +272,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
         {
             var httpContext = new DefaultHttpContext();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -197,7 +290,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
 
             httpContext.Request.RouteValues[paramName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -220,7 +313,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.RouteValues[specifiedName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -243,7 +336,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.RouteValues[unmatchedName] = unmatchedRouteParam.ToString(NumberFormatInfo.InvariantInfo);
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -271,7 +364,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.Query = query;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -294,7 +387,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.Headers[customHeaderName] = originalHeaderParam.ToString(NumberFormatInfo.InvariantInfo);
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -322,7 +415,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
             httpContext.Request.Body = new MemoryStream(requestBodyBytes);
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<Todo>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<Todo>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -341,7 +434,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Request.Headers["Content-Type"] = "application/json";
             httpContext.Request.Headers["Content-Length"] = "0";
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<Todo>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<Todo>)TestAction);
 
             await Assert.ThrowsAsync<JsonException>(() => requestDelegate(httpContext));
         }
@@ -360,7 +453,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Request.Headers["Content-Type"] = "application/json";
             httpContext.Request.Headers["Content-Length"] = "0";
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<Todo>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<Todo>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -384,7 +477,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Request.Headers["Content-Type"] = "application/json";
             httpContext.Request.Headers["Content-Length"] = "0";
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<BodyStruct>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<BodyStruct>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -414,7 +507,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Features.Set<IHttpRequestLifetimeFeature>(new TestHttpRequestLifetimeFeature());
             httpContext.RequestServices = serviceCollection.BuildServiceProvider();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<Todo>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<Todo>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -450,7 +543,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Features.Set<IHttpRequestLifetimeFeature>(new TestHttpRequestLifetimeFeature());
             httpContext.RequestServices = serviceCollection.BuildServiceProvider();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<Todo>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<Todo>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -485,7 +578,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.Form = form;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -515,7 +608,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Features.Set<IHttpRequestLifetimeFeature>(new TestHttpRequestLifetimeFeature());
             httpContext.RequestServices = serviceCollection.BuildServiceProvider();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -551,7 +644,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             httpContext.Features.Set<IHttpRequestLifetimeFeature>(new TestHttpRequestLifetimeFeature());
             httpContext.RequestServices = serviceCollection.BuildServiceProvider();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<int>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -571,8 +664,8 @@ namespace Microsoft.AspNetCore.Routing.Internal
             void TestAction([FromBody] int value1, [FromForm] int value2) { }
             void TestActionWithFlippedParams([FromForm] int value1, [FromBody] int value2) { }
 
-            Assert.Throws<InvalidOperationException>(() => MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int, int>)TestAction));
-            Assert.Throws<InvalidOperationException>(() => MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int, int>)TestActionWithFlippedParams));
+            Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create((Action<int, int>)TestAction));
+            Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create((Action<int, int>)TestActionWithFlippedParams));
         }
 
         [Fact]
@@ -580,7 +673,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
         {
             void TestAction([FromBody] int value1, [FromBody] int value2) { }
 
-            Assert.Throws<InvalidOperationException>(() => MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int, int>)TestAction));
+            Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create((Action<int, int>)TestAction));
         }
 
         [Fact]
@@ -600,7 +693,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.RequestServices = serviceCollection.BuildServiceProvider();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<MyService>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<MyService>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -619,7 +712,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
 
             var httpContext = new DefaultHttpContext();
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<HttpContext>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<HttpContext>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -639,7 +732,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var httpContext = new DefaultHttpContext();
             httpContext.Request.Headers["Content-Type"] = "application/x-www-form-urlencoded";
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<IFormCollection>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<IFormCollection>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -662,7 +755,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
                 RequestAborted = cts.Token
             };
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<CancellationToken>)TestAction);
+            var requestDelegate = RequestDelegateFactory.Create((Action<CancellationToken>)TestAction);
 
             await requestDelegate(httpContext);
 
@@ -706,7 +799,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var responseBodyStream = new MemoryStream();
             httpContext.Response.Body = responseBodyStream;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -755,7 +848,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var responseBodyStream = new MemoryStream();
             httpContext.Response.Body = responseBodyStream;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -798,7 +891,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var responseBodyStream = new MemoryStream();
             httpContext.Response.Body = responseBodyStream;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -839,7 +932,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var responseBodyStream = new MemoryStream();
             httpContext.Response.Body = responseBodyStream;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
@@ -880,7 +973,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
             var responseBodyStream = new MemoryStream();
             httpContext.Response.Body = responseBodyStream;
 
-            var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);
+            var requestDelegate = RequestDelegateFactory.Create(@delegate);
 
             await requestDelegate(httpContext);
 
diff --git a/src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs
index e816c1cdb77..e071bae5ac8 100644
--- a/src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/MapActionEndpointRouteBuilderExtensions.cs
@@ -5,8 +5,8 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Routing;
-using Microsoft.AspNetCore.Routing.Internal;
 using Microsoft.AspNetCore.Routing.Patterns;
 
 namespace Microsoft.AspNetCore.Builder
@@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Builder
             const int defaultOrder = 0;
 
             var builder = new RouteEndpointBuilder(
-                MapActionExpressionTreeBuilder.BuildRequestDelegate(action),
+                RequestDelegateFactory.Create(action),
                 pattern,
                 defaultOrder)
             {
diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
index 67723bd1d17..e96f32ceddf 100644
--- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
+++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj
@@ -24,7 +24,6 @@ Microsoft.AspNetCore.Routing.RouteCollection</Description>
 
   <ItemGroup>
     <Compile Include="$(SharedSourceRoot)PropertyHelper\*.cs" />
-    <Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" />
   </ItemGroup>
 
   <ItemGroup>
-- 
GitLab