Skip to content

Commit

Permalink
Make RequestDelegateFactory public (was MapActionExpressionTreeBuilde…
Browse files Browse the repository at this point in the history
…r) (#31171)

* Make RequestDelegateBuilder public

- Formerly known as MapActionExpressionTreeBuilder

* Add new BuildRequestDelegate overloads

* Address PR feedback

* RequestDelegateBuilder -> RequestDelegateFactory

* Build -> Create

* Address final review feedback.
  • Loading branch information
halter73 committed Mar 30, 2021
1 parent 698d872 commit 64a94f0
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" />
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" Link="StreamCopyOperationInternal.cs" />
</ItemGroup>

Expand Down
4 changes: 4 additions & 0 deletions src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand All @@ -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!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,26 @@
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));
Expand All @@ -44,7 +46,85 @@ internal static class MapActionExpressionTreeBuilder
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

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

var method = action.Method;

var consumeBodyDirectly = false;
var consumeBodyAsForm = false;
Type? bodyType = null;
Expand All @@ -72,7 +150,7 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
// 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)
Expand Down Expand Up @@ -156,18 +234,17 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)

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>
{
Expand All @@ -177,22 +254,22 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)

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))
{
Expand Down Expand Up @@ -220,10 +297,10 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
}
}
}
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))
{
Expand Down Expand Up @@ -254,18 +331,18 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
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));
Expand Down Expand Up @@ -357,10 +434,7 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
requestDelegate = invoker;
}

return httpContext =>
{
return requestDelegate(action.Target, httpContext);
};
return requestDelegate;
}

private static ILogger GetLogger(HttpContext httpContext)
Expand Down
Loading

0 comments on commit 64a94f0

Please sign in to comment.