Skip to content

Commit 64a94f0

Browse files
authored
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.
1 parent 698d872 commit 64a94f0

6 files changed

+240
-69
lines changed

src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" />
1415
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" Link="StreamCopyOperationInternal.cs" />
1516
</ItemGroup>
1617

src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Set(string! name, object? valu
152152
Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetCookie.get -> System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue!>!
153153
Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetCookie.set -> void
154154
Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetList<T>(string! name, System.Collections.Generic.IList<T>? values) -> void
155+
Microsoft.AspNetCore.Http.RequestDelegateFactory
155156
override Microsoft.AspNetCore.Http.Extensions.QueryBuilder.Equals(object? obj) -> bool
156157
override Microsoft.AspNetCore.Http.Extensions.QueryBuilder.ToString() -> string!
157158
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
168169
static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.GetTypedHeaders(this Microsoft.AspNetCore.Http.HttpRequest! request) -> Microsoft.AspNetCore.Http.Headers.RequestHeaders!
169170
static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.GetTypedHeaders(this Microsoft.AspNetCore.Http.HttpResponse! response) -> Microsoft.AspNetCore.Http.Headers.ResponseHeaders!
170171
static Microsoft.AspNetCore.Http.HttpContextServerVariableExtensions.GetServerVariable(this Microsoft.AspNetCore.Http.HttpContext! context, string! variableName) -> string?
172+
static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Delegate! action) -> Microsoft.AspNetCore.Http.RequestDelegate!
173+
static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Reflection.MethodInfo! methodInfo) -> Microsoft.AspNetCore.Http.RequestDelegate!
174+
static Microsoft.AspNetCore.Http.RequestDelegateFactory.Create(System.Reflection.MethodInfo! methodInfo, System.Func<Microsoft.AspNetCore.Http.HttpContext!, object!>! targetFactory) -> Microsoft.AspNetCore.Http.RequestDelegate!
171175
static Microsoft.AspNetCore.Http.ResponseExtensions.Clear(this Microsoft.AspNetCore.Http.HttpResponse! response) -> void
172176
static Microsoft.AspNetCore.Http.ResponseExtensions.Redirect(this Microsoft.AspNetCore.Http.HttpResponse! response, string! location, bool permanent, bool preserveMethod) -> void
173177
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!

src/Http/Routing/src/Internal/MapActionExpressionTreeBuilder.cs renamed to src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 110 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,26 @@
1111
using System.Reflection;
1212
using System.Threading;
1313
using System.Threading.Tasks;
14-
using Microsoft.AspNetCore.Http;
1514
using Microsoft.AspNetCore.Http.Metadata;
1615
using Microsoft.Extensions.DependencyInjection;
1716
using Microsoft.Extensions.Internal;
1817
using Microsoft.Extensions.Logging;
1918

20-
namespace Microsoft.AspNetCore.Routing.Internal
19+
namespace Microsoft.AspNetCore.Http
2120
{
22-
internal static class MapActionExpressionTreeBuilder
21+
/// <summary>
22+
/// Creates <see cref="RequestDelegate"/> implementations from <see cref="Delegate"/> request handlers.
23+
/// </summary>
24+
public static class RequestDelegateFactory
2325
{
2426
private static readonly MethodInfo ChangeTypeMethodInfo = GetMethodInfo<Func<object, Type, object>>((value, type) => Convert.ChangeType(value, type, CultureInfo.InvariantCulture));
25-
private static readonly MethodInfo ExecuteTaskOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteTask), BindingFlags.NonPublic | BindingFlags.Static)!;
26-
private static readonly MethodInfo ExecuteTaskOfStringMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
27-
private static readonly MethodInfo ExecuteValueTaskOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
28-
private static readonly MethodInfo ExecuteValueTaskMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
29-
private static readonly MethodInfo ExecuteValueTaskOfStringMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
30-
private static readonly MethodInfo ExecuteTaskResultOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
31-
private static readonly MethodInfo ExecuteValueResultTaskOfTMethodInfo = typeof(MapActionExpressionTreeBuilder).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
27+
private static readonly MethodInfo ExecuteTaskOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTask), BindingFlags.NonPublic | BindingFlags.Static)!;
28+
private static readonly MethodInfo ExecuteTaskOfStringMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
29+
private static readonly MethodInfo ExecuteValueTaskOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
30+
private static readonly MethodInfo ExecuteValueTaskMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
31+
private static readonly MethodInfo ExecuteValueTaskOfStringMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
32+
private static readonly MethodInfo ExecuteTaskResultOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
33+
private static readonly MethodInfo ExecuteValueResultTaskOfTMethodInfo = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
3234
private static readonly MethodInfo GetRequiredServiceMethodInfo = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
3335
private static readonly MethodInfo ResultWriteResponseAsync = typeof(IResult).GetMethod(nameof(IResult.ExecuteAsync), BindingFlags.Public | BindingFlags.Instance)!;
3436
private static readonly MethodInfo StringResultWriteResponseAsync = GetMethodInfo<Func<HttpResponse, string, Task>>((response, text) => HttpResponseWritingExtensions.WriteAsync(response, text, default));
@@ -44,7 +46,85 @@ internal static class MapActionExpressionTreeBuilder
4446
private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextParameter, nameof(HttpContext.Response));
4547
private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextParameter, nameof(HttpContext.RequestAborted));
4648

47-
public static RequestDelegate BuildRequestDelegate(Delegate action)
49+
/// <summary>
50+
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="action"/>.
51+
/// </summary>
52+
/// <param name="action">A request handler with any number of custom parameters that often produces a response with its return value.</param>
53+
/// <returns>The <see cref="RequestDelegate"/>.</returns>
54+
public static RequestDelegate Create(Delegate action)
55+
{
56+
if (action is null)
57+
{
58+
throw new ArgumentNullException(nameof(action));
59+
}
60+
61+
var targetExpression = action.Target switch
62+
{
63+
object => Expression.Convert(TargetArg, action.Target.GetType()),
64+
null => null,
65+
};
66+
67+
var untargetedRequestDelegate = CreateRequestDelegate(action.Method, targetExpression);
68+
69+
return httpContext =>
70+
{
71+
return untargetedRequestDelegate(action.Target, httpContext);
72+
};
73+
}
74+
75+
/// <summary>
76+
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
77+
/// </summary>
78+
/// <param name="methodInfo">A static request handler with any number of custom parameters that often produces a response with its return value.</param>
79+
/// <returns>The <see cref="RequestDelegate"/>.</returns>
80+
public static RequestDelegate Create(MethodInfo methodInfo)
81+
{
82+
if (methodInfo is null)
83+
{
84+
throw new ArgumentNullException(nameof(methodInfo));
85+
}
86+
87+
var untargetedRequestDelegate = CreateRequestDelegate(methodInfo, targetExpression: null);
88+
89+
return httpContext =>
90+
{
91+
return untargetedRequestDelegate(null, httpContext);
92+
};
93+
}
94+
95+
/// <summary>
96+
/// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
97+
/// </summary>
98+
/// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
99+
/// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
100+
/// <returns>The <see cref="RequestDelegate"/>.</returns>
101+
public static RequestDelegate Create(MethodInfo methodInfo, Func<HttpContext, object> targetFactory)
102+
{
103+
if (methodInfo is null)
104+
{
105+
throw new ArgumentNullException(nameof(methodInfo));
106+
}
107+
108+
if (targetFactory is null)
109+
{
110+
throw new ArgumentNullException(nameof(targetFactory));
111+
}
112+
113+
if (methodInfo.DeclaringType is null)
114+
{
115+
throw new ArgumentException($"A {nameof(targetFactory)} was provided, but {nameof(methodInfo)} does not have a Declaring type.");
116+
}
117+
118+
var targetExpression = Expression.Convert(TargetArg, methodInfo.DeclaringType);
119+
var untargetedRequestDelegate = CreateRequestDelegate(methodInfo, targetExpression);
120+
121+
return httpContext =>
122+
{
123+
return untargetedRequestDelegate(targetFactory(httpContext), httpContext);
124+
};
125+
}
126+
127+
private static Func<object?, HttpContext, Task> CreateRequestDelegate(MethodInfo methodInfo, Expression? targetExpression)
48128
{
49129
// Non void return type
50130

@@ -62,8 +142,6 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
62142
// return default;
63143
// }
64144

65-
var method = action.Method;
66-
67145
var consumeBodyDirectly = false;
68146
var consumeBodyAsForm = false;
69147
Type? bodyType = null;
@@ -72,7 +150,7 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
72150
// This argument represents the deserialized body returned from IHttpRequestReader
73151
// when the method has a FromBody attribute declared
74152

75-
var methodParameters = method.GetParameters();
153+
var methodParameters = methodInfo.GetParameters();
76154
var args = new List<Expression>(methodParameters.Length);
77155

78156
foreach (var parameter in methodParameters)
@@ -156,18 +234,17 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
156234

157235
MethodCallExpression methodCall;
158236

159-
if (action.Target is null)
237+
if (targetExpression is null)
160238
{
161-
methodCall = Expression.Call(method, args);
239+
methodCall = Expression.Call(methodInfo, args);
162240
}
163241
else
164242
{
165-
var castedTarget = Expression.Convert(TargetArg, action.Target.GetType());
166-
methodCall = Expression.Call(castedTarget, method, args);
243+
methodCall = Expression.Call(targetExpression, methodInfo, args);
167244
}
168245

169246
// Exact request delegate match
170-
if (method.ReturnType == typeof(void))
247+
if (methodInfo.ReturnType == typeof(void))
171248
{
172249
var bodyExpressions = new List<Expression>
173250
{
@@ -177,22 +254,22 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
177254

178255
body = Expression.Block(bodyExpressions);
179256
}
180-
else if (AwaitableInfo.IsTypeAwaitable(method.ReturnType, out var info))
257+
else if (AwaitableInfo.IsTypeAwaitable(methodInfo.ReturnType, out var info))
181258
{
182-
if (method.ReturnType == typeof(Task))
259+
if (methodInfo.ReturnType == typeof(Task))
183260
{
184261
body = methodCall;
185262
}
186-
else if (method.ReturnType == typeof(ValueTask))
263+
else if (methodInfo.ReturnType == typeof(ValueTask))
187264
{
188265
body = Expression.Call(
189266
ExecuteValueTaskMethodInfo,
190267
methodCall);
191268
}
192-
else if (method.ReturnType.IsGenericType &&
193-
method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
269+
else if (methodInfo.ReturnType.IsGenericType &&
270+
methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
194271
{
195-
var typeArg = method.ReturnType.GetGenericArguments()[0];
272+
var typeArg = methodInfo.ReturnType.GetGenericArguments()[0];
196273

197274
if (typeof(IResult).IsAssignableFrom(typeArg))
198275
{
@@ -220,10 +297,10 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
220297
}
221298
}
222299
}
223-
else if (method.ReturnType.IsGenericType &&
224-
method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
300+
else if (methodInfo.ReturnType.IsGenericType &&
301+
methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
225302
{
226-
var typeArg = method.ReturnType.GetGenericArguments()[0];
303+
var typeArg = methodInfo.ReturnType.GetGenericArguments()[0];
227304

228305
if (typeof(IResult).IsAssignableFrom(typeArg))
229306
{
@@ -254,18 +331,18 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
254331
else
255332
{
256333
// TODO: Handle custom awaitables
257-
throw new NotSupportedException($"Unsupported return type: {method.ReturnType}");
334+
throw new NotSupportedException($"Unsupported return type: {methodInfo.ReturnType}");
258335
}
259336
}
260-
else if (typeof(IResult).IsAssignableFrom(method.ReturnType))
337+
else if (typeof(IResult).IsAssignableFrom(methodInfo.ReturnType))
261338
{
262339
body = Expression.Call(methodCall, ResultWriteResponseAsync, HttpContextParameter);
263340
}
264-
else if (method.ReturnType == typeof(string))
341+
else if (methodInfo.ReturnType == typeof(string))
265342
{
266343
body = Expression.Call(StringResultWriteResponseAsync, HttpResponseExpr, methodCall, Expression.Constant(CancellationToken.None));
267344
}
268-
else if (method.ReturnType.IsValueType)
345+
else if (methodInfo.ReturnType.IsValueType)
269346
{
270347
var box = Expression.TypeAs(methodCall, typeof(object));
271348
body = Expression.Call(JsonResultWriteResponseAsync, HttpResponseExpr, box, Expression.Constant(CancellationToken.None));
@@ -357,10 +434,7 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
357434
requestDelegate = invoker;
358435
}
359436

360-
return httpContext =>
361-
{
362-
return requestDelegate(action.Target, httpContext);
363-
};
437+
return requestDelegate;
364438
}
365439

366440
private static ILogger GetLogger(HttpContext httpContext)

0 commit comments

Comments
 (0)