aspnetcore/src/Microsoft.AspNet.Mvc.Core/ReflectedActionExecutor.cs

151 lines
6.4 KiB
C#

// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public static class ReflectedActionExecutor
{
private static readonly MethodInfo _convertOfTMethod = typeof(ReflectedActionExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == "Convert");
// Method called via reflection.
private static Task<object> Convert<T>(object taskAsObject)
{
var task = (Task<T>)taskAsObject;
return CastToObject<T>(task);
}
public static async Task<object> ExecuteAsync(MethodInfo actionMethodInfo, object instance, IDictionary<string, object> actionArguments)
{
var methodArguments = PrepareArguments(actionArguments, actionMethodInfo.GetParameters());
object invocationResult = null;
try
{
invocationResult = actionMethodInfo.Invoke(instance, methodArguments);
}
catch (TargetInvocationException targetInvocationException)
{
// Capturing the actual exception and the original callstack and rethrow for external exception handlers to observe.
ExceptionDispatchInfo exceptionDispatchInfo = ExceptionDispatchInfo.Capture(targetInvocationException.InnerException);
exceptionDispatchInfo.Throw();
}
return await CoerceResultToTaskAsync(invocationResult, actionMethodInfo.ReturnType, actionMethodInfo.Name, actionMethodInfo.DeclaringType);
}
// We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task<T>.
// This is necessary to enable calling await on the returned task.
// i.e we need to write the following var result = await (Task<ActualType>)mInfo.Invoke.
// Returning Task<object> enables us to await on the result.
private static async Task<object> CoerceResultToTaskAsync(object result, Type returnType, string methodName, Type declaringType)
{
// If it is either a Task or Task<T>
// must coerce the return value to Task<object>
var resultAsTask = result as Task;
if (resultAsTask != null)
{
if (returnType == typeof(Task))
{
ThrowIfWrappedTaskInstance(resultAsTask.GetType(), methodName, declaringType);
return await CastToObject(resultAsTask);
}
Type taskValueType = TypeHelper.GetTaskInnerTypeOrNull(returnType);
if (taskValueType != null)
{
// for: public Task<T> Action()
// constructs: return (Task<object>)Convert<T>((Task<T>)result)
var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType);
var convertedResult = (Task<object>)genericMethodInfo.Invoke(null, new object[] { result });
return await convertedResult;
}
// This will be the case for:
// 1. Types which have derived from Task and Task<T>,
// 2. Action methods which use dynamic keyword but return a Task or Task<T>.
throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(methodName, declaringType));
}
else
{
return result;
}
}
private static object[] PrepareArguments(IDictionary<string, object> actionParameters, ParameterInfo[] declaredParameterInfos)
{
int count = declaredParameterInfos.Length;
if (count == 0)
{
return null;
}
var arguments = new object[count];
for (int index = 0; index < count; index++)
{
var parameterInfo = declaredParameterInfos[index];
object value;
if (!actionParameters.TryGetValue(parameterInfo.Name, out value))
{
if (parameterInfo.HasDefaultValue)
{
value = parameterInfo.DefaultValue;
}
else
{
value = parameterInfo.ParameterType.IsValueType()
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
}
arguments[index] = value;
}
return arguments;
}
private static void ThrowIfWrappedTaskInstance(Type actualTypeReturned, string methodName, Type declaringType)
{
// Throw if a method declares a return type of Task and returns an instance of Task<Task> or Task<Task<T>>
// This most likely indicates that the developer forgot to call Unwrap() somewhere.
if (actualTypeReturned != typeof(Task))
{
Type innerTaskType = TypeHelper.GetTaskInnerTypeOrNull(actualTypeReturned);
if (innerTaskType != null && typeof(Task).IsAssignableFrom(innerTaskType))
{
throw new InvalidOperationException(
Resources.FormatActionExecutor_WrappedTaskInstance(
methodName,
declaringType,
actualTypeReturned.FullName
));
}
}
}
/// <summary>
/// Cast Task to Task of object
/// </summary>
private static async Task<object> CastToObject(Task task)
{
await task;
return null;
}
/// <summary>
/// Cast Task of T to Task of object
/// </summary>
private static async Task<object> CastToObject<T>(Task<T> task)
{
return (object)await task;
}
}
}