Skip to content

Commit

Permalink
Remove serialization of Task<string> in MapAction (#30369)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kahbazi authored Feb 23, 2021
1 parent 465a219 commit d889357
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 7 deletions.
76 changes: 69 additions & 7 deletions src/Http/Routing/src/Internal/MapActionExpressionTreeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ internal static class MapActionExpressionTreeBuilder
{
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(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 GetRequiredServiceMethodInfo = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
Expand Down Expand Up @@ -195,10 +197,20 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
else
{
// ExecuteTask<T>(action(..), httpContext);
body = Expression.Call(
ExecuteTaskOfTMethodInfo.MakeGenericMethod(typeArg),
methodCall,
HttpContextParameter);
if (typeArg == typeof(string))
{
body = Expression.Call(
ExecuteTaskOfStringMethodInfo,
methodCall,
HttpContextParameter);
}
else
{
body = Expression.Call(
ExecuteTaskOfTMethodInfo.MakeGenericMethod(typeArg),
methodCall,
HttpContextParameter);
}
}
}
else if (method.ReturnType.IsGenericType &&
Expand All @@ -216,10 +228,20 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
else
{
// ExecuteTask<T>(action(..), httpContext);
body = Expression.Call(
if (typeArg == typeof(string))
{
body = Expression.Call(
ExecuteValueTaskOfStringMethodInfo,
methodCall,
HttpContextParameter);
}
else
{
body = Expression.Call(
ExecuteValueTaskOfTMethodInfo.MakeGenericMethod(typeArg),
methodCall,
HttpContextParameter);
}
}
}
else
Expand Down Expand Up @@ -392,9 +414,34 @@ private static MemberInfo GetMemberInfo<T>(Expression<T> expr)
return mc.Member;
}

private static async Task ExecuteTask<T>(Task<T> task, HttpContext httpContext)
private static Task ExecuteTask<T>(Task<T> task, HttpContext httpContext)
{
static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext)
{
await httpContext.Response.WriteAsJsonAsync(await task);
}

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

return ExecuteAwaited(task, httpContext);
}

private static Task ExecuteTaskOfString(Task<string> task, HttpContext httpContext)
{
await httpContext.Response.WriteAsJsonAsync(await 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 ExecuteValueTask<T>(ValueTask<T> task, HttpContext httpContext)
Expand All @@ -412,6 +459,21 @@ static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext)
return ExecuteAwaited(task, httpContext);
}

private static Task ExecuteValueTaskOfString(ValueTask<string> task, HttpContext 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,49 @@ public async Task RequestDelegateUsesCustomIResult(Delegate @delegate)
Assert.Equal("Still not enough tests!", decodedResponseBody);
}

public static IEnumerable<object[]> StringResult
{
get
{
var test = "String Test";

string TestAction() => test;
Task<string> TaskTestAction() => Task.FromResult(test);
ValueTask<string> ValueTaskTestAction() => ValueTask.FromResult(test);

static string StaticTestAction() => "String Test";
static Task<string> StaticTaskTestAction() => Task.FromResult("String Test");
static ValueTask<string> StaticValueTaskTestAction() => ValueTask.FromResult("String Test");

return new List<object[]>
{
new object[] { (Func<string>)TestAction },
new object[] { (Func<Task<string>>)TaskTestAction },
new object[] { (Func<ValueTask<string>>)ValueTaskTestAction },
new object[] { (Func<string>)StaticTestAction },
new object[] { (Func<Task<string>>)StaticTaskTestAction },
new object[] { (Func<ValueTask<string>>)StaticValueTaskTestAction },
};
}
}

[Theory]
[MemberData(nameof(StringResult))]
public async Task RequestDelegateWritesStringReturnValueAsJsonResponseBody(Delegate @delegate)
{
var httpContext = new DefaultHttpContext();
var responseBodyStream = new MemoryStream();
httpContext.Response.Body = responseBodyStream;

var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate(@delegate);

await requestDelegate(httpContext);

var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());

Assert.Equal("String Test", responseBody);
}

private class Todo
{
public int Id { get; set; }
Expand Down

0 comments on commit d889357

Please sign in to comment.