aspnetcore/src/Microsoft.AspNet.Mvc.Core/Controllers/ControllerActionExecutor.cs

194 lines
7.5 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNet.Mvc.Controllers
{
public static class ControllerActionExecutor
{
private static readonly MethodInfo _convertOfTMethod =
typeof(ControllerActionExecutor).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 Task<object> ExecuteAsync(
MethodInfo actionMethodInfo,
object instance,
IDictionary<string, object> actionArguments)
{
var orderedArguments = PrepareArguments(actionArguments, actionMethodInfo.GetParameters());
return ExecuteAsync(actionMethodInfo, instance, orderedArguments);
}
public static Task<object> ExecuteAsync(
MethodInfo actionMethodInfo,
object instance,
object[] orderedActionArguments)
{
object invocationResult = null;
try
{
invocationResult = actionMethodInfo.Invoke(instance, orderedActionArguments);
}
catch (TargetInvocationException targetInvocationException)
{
// Capturing the exception and the original callstack and rethrow for external exception handlers.
var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(targetInvocationException.InnerException);
exceptionDispatchInfo.Throw();
}
return 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.
// This method is intentionally not using async pattern to keep jit time (on cold start) to a minimum.
private static 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 CastToObject(resultAsTask);
}
var taskValueType = 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 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 Task.FromResult(result);
}
}
private static object[] PrepareArguments(
IDictionary<string, object> actionParameters,
ParameterInfo[] declaredParameterInfos)
{
var count = declaredParameterInfos.Length;
if (count == 0)
{
return null;
}
var arguments = new object[count];
for (var 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
{
var defaultValueAttribute =
parameterInfo.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute?.Value == null)
{
value = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
value = defaultValueAttribute.Value;
}
}
}
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))
{
var innerTaskType = 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;
}
private static Type GetTaskInnerTypeOrNull(Type type)
{
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(Task<>));
return genericType?.GenericTypeArguments[0];
}
}
}