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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.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",
|
||||
|
|
|
|||
|
|
@ -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