React to new ObjectMethodExecutor in Common

This commit is contained in:
Steve Sanderson 2017-04-20 17:49:05 +01:00
parent 720a47765d
commit 6f3a295041
14 changed files with 728 additions and 522 deletions

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.Internal
{
@ -15,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
IDictionary<string, object> actionParameters,
ObjectMethodExecutor actionMethodExecutor)
{
var declaredParameterInfos = actionMethodExecutor.ActionParameters;
var declaredParameterInfos = actionMethodExecutor.MethodParameters;
var count = declaredParameterInfos.Length;
if (count == 0)
{

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
@ -12,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Core.Internal;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Internal
@ -776,16 +778,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var returnType = executor.MethodReturnType;
if (returnType == typeof(void))
{
// Sync method returning void
executor.Execute(controller, orderedArguments);
result = new EmptyResult();
}
else if (returnType == typeof(Task))
{
// Async method returning Task
// Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task.
await (Task)executor.Execute(controller, orderedArguments);
result = new EmptyResult();
}
else if (executor.TaskGenericType == typeof(IActionResult))
else if (returnType == typeof(Task<IActionResult>))
{
// Async method returning Task<IActionResult>
// Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task<IActionResult>.
result = await (Task<IActionResult>)executor.Execute(controller, orderedArguments);
if (result == null)
{
@ -793,45 +800,49 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
}
}
else if (executor.IsTypeAssignableFromIActionResult)
else if (IsResultIActionResult(_executor))
{
if (_executor.IsMethodAsync)
{
// Async method returning awaitable-of-IActionResult (e.g., Task<ViewResult>)
// We have to use ExecuteAsync because we don't know the awaitable's type at compile time.
result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments);
}
else
{
// Sync method returning IActionResult (e.g., ViewResult)
result = (IActionResult)_executor.Execute(controller, orderedArguments);
}
if (result == null)
{
throw new InvalidOperationException(
Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));
Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.AsyncResultType ?? returnType));
}
}
else if (!executor.IsMethodAsync)
{
// Sync method returning arbitrary object
var resultAsObject = executor.Execute(controller, orderedArguments);
result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
{
DeclaredType = returnType,
};
}
else if (executor.TaskGenericType != null)
else if (executor.AsyncResultType == typeof(void))
{
var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
{
DeclaredType = executor.TaskGenericType,
};
// Async method returning awaitable-of-void
await executor.ExecuteAsync(controller, orderedArguments);
result = new EmptyResult();
}
else
{
// This will be the case for types which have derived from Task and Task<T> or non Task types.
throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(
executor.MethodInfo.Name,
executor.MethodInfo.DeclaringType));
// Async method returning awaitable-of-nonvoid
var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
{
DeclaredType = executor.AsyncResultType,
};
}
_result = result;
@ -847,6 +858,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private static bool IsResultIActionResult(ObjectMethodExecutor executor)
{
var resultType = executor.AsyncResultType ?? executor.MethodReturnType;
return typeof(IActionResult).IsAssignableFrom(resultType);
}
private async Task InvokeNextResultFilterAsync()
{
try

View File

@ -7,6 +7,7 @@ using System.Linq;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.Internal
{
@ -54,9 +55,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext);
filters = filterFactoryResult.Filters;
var parameterDefaultValues = ParameterDefaultValues
.GetParameterDefaultValues(actionDescriptor.MethodInfo);
var executor = ObjectMethodExecutor.Create(
actionDescriptor.MethodInfo,
actionDescriptor.ControllerTypeInfo);
actionDescriptor.ControllerTypeInfo,
parameterDefaultValues);
cacheEntry = new Entry(filterFactoryResult.CacheableFilters, executor);
cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);

View File

