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:
harshgMSFT 2014-03-06 18:35:38 -08:00
parent 4bc7c36522
commit 9cd99a42a7
14 changed files with 866 additions and 29 deletions

View File

@ -61,6 +61,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{49EBEEDD
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}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -182,5 +188,9 @@ Global
{3EB2CFF9-6E67-4C03-9AC4-2DD169024938} = {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}
{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
EndGlobal

View File

@ -10,7 +10,7 @@ namespace MvcSample.Web.Filters
{
object age = null;
if (context.ActionParameters.TryGetValue("age", out age))
if (context.ActionArguments.TryGetValue("age", out age))
{
if (age is int)
{
@ -25,7 +25,7 @@ namespace MvcSample.Web.Filters
intAge = 29;
}
context.ActionParameters["age"] = intAge;
context.ActionArguments["age"] = intAge;
}
}

View File

@ -13,13 +13,13 @@ namespace MvcSample.Web.Filters
{
object originalUserName = null;
context.ActionParameters.TryGetValue("userName", out originalUserName);
context.ActionArguments.TryGetValue("userName", out originalUserName);
var userName = originalUserName as string;
if (string.IsNullOrWhiteSpace(userName))
{
context.ActionParameters["userName"] = _userNames[(_index++)%3];
context.ActionArguments["userName"] = _userNames[(_index++)%3];
}
await next();

View File

@ -6,20 +6,16 @@ namespace Microsoft.AspNet.Mvc
public class ActionFilterContext
{
public ActionFilterContext(ActionContext actionContext,
IDictionary<string, object> actionParameters,
Type methodReturnType)
IDictionary<string, object> actionArguments)
{
ActionContext = actionContext;
ActionParameters = actionParameters;
MethodReturnType = methodReturnType;
ActionArguments = actionArguments;
}
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 Type MethodReturnType { get; private set; }
public virtual IActionResult Result { get; set; }
}
}

View File

@ -1,32 +1,40 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
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.
public class ReflectedActionFilterEndPoint : IActionFilter
{
private readonly Func<object[], Task<object>> _coreMethodInvoker;
private readonly IActionResultFactory _actionResultFactory;
private readonly object _controllerInstance;
public ReflectedActionFilterEndPoint(Func<object[], Task<object>> coreMethodInvoker,
IActionResultFactory actionResultFactory)
public ReflectedActionFilterEndPoint(IActionResultFactory actionResultFactory, object controllerInstance)
{
_coreMethodInvoker = coreMethodInvoker;
_actionResultFactory = actionResultFactory;
_controllerInstance = controllerInstance;
}
public async Task Invoke(ActionFilterContext context, Func<Task> next)
{
// TODO: match the parameter names here.
var tempArray = context.ActionParameters.Values.ToArray(); // seriously broken for now, need to organize names to match.
var reflectedActionDescriptor = context.ActionContext.ActionDescriptor as ReflectedActionDescriptor;
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,
actionReturnValue,
context.ActionContext);
var underlyingReturnType = TypeHelper.GetTaskInnerTypeOrNull(actionMethodInfo.ReturnType) ?? actionMethodInfo.ReturnType;
context.Result = _actionResultFactory.CreateActionResult(
underlyingReturnType,
actionReturnValue,
context.ActionContext);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -100,15 +100,11 @@ namespace Microsoft.AspNet.Mvc
var parameterValues = await GetParameterValues(modelState);
var actionFilterContext = new ActionFilterContext(_actionContext,
parameterValues,
method.ReturnType);
parameterValues);
// TODO: This is extremely temporary and is going to get soon replaced with the action executer
var actionEndPoint = new ReflectedActionFilterEndPoint(async (inArray) => method.Invoke(controller, inArray),
_actionResultFactory);
var actionEndPoint = new ReflectedActionFilterEndPoint(_actionResultFactory, controller);
_actionFilters.Add(actionEndPoint);
var actionFilterPipeline = new FilterPipelineBuilder<ActionFilterContext>(_actionFilters,
actionFilterContext);

View File

@ -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>

View File

@ -22,7 +22,10 @@
"System.IO": "4.0.0.0",
"System.Linq": "4.0.0.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.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.Hosting": "3.9.0.0",

View File

@ -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);
}
}
}

View File

@ -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)
{
}
}
}
}

View File

@ -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": { }
}
}