Skip to content

Commit 23cedca

Browse files
committed
Add new BuildRequestDelegate overloads
1 parent 287d684 commit 23cedca

File tree

3 files changed

+189
-26
lines changed

3 files changed

+189
-26
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.GetTypedHeaders(
170170
static Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions.GetTypedHeaders(this Microsoft.AspNetCore.Http.HttpResponse! response) -> Microsoft.AspNetCore.Http.Headers.ResponseHeaders!
171171
static Microsoft.AspNetCore.Http.HttpContextServerVariableExtensions.GetServerVariable(this Microsoft.AspNetCore.Http.HttpContext! context, string! variableName) -> string?
172172
static Microsoft.AspNetCore.Http.RequestDelegateBuilder.BuildRequestDelegate(System.Delegate! action) -> Microsoft.AspNetCore.Http.RequestDelegate!
173+
static Microsoft.AspNetCore.Http.RequestDelegateBuilder.BuildRequestDelegate(System.Reflection.MethodInfo! methodInfo) -> Microsoft.AspNetCore.Http.RequestDelegate!
174+
static Microsoft.AspNetCore.Http.RequestDelegateBuilder.BuildRequestDelegate(System.Reflection.MethodInfo! methodInfo, System.Func<Microsoft.AspNetCore.Http.HttpContext!, object!>! targetFactory) -> Microsoft.AspNetCore.Http.RequestDelegate!
173175
static Microsoft.AspNetCore.Http.ResponseExtensions.Clear(this Microsoft.AspNetCore.Http.HttpResponse! response) -> void
174176
static Microsoft.AspNetCore.Http.ResponseExtensions.Redirect(this Microsoft.AspNetCore.Http.HttpResponse! response, string! location, bool permanent, bool preserveMethod) -> void
175177
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/Http.Extensions/src/RequestDelegateBuilder.cs

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,80 @@ public static class RequestDelegateBuilder
5252
/// <param name="action">A request handler with any number of custom parameters that often produces a response with its return value.</param>
5353
/// <returns>The <see cref="RequestDelegate"/></returns>
5454
public static RequestDelegate BuildRequestDelegate(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+
{ } => Expression.Convert(TargetArg, action.Target.GetType()),
64+
null => null,
65+
};
66+
67+
var untargetedRequestDelegate = BuildRequestDelegate(action.Method, targetExpression);
68+
69+
return httpContext =>
70+
{
71+
return untargetedRequestDelegate(action.Target, httpContext);
72+
};
73+
}
74+
75+
/// <summary>
76+
/// Builds 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 BuildRequestDelegate(MethodInfo methodInfo)
81+
{
82+
if (methodInfo is null)
83+
{
84+
throw new ArgumentNullException(nameof(methodInfo));
85+
}
86+
87+
var untargetedRequestDelegate = BuildRequestDelegate(methodInfo, targetExpression: null);
88+
89+
return httpContext =>
90+
{
91+
return untargetedRequestDelegate(null, httpContext);
92+
};
93+
}
94+
95+
96+
/// <summary>
97+
/// Builds a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
98+
/// </summary>
99+
/// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
100+
/// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method. If the </param>
101+
/// <returns>The <see cref="RequestDelegate"/></returns>
102+
public static RequestDelegate BuildRequestDelegate(MethodInfo methodInfo, Func<HttpContext, object> targetFactory)
103+
{
104+
if (methodInfo is null)
105+
{
106+
throw new ArgumentNullException(nameof(methodInfo));
107+
}
108+
109+
if (targetFactory is null)
110+
{
111+
throw new ArgumentNullException(nameof(targetFactory));
112+
}
113+
114+
if (methodInfo.DeclaringType is null)
115+
{
116+
throw new ArgumentException($"A {nameof(targetFactory)} was provided, but {nameof(methodInfo)} does not have a Declaring type.");
117+
}
118+
119+
var targetExpression = Expression.Convert(TargetArg, methodInfo.DeclaringType);
120+
var untargetedRequestDelegate = BuildRequestDelegate(methodInfo, targetExpression);
121+
122+
return httpContext =>
123+
{
124+
return untargetedRequestDelegate(targetFactory(httpContext), httpContext);
125+
};
126+
}
127+
128+
private static Func<object?, HttpContext, Task> BuildRequestDelegate(MethodInfo methodInfo, Expression? targetExpression)
55129
{
56130
// Non void return type
57131

@@ -69,8 +143,6 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
69143
// return default;
70144
// }
71145

72-
var method = action.Method;
73-
74146
var consumeBodyDirectly = false;
75147
var consumeBodyAsForm = false;
76148
Type? bodyType = null;
@@ -79,7 +151,7 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
79151
// This argument represents the deserialized body returned from IHttpRequestReader
80152
// when the method has a FromBody attribute declared
81153

82-
var methodParameters = method.GetParameters();
154+
var methodParameters = methodInfo.GetParameters();
83155
var args = new List<Expression>(methodParameters.Length);
84156

85157
foreach (var parameter in methodParameters)
@@ -163,18 +235,17 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
163235

164236
MethodCallExpression methodCall;
165237

166-
if (action.Target is null)
238+
if (targetExpression is null)
167239
{
168-
methodCall = Expression.Call(method, args);
240+
methodCall = Expression.Call(methodInfo, args);
169241
}
170242
else
171243
{
172-
var castedTarget = Expression.Convert(TargetArg, action.Target.GetType());
173-
methodCall = Expression.Call(castedTarget, method, args);
244+
methodCall = Expression.Call(targetExpression, methodInfo, args);
174245
}
175246

176247
// Exact request delegate match
177-
if (method.ReturnType == typeof(void))
248+
if (methodInfo.ReturnType == typeof(void))
178249
{
179250
var bodyExpressions = new List<Expression>
180251
{
@@ -184,22 +255,22 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
184255

185256
body = Expression.Block(bodyExpressions);
186257
}
187-
else if (AwaitableInfo.IsTypeAwaitable(method.ReturnType, out var info))
258+
else if (AwaitableInfo.IsTypeAwaitable(methodInfo.ReturnType, out var info))
188259
{
189-
if (method.ReturnType == typeof(Task))
260+
if (methodInfo.ReturnType == typeof(Task))
190261
{
191262
body = methodCall;
192263
}
193-
else if (method.ReturnType == typeof(ValueTask))
264+
else if (methodInfo.ReturnType == typeof(ValueTask))
194265
{
195266
body = Expression.Call(
196267
ExecuteValueTaskMethodInfo,
197268
methodCall);
198269
}
199-
else if (method.ReturnType.IsGenericType &&
200-
method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
270+
else if (methodInfo.ReturnType.IsGenericType &&
271+
methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
201272
{
202-
var typeArg = method.ReturnType.GetGenericArguments()[0];
273+
var typeArg = methodInfo.ReturnType.GetGenericArguments()[0];
203274

204275
if (typeof(IResult).IsAssignableFrom(typeArg))
205276
{
@@ -227,10 +298,10 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
227298
}
228299
}
229300
}
230-
else if (method.ReturnType.IsGenericType &&
231-
method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
301+
else if (methodInfo.ReturnType.IsGenericType &&
302+
methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
232303
{
233-
var typeArg = method.ReturnType.GetGenericArguments()[0];
304+
var typeArg = methodInfo.ReturnType.GetGenericArguments()[0];
234305

235306
if (typeof(IResult).IsAssignableFrom(typeArg))
236307
{
@@ -261,18 +332,18 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
261332
else
262333
{
263334
// TODO: Handle custom awaitables
264-
throw new NotSupportedException($"Unsupported return type: {method.ReturnType}");
335+
throw new NotSupportedException($"Unsupported return type: {methodInfo.ReturnType}");
265336
}
266337
}
267-
else if (typeof(IResult).IsAssignableFrom(method.ReturnType))
338+
else if (typeof(IResult).IsAssignableFrom(methodInfo.ReturnType))
268339
{
269340
body = Expression.Call(methodCall, ResultWriteResponseAsync, HttpContextParameter);
270341
}
271-
else if (method.ReturnType == typeof(string))
342+
else if (methodInfo.ReturnType == typeof(string))
272343
{
273344
body = Expression.Call(StringResultWriteResponseAsync, HttpResponseExpr, methodCall, Expression.Constant(CancellationToken.None));
274345
}
275-
else if (method.ReturnType.IsValueType)
346+
else if (methodInfo.ReturnType.IsValueType)
276347
{
277348
var box = Expression.TypeAs(methodCall, typeof(object));
278349
body = Expression.Call(JsonResultWriteResponseAsync, HttpResponseExpr, box, Expression.Constant(CancellationToken.None));
@@ -364,10 +435,7 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
364435
requestDelegate = invoker;
365436
}
366437

367-
return httpContext =>
368-
{
369-
return requestDelegate(action.Target, httpContext);
370-
};
438+
return requestDelegate;
371439
}
372440

373441
private static ILogger GetLogger(HttpContext httpContext)

src/Http/Http.Extensions/test/RouteDelegateBuilderTests.cs renamed to src/Http/Http.Extensions/test/RequestDelegateBuilderTests.cs

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Generic;
88
using System.Globalization;
99
using System.IO;
10+
using System.Reflection;
1011
using System.Text;
1112
using System.Text.Json;
1213
using System.Threading;
@@ -22,7 +23,7 @@
2223

2324
namespace Microsoft.AspNetCore.Routing.Internal
2425
{
25-
public class RouteDelegateBuilderTests
26+
public class RequestDelegateBuilderTests
2627
{
2728
public static IEnumerable<object[]> NoResult
2829
{
@@ -92,6 +93,98 @@ public async Task RequestDelegateInvokesAction(Delegate @delegate)
9293
Assert.True(httpContext.Items["invoked"] as bool?);
9394
}
9495

96+
private static void StaticTestActionBasicReflection(HttpContext httpContext)
97+
{
98+
httpContext.Items.Add("invoked", true);
99+
}
100+
101+
[Fact]
102+
public async Task StaticMethodInfoOverloadWorksWithBasicReflection()
103+
{
104+
var methodInfo = typeof(RequestDelegateBuilderTests).GetMethod(
105+
nameof(StaticTestActionBasicReflection),
106+
BindingFlags.NonPublic | BindingFlags.Static,
107+
new[] { typeof(HttpContext) });
108+
109+
var requestDelegate = RequestDelegateBuilder.BuildRequestDelegate(methodInfo!);
110+
111+
var httpContext = new DefaultHttpContext();
112+
113+
await requestDelegate(httpContext);
114+
115+
Assert.True(httpContext.Items["invoked"] as bool?);
116+
}
117+
118+
private class TestNonStaticActionClass
119+
{
120+
private readonly object _invokedValue;
121+
122+
public TestNonStaticActionClass(object invokedValue)
123+
{
124+
_invokedValue = invokedValue;
125+
}
126+
127+
private void NonStaticTestAction(HttpContext httpContext)
128+
{
129+
httpContext.Items.Add("invoked", _invokedValue);
130+
}
131+
}
132+
133+
[Fact]
134+
public async Task NonStaticMethodInfoOverloadWorksWithBasicReflection()
135+
{
136+
var methodInfo = typeof(TestNonStaticActionClass).GetMethod(
137+
"NonStaticTestAction",
138+
BindingFlags.NonPublic | BindingFlags.Instance,
139+
new[] { typeof(HttpContext) });
140+
141+
var invoked = false;
142+
143+
object GetTarget()
144+
{
145+
if (!invoked)
146+
{
147+
invoked = true;
148+
return new TestNonStaticActionClass(1);
149+
}
150+
151+
return new TestNonStaticActionClass(2);
152+
}
153+
154+
var requestDelegate = RequestDelegateBuilder.BuildRequestDelegate(methodInfo!, _ => GetTarget());
155+
156+
var httpContext = new DefaultHttpContext();
157+
158+
await requestDelegate(httpContext);
159+
160+
Assert.Equal(1, httpContext.Items["invoked"]);
161+
162+
httpContext = new DefaultHttpContext();
163+
164+
await requestDelegate(httpContext);
165+
166+
Assert.Equal(2, httpContext.Items["invoked"]);
167+
}
168+
169+
[Fact]
170+
public void BuildRequestDelegateThrowsArgumentNullExceptions()
171+
{
172+
var methodInfo = typeof(RequestDelegateBuilderTests).GetMethod(
173+
nameof(StaticTestActionBasicReflection),
174+
BindingFlags.NonPublic | BindingFlags.Static,
175+
new[] { typeof(HttpContext) });
176+
177+
var exNullAction = Assert.Throws<ArgumentNullException>(() => RequestDelegateBuilder.BuildRequestDelegate(action: null!));
178+
var exNullMethodInfo1 = Assert.Throws<ArgumentNullException>(() => RequestDelegateBuilder.BuildRequestDelegate(methodInfo: null!));
179+
var exNullMethodInfo2 = Assert.Throws<ArgumentNullException>(() => RequestDelegateBuilder.BuildRequestDelegate(methodInfo: null!, _ => 0));
180+
var exNullTargetFactory = Assert.Throws<ArgumentNullException>(() => RequestDelegateBuilder.BuildRequestDelegate(methodInfo!, targetFactory: null!));
181+
182+
Assert.Equal("action", exNullAction.ParamName);
183+
Assert.Equal("methodInfo", exNullMethodInfo1.ParamName);
184+
Assert.Equal("methodInfo", exNullMethodInfo2.ParamName);
185+
Assert.Equal("targetFactory", exNullTargetFactory.ParamName);
186+
}
187+
95188
public static IEnumerable<object[]> FromRouteResult
96189
{
97190
get

0 commit comments

Comments
 (0)