@ -1,253 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ObjectMethodExecutor
{
private readonly object[] _parameterDefaultValues;
private readonly ActionExecutorAsync _executorAsync;
private readonly ActionExecutor _executor;
private static readonly MethodInfo _convertOfTMethod =
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert));
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo));
}
MethodInfo = methodInfo;
TargetTypeInfo = targetTypeInfo;
ActionParameters = methodInfo.GetParameters();
MethodReturnType = methodInfo.ReturnType;
IsMethodAsync = typeof(Task).IsAssignableFrom(MethodReturnType);
TaskGenericType = IsMethodAsync ? GetTaskInnerTypeOrNull(MethodReturnType) : null;
IsTypeAssignableFromIActionResult = typeof(IActionResult).IsAssignableFrom(TaskGenericType ?? MethodReturnType);
if (IsMethodAsync && TaskGenericType != null)
{
// For backwards compatibility we're creating a sync-executor for an async method. This was
// supported in the past even though MVC wouldn't have called it.
_executor = GetExecutor(methodInfo, targetTypeInfo);
_executorAsync = GetExecutorAsync(TaskGenericType, methodInfo, targetTypeInfo);
}
else
{
_executor = GetExecutor(methodInfo, targetTypeInfo);
}
_parameterDefaultValues = GetParameterDefaultValues(ActionParameters);
}
private delegate Task<object> ActionExecutorAsync(object target, object[] parameters);
private delegate object ActionExecutor(object target, object[] parameters);
private delegate void VoidActionExecutor(object target, object[] parameters);
public MethodInfo MethodInfo { get; }
public ParameterInfo[] ActionParameters { get; }
public TypeInfo TargetTypeInfo { get; }
public Type TaskGenericType { get; }
// This field is made internal set because it is set in unit tests.
public Type MethodReturnType { get; internal set; }
public bool IsMethodAsync { get; }
public bool IsTypeAssignableFromIActionResult { get; }
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
var executor = new ObjectMethodExecutor(methodInfo, targetTypeInfo);
return executor;
}
public Task<object> ExecuteAsync(object target, object[] parameters)
{
return _executorAsync(target, parameters);
}
public object Execute(object target, object[] parameters)
{
return _executor(target, parameters);
}
public object GetDefaultValueForParameter(int index)
{
if (index < 0 || index > ActionParameters.Length - 1)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _parameterDefaultValues[index];
}
private static ActionExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)"
// Create function
if (methodCall.Type == typeof(void))
{
var lambda = Expression.Lambda<VoidActionExecutor>(methodCall, targetParameter, parametersParameter);
var voidExecutor = lambda.Compile();
return WrapVoidAction(voidExecutor);
}
else
{
// must coerce methodCall to match ActionExecutor signature
var castMethodCall = Expression.Convert(methodCall, typeof(object));
var lambda = Expression.Lambda<ActionExecutor>(castMethodCall, targetParameter, parametersParameter);
return lambda.Compile();
}
}
private static ActionExecutor WrapVoidAction(VoidActionExecutor executor)
{
return delegate (object target, object[] parameters)
{
executor(target, parameters);
return null;
};
}
private static ActionExecutorAsync GetExecutorAsync(Type taskInnerType, MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
// Parameters to executor
var targetParameter = Expression.Parameter(typeof(object), "target");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
var parameters = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
var coerceMethodCall = GetCoerceMethodCallExpression(taskInnerType, methodCall, methodInfo);
var lambda = Expression.Lambda<ActionExecutorAsync>(coerceMethodCall, targetParameter, parametersParameter);
return lambda.Compile();
}
// 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 Expression GetCoerceMethodCallExpression(
Type taskValueType,
MethodCallExpression methodCall,
MethodInfo methodInfo)
{
var castMethodCall = Expression.Convert(methodCall, typeof(object));
// for: public Task<T> Action()
// constructs: return (Task<object>)Convert<T>((Task<T>)result)
var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType);
var genericMethodCall = Expression.Call(null, genericMethodInfo, castMethodCall);
var convertedResult = Expression.Convert(genericMethodCall, typeof(Task<object>));
return convertedResult;
}
/// <summary>
/// Cast Task of T to Task of object
/// </summary>
private static async Task<object> CastToObject<T>(Task<T> task)
{
return (object)await task;
}
private static Type GetTaskInnerTypeOrNull(Type type)
{
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(Task<>));
return genericType?.GenericTypeArguments[0];
}
private static Task<object> Convert<T>(object taskAsObject)
{
var task = (Task<T>)taskAsObject;
return CastToObject<T>(task);
}
private static object[] GetParameterDefaultValues(ParameterInfo[] parameters)
{
var values = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
var parameterInfo = parameters[i];
object defaultValue;
if (parameterInfo.HasDefaultValue)
{
defaultValue = parameterInfo.DefaultValue;
}
else
{
var defaultValueAttribute = parameterInfo
.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute?.Value == null)
{
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
defaultValue = defaultValueAttribute.Value;
}
}
values[i] = defaultValue;
}
return values;
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Reflection;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public static class ParameterDefaultValues
{
public static object[] GetParameterDefaultValues(MethodInfo methodInfo)
{
if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo));
}
var parameters = methodInfo.GetParameters();
var values = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
var parameterInfo = parameters[i];
object defaultValue;
if (parameterInfo.HasDefaultValue)
{
defaultValue = parameterInfo.DefaultValue;
}
else
{
var defaultValueAttribute = parameterInfo
.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute?.Value == null)
{
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
defaultValue = defaultValueAttribute.Value;
}
}
values[i] = defaultValue;
}
return values;
}
}
}

