ActionExecutor implementation WebFx W113
The changes include: 1. Action executor changes required for supporting sync and async operations Taksk and Task 2. Adding test project for MVC core - This contains ActionExecutor Tests. 3. Also adding a resources file for MVC core project
This commit is contained in:
parent
4bc7c36522
commit
9cd99a42a7
10
WebFx.sln
10
WebFx.sln
|
|
@ -61,6 +61,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{49EBEEDD
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.ModelBinding.Test.k10", "test\Microsoft.AspNet.Mvc.ModelBinding.Test\Microsoft.AspNet.Mvc.ModelBinding.Test.k10.csproj", "{5A219830-3C19-475D-901F-E580BA87DFF8}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.ModelBinding.Test.k10", "test\Microsoft.AspNet.Mvc.ModelBinding.Test\Microsoft.AspNet.Mvc.ModelBinding.Test.k10.csproj", "{5A219830-3C19-475D-901F-E580BA87DFF8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Mvc.Core.Test.net45", "test\Microsoft.AspNet.Mvc.Core.Test\Microsoft.AspNet.Mvc.Core.Test.net45.csproj", "{998C5A2E-D043-465F-BE19-076D27444289}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -151,6 +153,10 @@ Global
|
||||||
{5A219830-3C19-475D-901F-E580BA87DFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5A219830-3C19-475D-901F-E580BA87DFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5A219830-3C19-475D-901F-E580BA87DFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5A219830-3C19-475D-901F-E580BA87DFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5A219830-3C19-475D-901F-E580BA87DFF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5A219830-3C19-475D-901F-E580BA87DFF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{998C5A2E-D043-465F-BE19-076D27444289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{998C5A2E-D043-465F-BE19-076D27444289}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{998C5A2E-D043-465F-BE19-076D27444289}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{998C5A2E-D043-465F-BE19-076D27444289}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -182,5 +188,9 @@ Global
|
||||||
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938} = {49EBEEDD-E117-4B91-B4BA-56FB80AF4F3C}
|
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938} = {49EBEEDD-E117-4B91-B4BA-56FB80AF4F3C}
|
||||||
{68FC3791-A9E4-4EDE-93A5-C7AC7DC0ED6E} = {49EBEEDD-E117-4B91-B4BA-56FB80AF4F3C}
|
{68FC3791-A9E4-4EDE-93A5-C7AC7DC0ED6E} = {49EBEEDD-E117-4B91-B4BA-56FB80AF4F3C}
|
||||||
{75A07B53-C5EE-4995-A55B-27562C23BCCD} = {49EBEEDD-E117-4B91-B4BA-56FB80AF4F3C}
|
{75A07B53-C5EE-4995-A55B-27562C23BCCD} = {49EBEEDD-E117-4B91-B4BA-56FB80AF4F3C}
|
||||||
|
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
|
{75A07B53-C5EE-4995-A55B-27562C23BCCD} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
|
{68FC3791-A9E4-4EDE-93A5-C7AC7DC0ED6E} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
|
{998C5A2E-D043-465F-BE19-076D27444289} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace MvcSample.Web.Filters
|
||||||
{
|
{
|
||||||
object age = null;
|
object age = null;
|
||||||
|
|
||||||
if (context.ActionParameters.TryGetValue("age", out age))
|
if (context.ActionArguments.TryGetValue("age", out age))
|
||||||
{
|
{
|
||||||
if (age is int)
|
if (age is int)
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +25,7 @@ namespace MvcSample.Web.Filters
|
||||||
intAge = 29;
|
intAge = 29;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ActionParameters["age"] = intAge;
|
context.ActionArguments["age"] = intAge;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ namespace MvcSample.Web.Filters
|
||||||
{
|
{
|
||||||
object originalUserName = null;
|
object originalUserName = null;
|
||||||
|
|
||||||
context.ActionParameters.TryGetValue("userName", out originalUserName);
|
context.ActionArguments.TryGetValue("userName", out originalUserName);
|
||||||
|
|
||||||
var userName = originalUserName as string;
|
var userName = originalUserName as string;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(userName))
|
if (string.IsNullOrWhiteSpace(userName))
|
||||||
{
|
{
|
||||||
context.ActionParameters["userName"] = _userNames[(_index++)%3];
|
context.ActionArguments["userName"] = _userNames[(_index++)%3];
|
||||||
}
|
}
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public class ActionFilterContext
|
public class ActionFilterContext
|
||||||
{
|
{
|
||||||
public ActionFilterContext(ActionContext actionContext,
|
public ActionFilterContext(ActionContext actionContext,
|
||||||
IDictionary<string, object> actionParameters,
|
IDictionary<string, object> actionArguments)
|
||||||
Type methodReturnType)
|
|
||||||
{
|
{
|
||||||
ActionContext = actionContext;
|
ActionContext = actionContext;
|
||||||
ActionParameters = actionParameters;
|
ActionArguments = actionArguments;
|
||||||
MethodReturnType = methodReturnType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IDictionary<string, object> ActionParameters { get; private set; }
|
public virtual IDictionary<string, object> ActionArguments { get; private set; }
|
||||||
|
|
||||||
public virtual ActionContext ActionContext { get; private set; }
|
public virtual ActionContext ActionContext { get; private set; }
|
||||||
|
|
||||||
public virtual Type MethodReturnType { get; private set; }
|
|
||||||
|
|
||||||
public virtual IActionResult Result { get; set; }
|
public virtual IActionResult Result { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,40 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Filters
|
namespace Microsoft.AspNet.Mvc.Filters
|
||||||
{
|
{
|
||||||
// This one lives in the Filters namespace, and only intended to be consumed by folks that rewrite the action invoker.
|
// This one lives in the Filters namespace, and only intended to be consumed by folks that rewrite the action invoker.
|
||||||
public class ReflectedActionFilterEndPoint : IActionFilter
|
public class ReflectedActionFilterEndPoint : IActionFilter
|
||||||
{
|
{
|
||||||
private readonly Func<object[], Task<object>> _coreMethodInvoker;
|
|
||||||
private readonly IActionResultFactory _actionResultFactory;
|
private readonly IActionResultFactory _actionResultFactory;
|
||||||
|
private readonly object _controllerInstance;
|
||||||
|
|
||||||
public ReflectedActionFilterEndPoint(Func<object[], Task<object>> coreMethodInvoker,
|
public ReflectedActionFilterEndPoint(IActionResultFactory actionResultFactory, object controllerInstance)
|
||||||
IActionResultFactory actionResultFactory)
|
|
||||||
{
|
{
|
||||||
_coreMethodInvoker = coreMethodInvoker;
|
|
||||||
_actionResultFactory = actionResultFactory;
|
_actionResultFactory = actionResultFactory;
|
||||||
|
_controllerInstance = controllerInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Invoke(ActionFilterContext context, Func<Task> next)
|
public async Task Invoke(ActionFilterContext context, Func<Task> next)
|
||||||
{
|
{
|
||||||
// TODO: match the parameter names here.
|
var reflectedActionDescriptor = context.ActionContext.ActionDescriptor as ReflectedActionDescriptor;
|
||||||
var tempArray = context.ActionParameters.Values.ToArray(); // seriously broken for now, need to organize names to match.
|
if (reflectedActionDescriptor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resources.ReflectedActionFilterEndPoint_UnexpectedActionDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
var actionReturnValue = await _coreMethodInvoker(tempArray);
|
var actionMethodInfo = reflectedActionDescriptor.MethodInfo;
|
||||||
|
var actionReturnValue = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
actionMethodInfo,
|
||||||
|
_controllerInstance,
|
||||||
|
context.ActionArguments);
|
||||||
|
|
||||||
context.Result = _actionResultFactory.CreateActionResult(context.MethodReturnType,
|
var underlyingReturnType = TypeHelper.GetTaskInnerTypeOrNull(actionMethodInfo.ReturnType) ?? actionMethodInfo.ReturnType;
|
||||||
actionReturnValue,
|
context.Result = _actionResultFactory.CreateActionResult(
|
||||||
context.ActionContext);
|
underlyingReturnType,
|
||||||
|
actionReturnValue,
|
||||||
|
context.ActionContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
internal static class TypeHelper
|
||||||
|
{
|
||||||
|
private static readonly Type TaskGenericType = typeof(Task<>);
|
||||||
|
|
||||||
|
public static Type GetTaskInnerTypeOrNull([NotNull]Type type)
|
||||||
|
{
|
||||||
|
if (type.GetTypeInfo().IsGenericType && !type.GetTypeInfo().IsGenericTypeDefinition)
|
||||||
|
{
|
||||||
|
var genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||||
|
var genericArguments = type.GetGenericArguments();
|
||||||
|
if (genericArguments.Length == 1 && TaskGenericType == genericTypeDefinition)
|
||||||
|
{
|
||||||
|
// Only Return if there is a single argument.
|
||||||
|
return genericArguments[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
// <auto-generated />
|
||||||
|
namespace Microsoft.AspNet.Mvc.Core
|
||||||
|
{
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
|
||||||
|
internal static class Resources
|
||||||
|
{
|
||||||
|
private static readonly ResourceManager _resourceManager
|
||||||
|
= new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ActionExecutor_WrappedTaskInstance
|
||||||
|
{
|
||||||
|
get { return GetString("ActionExecutor_WrappedTaskInstance"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatActionExecutor_WrappedTaskInstance(object p0, object p1, object p2)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_WrappedTaskInstance"), p0, p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ActionExecutor_UnexpectedTaskInstance
|
||||||
|
{
|
||||||
|
get { return GetString("ActionExecutor_UnexpectedTaskInstance"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatActionExecutor_UnexpectedTaskInstance(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ReflectedActionFilterEndPoint_UnexpectedActionDescriptor
|
||||||
|
{
|
||||||
|
get { return GetString("ReflectedActionFilterEndPoint_UnexpectedActionDescriptor"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatReflectedActionFilterEndPoint_UnexpectedActionDescriptor()
|
||||||
|
{
|
||||||
|
return GetString("ReflectedActionFilterEndPoint_UnexpectedActionDescriptor");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
|
{
|
||||||
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.Assert(value != null);
|
||||||
|
|
||||||
|
if (formatterNames != null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < formatterNames.Length; i++)
|
||||||
|
{
|
||||||
|
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -100,15 +100,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var parameterValues = await GetParameterValues(modelState);
|
var parameterValues = await GetParameterValues(modelState);
|
||||||
|
|
||||||
var actionFilterContext = new ActionFilterContext(_actionContext,
|
var actionFilterContext = new ActionFilterContext(_actionContext,
|
||||||
parameterValues,
|
parameterValues);
|
||||||
method.ReturnType);
|
|
||||||
|
|
||||||
// TODO: This is extremely temporary and is going to get soon replaced with the action executer
|
var actionEndPoint = new ReflectedActionFilterEndPoint(_actionResultFactory, controller);
|
||||||
var actionEndPoint = new ReflectedActionFilterEndPoint(async (inArray) => method.Invoke(controller, inArray),
|
|
||||||
_actionResultFactory);
|
|
||||||
|
|
||||||
_actionFilters.Add(actionEndPoint);
|
_actionFilters.Add(actionEndPoint);
|
||||||
|
|
||||||
var actionFilterPipeline = new FilterPipelineBuilder<ActionFilterContext>(_actionFilters,
|
var actionFilterPipeline = new FilterPipelineBuilder<ActionFilterContext>(_actionFilters,
|
||||||
actionFilterContext);
|
actionFilterContext);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="ActionExecutor_WrappedTaskInstance" xml:space="preserve">
|
||||||
|
<value>The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionExecutor_UnexpectedTaskInstance" xml:space="preserve">
|
||||||
|
<value>The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ReflectedActionFilterEndPoint_UnexpectedActionDescriptor" xml:space="preserve">
|
||||||
|
<value>The class ReflectedActionFilterEndPoint only supports ReflectedActionDescriptors.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
|
|
@ -22,7 +22,10 @@
|
||||||
"System.IO": "4.0.0.0",
|
"System.IO": "4.0.0.0",
|
||||||
"System.Linq": "4.0.0.0",
|
"System.Linq": "4.0.0.0",
|
||||||
"System.Reflection": "4.0.10.0",
|
"System.Reflection": "4.0.10.0",
|
||||||
|
"System.Reflection.Emit.ILGeneration": "4.0.0.0",
|
||||||
|
"System.Reflection.Emit.Lightweight": "4.0.0.0",
|
||||||
"System.Reflection.Extensions": "4.0.0.0",
|
"System.Reflection.Extensions": "4.0.0.0",
|
||||||
|
"System.Resources.ResourceManager": "4.0.0.0",
|
||||||
"System.Runtime": "4.0.20.0",
|
"System.Runtime": "4.0.20.0",
|
||||||
"System.Runtime.Extensions": "4.0.10.0",
|
"System.Runtime.Extensions": "4.0.10.0",
|
||||||
"System.Runtime.Hosting": "3.9.0.0",
|
"System.Runtime.Hosting": "3.9.0.0",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,314 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
|
{
|
||||||
|
public class ActionExecutorTests
|
||||||
|
{
|
||||||
|
private TestController _controller = new TestController();
|
||||||
|
|
||||||
|
private delegate void MethodWithVoidReturnType();
|
||||||
|
|
||||||
|
private delegate string SyncMethod(string s);
|
||||||
|
|
||||||
|
private delegate Task MethodWithTaskReturnType(int i, string s);
|
||||||
|
|
||||||
|
private delegate Task<int> MethodWithTaskOfIntReturnType(int i, string s);
|
||||||
|
|
||||||
|
private delegate Task<Task<int>> MethodWithTaskOfTaskOfIntReturnType(int i, string s);
|
||||||
|
|
||||||
|
public delegate TestController.TaskDerivedType MethodWithCustomTaskReturnType(int i, string s);
|
||||||
|
|
||||||
|
private delegate TestController.TaskOfTDerivedType<int> MethodWithCustomTaskOfTReturnType(int i, string s);
|
||||||
|
|
||||||
|
private delegate dynamic ReturnTaskAsDynamicValue(int i, string s);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithVoidReturnType()
|
||||||
|
{
|
||||||
|
var methodWithVoidReturnType = new MethodWithVoidReturnType(TestController.VoidAction);
|
||||||
|
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithVoidReturnType.GetMethodInfo(),
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
Assert.Same(null, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_TaskReturnType()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithTaskReturnType = new MethodWithTaskReturnType(_controller.TaskAction);
|
||||||
|
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters);
|
||||||
|
Assert.Same(null, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_TaskOfValueReturnType()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||||
|
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters);
|
||||||
|
Assert.Equal(inputParam1, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_TaskOfTaskOfValueReturnType()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithTaskOfTaskOfIntReturnType = new MethodWithTaskOfTaskOfIntReturnType(_controller.TaskOfTaskAction);
|
||||||
|
var result = await (Task<int>)(await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters));
|
||||||
|
Assert.Equal(inputParam1, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithAsyncKeywordThrows()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionWithException);
|
||||||
|
await AssertThrowsAsync<NotImplementedException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
"Not Implemented Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithoutAsyncThrows()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionWithExceptionWithoutAsync);
|
||||||
|
await AssertThrowsAsync<NotImplementedException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
"Not Implemented Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithExceptionsAfterAwait()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionThrowAfterAwait);
|
||||||
|
await AssertThrowsAsync<ArgumentException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
"Argument Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SyncAction()
|
||||||
|
{
|
||||||
|
string inputString = "hello";
|
||||||
|
var syncMethod = new SyncMethod(_controller.Echo);
|
||||||
|
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
syncMethod.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
new Dictionary<string, object>() { { "input", inputString } });
|
||||||
|
Assert.Equal(inputString, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SyncAction_WithException()
|
||||||
|
{
|
||||||
|
string inputString = "hello";
|
||||||
|
var syncMethod = new SyncMethod(_controller.EchoWithException);
|
||||||
|
var expectedException = "The method or operation is not implemented.";
|
||||||
|
await AssertThrowsAsync<NotImplementedException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
syncMethod.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
new Dictionary<string, object>() { { "input", inputString } }),
|
||||||
|
expectedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithCustomTaskReturnTypeThrows()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
// If it is an unrecognized derived type we throw an InvalidOperationException.
|
||||||
|
var methodWithCutomTaskReturnType = new MethodWithCustomTaskReturnType(_controller.TaskActionWithCustomTaskReturnType);
|
||||||
|
|
||||||
|
string expectedException = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
"The method 'TaskActionWithCustomTaskReturnType' on type '{0}' returned a Task instance even though it is not an asynchronous method.",
|
||||||
|
typeof(TestController));
|
||||||
|
await AssertThrowsAsync<InvalidOperationException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithCutomTaskReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
expectedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithCustomTaskOfTReturnTypeThrows()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithCutomTaskOfTReturnType = new MethodWithCustomTaskOfTReturnType(_controller.TaskActionWithCustomTaskOfTReturnType);
|
||||||
|
string expectedException = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
"The method 'TaskActionWithCustomTaskOfTReturnType' on type '{0}' returned a Task instance even though it is not an asynchronous method.",
|
||||||
|
typeof(TestController));
|
||||||
|
|
||||||
|
await AssertThrowsAsync<InvalidOperationException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithCutomTaskOfTReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
expectedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_ReturningUnwrappedTaskThrows()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var methodWithUnwrappedTask = new MethodWithTaskReturnType(_controller.UnwrappedTask);
|
||||||
|
await AssertThrowsAsync<InvalidOperationException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithUnwrappedTask.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
string.Format(CultureInfo.CurrentCulture,
|
||||||
|
"The method 'UnwrappedTask' on type '{0}' returned an instance of '{1}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.",
|
||||||
|
typeof(TestController),
|
||||||
|
typeof(Task<Task>).FullName
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncAction_WithDynamicReturnTypeThrows()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||||
|
|
||||||
|
var dynamicTaskMethod = new ReturnTaskAsDynamicValue(_controller.ReturnTaskAsDynamicValue);
|
||||||
|
string expectedException = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
"The method 'ReturnTaskAsDynamicValue' on type '{0}' returned a Task instance even though it is not an asynchronous method.",
|
||||||
|
typeof(TestController));
|
||||||
|
await AssertThrowsAsync<InvalidOperationException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
dynamicTaskMethod.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
expectedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParametersInRandomOrder()
|
||||||
|
{
|
||||||
|
int inputParam1 = 1;
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
|
||||||
|
// Note that the order of parameters is reversed
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "s", inputParam2 }, { "i", inputParam1 } };
|
||||||
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||||
|
|
||||||
|
var result = await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters);
|
||||||
|
Assert.Equal(inputParam1, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task InvalidParameterValueThrows()
|
||||||
|
{
|
||||||
|
string inputParam2 = "Second Parameter";
|
||||||
|
|
||||||
|
var actionParameters = new Dictionary<string, object> { { "i", "Some Invalid Value" }, { "s", inputParam2 } };
|
||||||
|
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||||
|
var expectedException = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
"Object of type '{0}' cannot be converted to type '{1}'.",
|
||||||
|
typeof (string),
|
||||||
|
typeof (int));
|
||||||
|
|
||||||
|
// If it is an unrecognized derived type we throw an InvalidOperationException.
|
||||||
|
await AssertThrowsAsync<ArgumentException>(
|
||||||
|
async () =>
|
||||||
|
await ReflectedActionExecutor.ExecuteAsync(
|
||||||
|
methodWithTaskOfIntReturnType.GetMethodInfo(),
|
||||||
|
_controller,
|
||||||
|
actionParameters),
|
||||||
|
expectedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: XUnit Assert.Throw is not async-aware. Check if the latest version supports it.
|
||||||
|
private static async Task AssertThrowsAsync<TException>(Func<Task<object>> func, string expectedExceptionMessage = "")
|
||||||
|
{
|
||||||
|
var expected = typeof(TException);
|
||||||
|
Type actual = null;
|
||||||
|
string actualExceptionMessage = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await func();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
actual = e.GetType();
|
||||||
|
actualExceptionMessage = e.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
|
||||||
|
Assert.Equal(expectedExceptionMessage, actualExceptionMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
|
{
|
||||||
|
public class TestController
|
||||||
|
{
|
||||||
|
public static void VoidAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TaskAction(int i, string s)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> TaskValueTypeAction(int i, string s)
|
||||||
|
{
|
||||||
|
Console.WriteLine(s);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Task<int>> TaskOfTaskAction(int i, string s)
|
||||||
|
{
|
||||||
|
return TaskValueTypeAction(i, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> TaskValueTypeActionWithoutAsync(int i, string s)
|
||||||
|
{
|
||||||
|
return TaskValueTypeAction(i, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> TaskActionWithException(int i, string s)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Not Implemented Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> TaskActionWithExceptionWithoutAsync(int i, string s)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Not Implemented Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> TaskActionThrowAfterAwait(int i, string s)
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
throw new ArgumentException("Argument Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskDerivedType TaskActionWithCustomTaskReturnType(int i, string s)
|
||||||
|
{
|
||||||
|
Console.WriteLine(s);
|
||||||
|
return new TaskDerivedType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskOfTDerivedType<int> TaskActionWithCustomTaskOfTReturnType(int i, string s)
|
||||||
|
{
|
||||||
|
Console.WriteLine(s);
|
||||||
|
return new TaskOfTDerivedType<int>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a Task<Task> instead of a Task. This should throw an InvalidOperationException.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task UnwrappedTask(int i, string s)
|
||||||
|
{
|
||||||
|
return Task.Factory.StartNew(async () => await Task.Delay(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Echo(string input)
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EchoWithException(string input)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public dynamic ReturnTaskAsDynamicValue(int i, string s)
|
||||||
|
{
|
||||||
|
return Task.Factory.StartNew(() => i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TaskDerivedType : Task
|
||||||
|
{
|
||||||
|
public TaskDerivedType()
|
||||||
|
: base(() => Console.WriteLine("In The Constructor"))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TaskOfTDerivedType<T> : Task<T>
|
||||||
|
{
|
||||||
|
public TaskOfTDerivedType(T input)
|
||||||
|
: base(() => input)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"version" : "0.1-alpha-*",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Mvc.Core" : "",
|
||||||
|
"Microsoft.AspNet.Mvc" : "",
|
||||||
|
"Moq": "4.0.10827",
|
||||||
|
"Xunit.KRunner": "0.1-alpha-*",
|
||||||
|
"xunit.abstractions": "2.0.0-aspnet-*",
|
||||||
|
"xunit.assert": "2.0.0-aspnet-*",
|
||||||
|
"xunit.core": "2.0.0-aspnet-*",
|
||||||
|
"xunit.execution": "2.0.0-aspnet-*"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"test": "Xunit.KRunner"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"net45": { }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue