// 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 Convert(object taskAsObject) { var task = (Task)taskAsObject; return CastToObject(task); } public static async Task ExecuteAsync(MethodInfo actionMethodInfo, object instance, IDictionary 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. // This is necessary to enable calling await on the returned task. // i.e we need to write the following var result = await (Task)mInfo.Invoke. // Returning Task enables us to await on the result. private static async Task CoerceResultToTaskAsync(object result, Type returnType, string methodName, Type declaringType) { // If it is either a Task or Task // must coerce the return value to Task 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 Action() // constructs: return (Task)Convert((Task)result) var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType); var convertedResult = (Task)genericMethodInfo.Invoke(null, new object[] { result }); return await convertedResult; } // This will be the case for: // 1. Types which have derived from Task and Task, // 2. Action methods which use dynamic keyword but return a Task or Task. throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(methodName, declaringType)); } else { return result; } } private static object[] PrepareArguments(IDictionary 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 or Task> // 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 )); } } } /// /// Cast Task to Task of object /// private static async Task CastToObject(Task task) { await task; return null; } /// /// Cast Task of T to Task of object /// private static async Task CastToObject(Task task) { return (object)await task; } } }