View File

@ -33,6 +33,8 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.HashCodeCombiner.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.ObjectMethodExecutor.Sources" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.PropertyActivator.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.ViewComponents
@ -109,17 +110,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
object resultAsObject = null;
var taskGenericType = executor.TaskGenericType;
var returnType = executor.MethodReturnType;
if (taskGenericType == typeof(IViewComponentResult))
if (returnType == typeof(Task<IViewComponentResult>))
{
resultAsObject = await (Task<IViewComponentResult>)executor.Execute(component, arguments);
}
else if (taskGenericType == typeof(string))
else if (returnType == typeof(Task<string>))
{
resultAsObject = await (Task<string>)executor.Execute(component, arguments);
}
else if (taskGenericType == typeof(IHtmlContent))
else if (returnType == typeof(Task<IHtmlContent>))
{
resultAsObject = await (Task<IHtmlContent>)executor.Execute(component, arguments);
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Concurrent;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
@ -55,7 +56,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
nameof(ViewComponentDescriptor)));
}
executor = ObjectMethodExecutor.Create(viewComponentDescriptor.MethodInfo, viewComponentDescriptor.TypeInfo);
var parameterDefaultValues = ParameterDefaultValues
.GetParameterDefaultValues(methodInfo);
executor = ObjectMethodExecutor.Create(
viewComponentDescriptor.MethodInfo,
viewComponentDescriptor.TypeInfo,
parameterDefaultValues);
cache.Entries.TryAdd(viewComponentDescriptor, executor);
return executor;

View File

@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing;
@ -2626,7 +2627,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
[Fact]
public async Task InvokeAction_AsyncAction_WithCustomTaskReturnTypeThrows()
public async Task InvokeAction_AsyncAction_WithCustomTaskReturnType()
{
// Arrange
var inputParam1 = 1;
@ -2646,19 +2647,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
nameof(TestController.TaskActionWithCustomTaskReturnType),
actionParameters);
var 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));
// Act
await invoker.InvokeAsync();
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => invoker.InvokeAsync());
Assert.Equal(expectedException, ex.Message);
// Assert
Assert.IsType(typeof(EmptyResult), result);
}
[Fact]
public async Task InvokeAction_AsyncAction_WithCustomTaskOfTReturnTypeThrows()
public async Task InvokeAction_AsyncAction_WithCustomTaskOfTReturnType()
{
// Arrange
var inputParam1 = 1;
@ -2678,15 +2675,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
nameof(TestController.TaskActionWithCustomTaskOfTReturnType),
actionParameters);
var 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));
// Act
await invoker.InvokeAsync();
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => invoker.InvokeAsync());
Assert.Equal(expectedException, ex.Message);
// Assert
Assert.IsType(typeof(ObjectResult), result);
Assert.IsType(typeof(int), ((ObjectResult)result).Value);
Assert.Equal(1, ((ObjectResult)result).Value);
}
[Fact]
@ -2941,7 +2936,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new IFilterMetadata[0],
ObjectMethodExecutor.Create(
actionDescriptor.MethodInfo,
actionDescriptor.ControllerTypeInfo));
actionDescriptor.ControllerTypeInfo,
ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo)));
// Act
await invoker.InvokeAsync();
@ -3378,12 +3374,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public TaskDerivedType TaskActionWithCustomTaskReturnType(int i, string s)
{
return new TaskDerivedType();
var task = new TaskDerivedType();
task.Start();
return task;
}
public TaskOfTDerivedType<int> TaskActionWithCustomTaskOfTReturnType(int i, string s)
{
return new TaskOfTDerivedType<int>(1);
var task = new TaskOfTDerivedType<int>(1);
task.Start();
return task;
}
/// <summary>
@ -3517,7 +3517,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static ObjectMethodExecutor CreateExecutor(ControllerActionDescriptor actionDescriptor)
{
return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo);
return ObjectMethodExecutor.Create(
actionDescriptor.MethodInfo,
actionDescriptor.ControllerTypeInfo,
ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo));
}
private static ControllerContext CreatControllerContext(

View File

@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;

View File

@ -1,227 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ObjectMethodExecutorTest
{
private TestObject _targetObject = new TestObject();
private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo();
[Fact]
public void ExecuteValueMethod()
{
var executor = GetExecutorForMethod("ValueMethod");
var result = executor.Execute(
_targetObject,
new object[] { 10, 20 });
Assert.Equal(30, (int)result);
}
[Fact]
public void ExecuteVoidValueMethod()
{
var executor = GetExecutorForMethod("VoidValueMethod");
var result = executor.Execute(
_targetObject,
new object[] { 10 });
Assert.Same(null, result);
}
[Fact]
public void ExecuteValueMethodWithReturnType()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnType");
var result = executor.Execute(
_targetObject,
new object[] { 10 });
var resultObject = Assert.IsType<TestObject>(result);
Assert.Equal("Hello", resultObject.value);
}
[Fact]
public void ExecuteValueMethodUpdateValue()
{
var executor = GetExecutorForMethod("ValueMethodUpdateValue");
var parameter = new TestObject();
var result = executor.Execute(
_targetObject,
new object[] { parameter });
var resultObject = Assert.IsType<TestObject>(result);
Assert.Equal("HelloWorld", resultObject.value);
}
[Fact]
public void ExecuteValueMethodWithReturnTypeThrowsException()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException");
var parameter = new TestObject();
Assert.Throws<NotImplementedException>(
() => executor.Execute(
_targetObject,
new object[] { parameter }));
}
[Fact]
public async Task ExecuteValueMethodAsync()
{
var executor = GetExecutorForMethod("ValueMethodAsync");
var result = await executor.ExecuteAsync(
_targetObject,
new object[] { 10, 20 });
Assert.Equal(30, (int)result);
}
[Fact]
public async Task ExecuteValueMethodWithReturnTypeAsync()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync");
var result = await executor.ExecuteAsync(
_targetObject,
new object[] { 10 });
var resultObject = Assert.IsType<TestObject>(result);
Assert.Equal("Hello", resultObject.value);
}
[Fact]
public async Task ExecuteValueMethodUpdateValueAsync()
{
var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync");
var parameter = new TestObject();
var result = await executor.ExecuteAsync(
_targetObject,
new object[] { parameter });
var resultObject = Assert.IsType<TestObject>(result);
Assert.Equal("HelloWorld", resultObject.value);
}
[Fact]
public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync()
{
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync");
var parameter = new TestObject();
await Assert.ThrowsAsync<NotImplementedException>(
() => executor.ExecuteAsync(
_targetObject,
new object[] { parameter }));
}
[Theory]
[InlineData("EchoWithDefaultAttributes", new object[] { "hello", true, 10 })]
[InlineData("EchoWithDefaultValues", new object[] { "hello", true, 20 })]
[InlineData("EchoWithDefaultValuesAndAttributes", new object[] { "hello", 20 })]
[InlineData("EchoWithNoDefaultAttributesAndValues", new object[] { null, 0, false, null })]
public void GetDefaultValueForParameters_ReturnsExpectedValues(string methodName, object[] expectedValues)
{
var executor = GetExecutorForMethod(methodName);
var defaultValues = new object[expectedValues.Length];
for (var index = 0; index < expectedValues.Length; index++)
{
defaultValues[index] = executor.GetDefaultValueForParameter(index);
}
Assert.Equal(expectedValues, defaultValues);
}
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
{
var method = typeof(TestObject).GetMethod(methodName);
var executor = ObjectMethodExecutor.Create(method, targetTypeInfo);
return executor;
}
public class TestObject
{
public string value;
public int ValueMethod(int i, int j)
{
return i + j;
}
public void VoidValueMethod(int i)
{
}
public TestObject ValueMethodWithReturnType(int i)
{
return new TestObject() { value = "Hello" }; ;
}
public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i)
{
throw new NotImplementedException("Not Implemented Exception");
}
public TestObject ValueMethodUpdateValue(TestObject parameter)
{
parameter.value = "HelloWorld";
return parameter;
}
public Task<int> ValueMethodAsync(int i, int j)
{
return Task.FromResult<int>(i + j);
}
public async Task VoidValueMethodAsync(int i)
{
await ValueMethodAsync(3, 4);
}
public Task<TestObject> ValueMethodWithReturnTypeAsync(int i)
{
return Task.FromResult<TestObject>(new TestObject() { value = "Hello" });
}
public Task<TestObject> ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i)
{
throw new NotImplementedException("Not Implemented Exception");
}
public Task<TestObject> ValueMethodUpdateValueAsync(TestObject parameter)
{
parameter.value = "HelloWorld";
return Task.FromResult<TestObject>(parameter);
}
public string EchoWithDefaultAttributes(
[DefaultValue("hello")] string input1,
[DefaultValue(true)] bool input2,
[DefaultValue(10)] int input3)
{
return input1;
}
public string EchoWithDefaultValues(
string input1 = "hello",
bool input2 = true,
int input3 = 20)
{
return input1;
}
public string EchoWithDefaultValuesAndAttributes(
[DefaultValue("Hi")] string input1 = "hello",
[DefaultValue(10)] int input3 = 20)
{
return input1;
}
public string EchoWithNoDefaultAttributesAndValues(
string input1,
int input2,
bool input3,
TestObject input4)
{
return input1;
}
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ParameterDefaultValuesTest
{
[Theory]
[InlineData("DefaultAttributes", new object[] { "hello", true, 10 })]
[InlineData("DefaultValues", new object[] { "hello", true, 20 })]
[InlineData("DefaultValuesAndAttributes", new object[] { "hello", 20 })]
[InlineData("NoDefaultAttributesAndValues", new object[] { null, 0, false, null })]
public void GetParameterDefaultValues_ReturnsExpectedValues(string methodName, object[] expectedValues)
{
// Arrange
var methodInfo = typeof(TestObject).GetMethod(methodName);
// Act
var actualValues = ParameterDefaultValues.GetParameterDefaultValues(methodInfo);
// Assert
Assert.Equal(expectedValues, actualValues);
}
private class TestObject
{
public void DefaultAttributes(
[DefaultValue("hello")] string input1,
[DefaultValue(true)] bool input2,
[DefaultValue(10)] int input3)
{
}
public void DefaultValues(
string input1 = "hello",
bool input2 = true,
int input3 = 20)
{
}
public void DefaultValuesAndAttributes(
[DefaultValue("Hi")] string input1 = "hello",
[DefaultValue(10)] int input3 = 20)
{
}
public void NoDefaultAttributesAndValues(
string input1,
int input2,
bool input3,
TestObject input4)
{
}
}
}
}

View File

@ -0,0 +1,282 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class AsyncActionsTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
{
public AsyncActionsTests(MvcTestFixture<BasicWebSite.Startup> fixture)
{
Client = fixture.Client;
}
public HttpClient Client { get; }
[Fact]
public async Task AsyncVoidAction_ReturnsOK()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/AsyncVoidAction");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(0, responseBody.Length);
}
[Fact]
public async Task TaskAction_ReturnsOK()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskAction");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(0, responseBody.Length);
}
[Fact]
public async Task TaskExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskExceptionAction");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task TaskOfObjectAction_ReturnsJsonFormattedObject()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskOfObjectAction?message=Alpha");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("{\"text\":\"Alpha\"}", responseBody);
}
[Fact]
public async Task TaskOfObjectExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskOfObjectExceptionAction?message=Alpha");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task TaskOfIActionResultAction_ReturnsString()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskOfIActionResultAction?message=Beta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Beta", responseBody);
}
[Fact]
public async Task TaskOfIActionResultExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskOfIActionResultExceptionAction?message=Beta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task TaskOfContentResultAction_ReturnsString()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskOfContentResultAction?message=Gamma");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Gamma", responseBody);
}
[Fact]
public async Task TaskOfContentResultExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/TaskOfContentResultExceptionAction?message=Gamma");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task PreCompletedValueTaskOfObjectAction_ReturnsJsonFormattedObject()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/PreCompletedValueTaskOfObjectAction?message=Delta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("{\"text\":\"Delta\"}", responseBody);
}
[Fact]
public async Task PreCompletedValueTaskOfObjectExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/PreCompletedValueTaskOfObjectExceptionAction?message=Delta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task PreCompletedValueTaskOfIActionResultAction_ReturnsString()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/PreCompletedValueTaskOfIActionResultAction?message=Epsilon");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Epsilon", responseBody);
}
[Fact]
public async Task PreCompletedValueTaskOfIActionResultExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/PreCompletedValueTaskOfIActionResultExceptionAction?message=Epsilon");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task PreCompletedValueTaskOfContentResultAction_ReturnsString()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/PreCompletedValueTaskOfContentResultAction?message=Zeta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Zeta", responseBody);
}
[Fact]
public async Task PreCompletedValueTaskOfContentResultExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/PreCompletedValueTaskOfContentResultExceptionAction?message=Zeta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task CustomAwaitableVoidAction_ReturnsOK()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableVoidAction");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(0, responseBody.Length);
}
[Fact]
public async Task CustomAwaitableVoidExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableVoidExceptionAction");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task CustomAwaitableOfObjectAction_ReturnsJsonFormattedObject()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableOfObjectAction?message=Eta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("{\"text\":\"Eta\"}", responseBody);
}
[Fact]
public async Task CustomAwaitableOfObjectExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableOfObjectExceptionAction?message=Eta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task CustomAwaitableOfIActionResultAction_ReturnsString()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableOfIActionResultAction?message=Theta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Theta", responseBody);
}
[Fact]
public async Task CustomAwaitableOfIActionResultExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableOfIActionResultExceptionAction?message=Theta");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
[Fact]
public async Task CustomAwaitableOfContentResultAction_ReturnsString()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableOfContentResultAction?message=Iota");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Iota", responseBody);
}
[Fact]
public async Task CustomAwaitableOfContentResultExceptionAction_ReturnsCorrectError()
{
// Act
var response = await Client.GetAsync("http://localhost/AsyncActions/CustomAwaitableOfContentResultExceptionAction?message=Iota");
var responseBody = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Action exception message: This is a custom exception.", responseBody);
}
}
}

View File

@ -0,0 +1,254 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace BasicWebSite.Controllers
{
public class AsyncActionsController : Controller
{
const int SimulateDelayMilliseconds = 20;
public override void OnActionExecuted(ActionExecutedContext context)
{
// So that tests can observe we're following the proper flow after an exception, surface a
// message saying what the exception was.
if (context.Exception != null)
{
context.Result = Content($"Action exception message: {context.Exception.Message}");
context.ExceptionHandled = true;
}
}
public async void AsyncVoidAction()
{
await Task.Delay(SimulateDelayMilliseconds);
}
public Task TaskAction()
{
return Task.Delay(SimulateDelayMilliseconds);
}
public async Task TaskExceptionAction()
{
await Task.Delay(SimulateDelayMilliseconds);
throw new CustomException();
}
public async Task<Message> TaskOfObjectAction(string message)
{
await Task.Delay(SimulateDelayMilliseconds);
return new Message { Text = message };
}
public async Task<Message> TaskOfObjectExceptionAction(string message)
{
await Task.Delay(SimulateDelayMilliseconds);
throw new CustomException();
}
public async Task<IActionResult> TaskOfIActionResultAction(string message)
{
await Task.Delay(SimulateDelayMilliseconds);
return Content(message);
}
public async Task<IActionResult> TaskOfIActionResultExceptionAction(string message)
{
await Task.Delay(SimulateDelayMilliseconds);
throw new CustomException();
}
public async Task<ContentResult> TaskOfContentResultAction(string message)
{
await Task.Delay(SimulateDelayMilliseconds);
return Content(message);
}
public async Task<ContentResult> TaskOfContentResultExceptionAction(string message)
{
await Task.Delay(SimulateDelayMilliseconds);
throw new CustomException();
}
public ValueTask<Message> PreCompletedValueTaskOfObjectAction(string message)
{
return new ValueTask<Message>(new Message { Text = message });
}
public ValueTask<Message> PreCompletedValueTaskOfObjectExceptionAction(string message)
{
throw new CustomException();
}
public ValueTask<IActionResult> PreCompletedValueTaskOfIActionResultAction(string message)
{
return new ValueTask<IActionResult>(Content(message));
}
public ValueTask<IActionResult> PreCompletedValueTaskOfIActionResultExceptionAction(string message)
{
throw new CustomException();
}
public ValueTask<ContentResult> PreCompletedValueTaskOfContentResultAction(string message)
{
return new ValueTask<ContentResult>(Content(message));
}
public ValueTask<ContentResult> PreCompletedValueTaskOfContentResultExceptionAction(string message)
{
throw new CustomException();
}
public CustomAwaitable CustomAwaitableVoidAction()
{
return new CustomAwaitable(SimulateDelayMilliseconds);
}
public CustomAwaitable CustomAwaitableVoidExceptionAction()
{
throw new CustomException();
}
public CustomAwaitable<Message> CustomAwaitableOfObjectAction(string message)
{
return new CustomAwaitable<Message>(
SimulateDelayMilliseconds,
new Message { Text = message });
}
public CustomAwaitable<Message> CustomAwaitableOfObjectExceptionAction(string message)
{
throw new CustomException();
}
public CustomAwaitable<IActionResult> CustomAwaitableOfIActionResultAction(string message)
{
return new CustomAwaitable<IActionResult>(SimulateDelayMilliseconds, Content(message));
}
public CustomAwaitable<IActionResult> CustomAwaitableOfIActionResultExceptionAction(string message)
{
throw new CustomException();
}
public CustomAwaitable<ContentResult> CustomAwaitableOfContentResultAction(string message)
{
return new CustomAwaitable<ContentResult>(SimulateDelayMilliseconds, Content(message));
}
public CustomAwaitable<ContentResult> CustomAwaitableOfContentResultExceptionAction(string message)
{
throw new CustomException();
}
public class Message
{
public string Text { get; set; }
}
public class CustomAwaitable
{
protected readonly int _simulateDelayMilliseconds;
public CustomAwaitable(int simulateDelayMilliseconds)
{
_simulateDelayMilliseconds = simulateDelayMilliseconds;
}
public CustomAwaiter GetAwaiter()
{
return new CustomAwaiter(_simulateDelayMilliseconds);
}
}
public class CustomAwaitable<T> : CustomAwaitable
{
private readonly T _result;
public CustomAwaitable(int simulateDelayMilliseconds, T result)
: base(simulateDelayMilliseconds)
{
_result = result;
}
public new CustomAwaiter<T> GetAwaiter()
{
return new CustomAwaiter<T>(_simulateDelayMilliseconds, _result);
}
}
public class CustomAwaiter : INotifyCompletion
{
private IList<Action> _continuations = new List<Action>();
public CustomAwaiter(int simulateDelayMilliseconds)
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(simulateDelayMilliseconds);
lock(_continuations)
{
IsCompleted = true;
foreach (var continuation in _continuations)
{
continuation();
}
_continuations.Clear();
}
});
}
public bool IsCompleted { get; private set; }
public void OnCompleted(Action continuation)
{
lock (_continuations)
{
if (IsCompleted)
{
continuation();
}
else
{
_continuations.Add(continuation);
}
}
}
public void GetResult()
{
}
}
public class CustomAwaiter<T> : CustomAwaiter
{
private readonly T _result;
public CustomAwaiter(int simulateDelayMilliseconds, T result)
: base(simulateDelayMilliseconds)
{
_result = result;
}
public new T GetResult() => _result;
}
public class CustomException : Exception
{
public CustomException() : base("This is a custom exception.")
{
}
}
}
}