[Perf] Refactoring ControllerActionInvoker to avoid coercing action method's return type to Task<object> for simple cases. Fixes #4539
This commit is contained in:
parent
f7b2ee80fd
commit
d7ccea17ce
|
|
@ -11,23 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public static class ControllerActionExecutor
|
||||
{
|
||||
public static Task<object> ExecuteAsync(
|
||||
ObjectMethodExecutor actionMethodExecutor,
|
||||
object instance,
|
||||
IDictionary<string, object> actionArguments)
|
||||
{
|
||||
var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor);
|
||||
return ExecuteAsync(actionMethodExecutor, instance, orderedArguments);
|
||||
}
|
||||
|
||||
public static Task<object> ExecuteAsync(
|
||||
ObjectMethodExecutor actionMethodExecutor,
|
||||
object instance,
|
||||
object[] orderedActionArguments)
|
||||
{
|
||||
return actionMethodExecutor.ExecuteAsync(instance, orderedActionArguments);
|
||||
}
|
||||
|
||||
public static object[] PrepareArguments(
|
||||
IDictionary<string, object> actionParameters,
|
||||
ObjectMethodExecutor actionMethodExecutor)
|
||||
|
|
|
|||
|
|
@ -573,12 +573,67 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
_logger.ActionMethodExecuting(_actionExecutingContext, arguments);
|
||||
|
||||
var actionReturnValue = await ControllerActionExecutor.ExecuteAsync(
|
||||
_executor,
|
||||
_controller,
|
||||
arguments);
|
||||
var returnType = _executor.MethodReturnType;
|
||||
|
||||
result = CreateActionResult( actionMethodInfo.ReturnType, actionReturnValue);
|
||||
if (returnType == typeof(void))
|
||||
{
|
||||
_executor.Execute(_controller, arguments);
|
||||
result = new EmptyResult();
|
||||
}
|
||||
else if (returnType == typeof(Task))
|
||||
{
|
||||
await (Task)_executor.Execute(_controller, arguments);
|
||||
result = new EmptyResult();
|
||||
}
|
||||
else if (_executor.TaskGenericType == typeof(IActionResult))
|
||||
{
|
||||
result = await (Task<IActionResult>)_executor.Execute(_controller, arguments);
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
|
||||
}
|
||||
}
|
||||
else if (_executor.IsTypeAssignableFromIActionResult)
|
||||
{
|
||||
if (_executor.IsMethodAsync)
|
||||
{
|
||||
result = (IActionResult)await _executor.ExecuteAsync(_controller, arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (IActionResult)_executor.Execute(_controller, arguments);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));
|
||||
}
|
||||
}
|
||||
else if (!_executor.IsMethodAsync)
|
||||
{
|
||||
var resultAsObject = _executor.Execute(_controller, arguments);
|
||||
result = new ObjectResult(resultAsObject)
|
||||
{
|
||||
DeclaredType = returnType,
|
||||
};
|
||||
}
|
||||
else if (_executor.TaskGenericType != null)
|
||||
{
|
||||
var resultAsObject = await _executor.ExecuteAsync(_controller, arguments);
|
||||
result = new ObjectResult(resultAsObject)
|
||||
{
|
||||
DeclaredType = _executor.TaskGenericType,
|
||||
};
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
_logger.ActionMethodExecuted(_actionExecutingContext, result);
|
||||
}
|
||||
|
|
@ -773,49 +828,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
// Marking as internal for Unit Testing purposes.
|
||||
internal static IActionResult CreateActionResult(Type declaredReturnType, object actionReturnValue)
|
||||
{
|
||||
if (declaredReturnType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(declaredReturnType));
|
||||
}
|
||||
|
||||
// optimize common path
|
||||
var actionResult = actionReturnValue as IActionResult;
|
||||
if (actionResult != null)
|
||||
{
|
||||
return actionResult;
|
||||
}
|
||||
|
||||
if (declaredReturnType == typeof(void) ||
|
||||
declaredReturnType == typeof(Task))
|
||||
{
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
// Unwrap potential Task<T> types.
|
||||
var actualReturnType = GetTaskInnerTypeOrNull(declaredReturnType) ?? declaredReturnType;
|
||||
if (actionReturnValue == null &&
|
||||
typeof(IActionResult).IsAssignableFrom(actualReturnType))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(actualReturnType));
|
||||
}
|
||||
|
||||
return new ObjectResult(actionReturnValue)
|
||||
{
|
||||
DeclaredType = actualReturnType
|
||||
};
|
||||
}
|
||||
|
||||
private static Type GetTaskInnerTypeOrNull(Type type)
|
||||
{
|
||||
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(Task<>));
|
||||
|
||||
return genericType?.GenericTypeArguments[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A one-way cursor for filters.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -22,25 +22,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private static readonly MethodInfo _convertOfTMethod =
|
||||
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert));
|
||||
|
||||
private static readonly Expression<Func<object, Task<object>>> _createTaskFromResultExpression =
|
||||
((result) => Task.FromResult(result));
|
||||
|
||||
private static readonly MethodInfo _createTaskFromResultMethod =
|
||||
((MethodCallExpression)_createTaskFromResultExpression.Body).Method;
|
||||
|
||||
private static readonly Expression<Func<object, string, Type, Task<object>>> _coerceTaskExpression =
|
||||
((result, methodName, declaringType) => ObjectMethodExecutor.CoerceTaskType(result, methodName, declaringType));
|
||||
|
||||
private static readonly MethodInfo _coerceMethod = ((MethodCallExpression)_coerceTaskExpression.Body).Method;
|
||||
|
||||
private ObjectMethodExecutor(MethodInfo methodInfo)
|
||||
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);
|
||||
}
|
||||
|
||||
private delegate Task<object> ActionExecutorAsync(object target, object[] parameters);
|
||||
|
|
@ -53,17 +47,40 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
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; }
|
||||
|
||||
private ActionExecutorAsync TaskOfTActionExecutorAsync
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_executorAsync == null)
|
||||
{
|
||||
_executorAsync = GetExecutorAsync(TaskGenericType, MethodInfo, TargetTypeInfo);
|
||||
}
|
||||
|
||||
return _executorAsync;
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
||||
{
|
||||
var executor = new ObjectMethodExecutor(methodInfo);
|
||||
var executor = new ObjectMethodExecutor(methodInfo, targetTypeInfo);
|
||||
executor._executor = GetExecutor(methodInfo, targetTypeInfo);
|
||||
executor._executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo);
|
||||
return executor;
|
||||
}
|
||||
|
||||
public Task<object> ExecuteAsync(object target, object[] parameters)
|
||||
{
|
||||
return _executorAsync(target, parameters);
|
||||
return TaskOfTActionExecutorAsync(target, parameters);
|
||||
}
|
||||
|
||||
public object Execute(object target, object[] parameters)
|
||||
|
|
@ -132,7 +149,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
};
|
||||
}
|
||||
|
||||
private static ActionExecutorAsync GetExecutorAsync(MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
||||
private static ActionExecutorAsync GetExecutorAsync(Type taskInnerType, MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
||||
{
|
||||
// Parameters to executor
|
||||
var targetParameter = Expression.Parameter(typeof(object), "target");
|
||||
|
|
@ -155,83 +172,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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 WrapVoidActionAsync(voidExecutor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// must coerce methodCall to match ActionExecutorAsync signature
|
||||
var coerceMethodCall = GetCoerceMethodCallExpression(methodCall, methodInfo);
|
||||
var lambda = Expression.Lambda<ActionExecutorAsync>(coerceMethodCall, targetParameter, parametersParameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
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(MethodCallExpression methodCall, MethodInfo methodInfo)
|
||||
private static Expression GetCoerceMethodCallExpression(
|
||||
Type taskValueType,
|
||||
MethodCallExpression methodCall,
|
||||
MethodInfo methodInfo)
|
||||
{
|
||||
var castMethodCall = Expression.Convert(methodCall, typeof(object));
|
||||
var returnType = methodCall.Type;
|
||||
|
||||
if (typeof(Task).IsAssignableFrom(returnType))
|
||||
{
|
||||
if (returnType == typeof(Task))
|
||||
{
|
||||
var stringExpression = Expression.Constant(methodInfo.Name);
|
||||
var typeExpression = Expression.Constant(methodInfo.DeclaringType);
|
||||
return Expression.Call(null, _coerceMethod, castMethodCall, stringExpression, typeExpression);
|
||||
}
|
||||
|
||||
var taskValueType = GetTaskInnerTypeOrNull(returnType);
|
||||
if (taskValueType != null)
|
||||
{
|
||||
// for: public Task<T> Action()
|
||||
// constructs: return (Task<object>)Convert<T>((Task<T>)result)
|
||||
var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType);
|
||||
var genericMethodCall = Expression.Call(null, genericMethodInfo, castMethodCall);
|
||||
var convertedResult = Expression.Convert(genericMethodCall, typeof(Task<object>));
|
||||
return convertedResult;
|
||||
}
|
||||
|
||||
// This will be the case for types which have derived from Task and Task<T>
|
||||
throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(
|
||||
methodInfo.Name,
|
||||
methodInfo.DeclaringType));
|
||||
}
|
||||
|
||||
return Expression.Call(null, _createTaskFromResultMethod, castMethodCall);
|
||||
}
|
||||
|
||||
private static ActionExecutorAsync WrapVoidActionAsync(VoidActionExecutor executor)
|
||||
{
|
||||
return delegate (object target, object[] parameters)
|
||||
{
|
||||
executor(target, parameters);
|
||||
return Task.FromResult<object>(null);
|
||||
};
|
||||
}
|
||||
|
||||
private static Task<object> CoerceTaskType(object result, string methodName, Type declaringType)
|
||||
{
|
||||
var resultAsTask = (Task)result;
|
||||
return CastToObject(resultAsTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast Task to Task of object
|
||||
/// </summary>
|
||||
private static async Task<object> CastToObject(Task task)
|
||||
{
|
||||
await task;
|
||||
return null;
|
||||
// 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>
|
||||
|
|
|
|||
|
|
@ -71,48 +71,64 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var methodInfo = context.ViewComponentDescriptor?.MethodInfo;
|
||||
if (methodInfo == null)
|
||||
var executor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||
|
||||
var returnType = executor.MethodReturnType;
|
||||
|
||||
if (returnType == typeof(void) || returnType == typeof(Task))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||
nameof(ViewComponentDescriptor.MethodInfo),
|
||||
nameof(ViewComponentDescriptor)));
|
||||
throw new InvalidOperationException(Resources.ViewComponent_MustReturnValue);
|
||||
}
|
||||
|
||||
var isAsync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType);
|
||||
IViewComponentResult result;
|
||||
if (isAsync)
|
||||
if (executor.IsMethodAsync)
|
||||
{
|
||||
result = await InvokeAsyncCore(context);
|
||||
result = await InvokeAsyncCore(executor, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
||||
// execute the IViewResult asynchronously.
|
||||
result = InvokeSyncCore(context);
|
||||
result = InvokeSyncCore(executor, context);
|
||||
}
|
||||
|
||||
await result.ExecuteAsync(context);
|
||||
}
|
||||
|
||||
private async Task<IViewComponentResult> InvokeAsyncCore(ViewComponentContext context)
|
||||
private async Task<IViewComponentResult> InvokeAsyncCore(ObjectMethodExecutor executor, ViewComponentContext context)
|
||||
{
|
||||
var component = _viewComponentFactory.CreateViewComponent(context);
|
||||
|
||||
using (_logger.ViewComponentScope(context))
|
||||
{
|
||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, methodExecutor);
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, executor);
|
||||
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context, arguments);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(methodExecutor, component, arguments);
|
||||
|
||||
var viewComponentResult = CoerceToViewComponentResult(result);
|
||||
object resultAsObject = null;
|
||||
var taskGenericType = executor.TaskGenericType;
|
||||
|
||||
if (taskGenericType == typeof(IViewComponentResult))
|
||||
{
|
||||
resultAsObject = await (Task<IViewComponentResult>)executor.Execute(component, arguments);
|
||||
}
|
||||
else if (taskGenericType == typeof(string))
|
||||
{
|
||||
resultAsObject = await (Task<string>)executor.Execute(component, arguments);
|
||||
}
|
||||
else if (taskGenericType == typeof(IHtmlContent))
|
||||
{
|
||||
resultAsObject = await (Task<IHtmlContent>)executor.Execute(component, arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultAsObject = await executor.ExecuteAsync(component, arguments);
|
||||
}
|
||||
|
||||
var viewComponentResult = CoerceToViewComponentResult(resultAsObject);
|
||||
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
|
||||
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
|
||||
|
||||
|
|
@ -122,27 +138,25 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
|||
}
|
||||
}
|
||||
|
||||
private IViewComponentResult InvokeSyncCore(ViewComponentContext context)
|
||||
private IViewComponentResult InvokeSyncCore(ObjectMethodExecutor executor, ViewComponentContext context)
|
||||
{
|
||||
var component = _viewComponentFactory.CreateViewComponent(context);
|
||||
|
||||
using (_logger.ViewComponentScope(context))
|
||||
{
|
||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
||||
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||
|
||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||
context.Arguments,
|
||||
methodExecutor);
|
||||
executor);
|
||||
|
||||
_diagnosticSource.BeforeViewComponent(context, component);
|
||||
_logger.ViewComponentExecuting(context, arguments);
|
||||
|
||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||
object result;
|
||||
|
||||
try
|
||||
{
|
||||
result = methodExecutor.Execute(component, arguments);
|
||||
result = executor.Execute(component, arguments);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Concurrent;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
|
|
@ -46,6 +47,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
return executor;
|
||||
}
|
||||
|
||||
var methodInfo = viewComponentContext.ViewComponentDescriptor?.MethodInfo;
|
||||
if (methodInfo == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
|
||||
nameof(ViewComponentDescriptor.MethodInfo),
|
||||
nameof(ViewComponentDescriptor)));
|
||||
}
|
||||
|
||||
executor = ObjectMethodExecutor.Create(viewComponentDescriptor.MethodInfo, viewComponentDescriptor.TypeInfo);
|
||||
|
||||
cache.Entries.TryAdd(viewComponentDescriptor, executor);
|
||||
|
|
|
|||
|
|
@ -1,509 +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.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class ControllerActionExecutorTests
|
||||
{
|
||||
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_TaskReturnType()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithTaskReturnType = new MethodWithTaskReturnType(_controller.TaskAction);
|
||||
var result = await ExecuteAction(
|
||||
methodWithTaskReturnType,
|
||||
_controller,
|
||||
actionParameters);
|
||||
|
||||
// Assert
|
||||
Assert.Same(null, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_TaskOfValueReturnType()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters);
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_TaskOfTaskOfValueReturnType()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithTaskOfTaskOfIntReturnType = new MethodWithTaskOfTaskOfIntReturnType(_controller.TaskOfTaskAction);
|
||||
|
||||
// Act
|
||||
var result = await (Task<int>)( await ExecuteAction(
|
||||
methodWithTaskOfTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_WithAsyncKeywordThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionWithException);
|
||||
|
||||
// Act and Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_WithoutAsyncThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionWithExceptionWithoutAsync);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_WithExceptionsAfterAwait()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionThrowAfterAwait);
|
||||
var expectedException = "Argument Exception";
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncAction()
|
||||
{
|
||||
// Arrange
|
||||
var inputString = "hello";
|
||||
var syncMethod = new SyncMethod(_controller.Echo);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", inputString } });
|
||||
// Assert
|
||||
Assert.Equal(inputString, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncAction_WithException()
|
||||
{
|
||||
// Arrange
|
||||
var inputString = "hello";
|
||||
var syncMethod = new SyncMethod(_controller.EchoWithException);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", inputString } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithArgumentDictionary_DefaultValueAttributeUsed()
|
||||
{
|
||||
// Arrange
|
||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("hello", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithArgumentArray_DefaultValueAttributeIgnored()
|
||||
{
|
||||
// Arrange
|
||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new object[] { null, });
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithArgumentDictionary_DefaultParameterValueUsed()
|
||||
{
|
||||
// Arrange
|
||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("world", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithArgumentDictionary_AnyValue_HasPrecedenceOverDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
syncMethod,
|
||||
_controller,
|
||||
new Dictionary<string, object>() { { "input", null } });
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_WithCustomTaskReturnTypeThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var 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);
|
||||
|
||||
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 & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => ExecuteAction(
|
||||
methodWithCutomTaskReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncAction_WithCustomTaskOfTReturnTypeThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithCutomTaskOfTReturnType = new MethodWithCustomTaskOfTReturnType(_controller.TaskActionWithCustomTaskOfTReturnType);
|
||||
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 & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => ExecuteAction(
|
||||
methodWithCutomTaskOfTReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
// Async actions that declares a return type of Task and returns an instance of Task<Task> or Task<Task<T>>
|
||||
// are not handled in any special way as they are not supported. They are considered as methods that return Task type.
|
||||
[Fact]
|
||||
public async Task AsyncAction_ReturningUnwrappedTask()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var methodWithUnwrappedTask = new MethodWithTaskReturnType(_controller.UnwrappedTask);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
methodWithUnwrappedTask,
|
||||
_controller,
|
||||
actionParameters);
|
||||
|
||||
// Assert
|
||||
Assert.Same(null, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
// Async actions that has dynamic return type but return Task or Task<T> are not handled in any special way as,
|
||||
// they are not supported. They are considered as regular methods that return object type.
|
||||
public async Task AsyncAction_WithDynamicReturnType()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
|
||||
var dynamicTaskMethod = new ReturnTaskAsDynamicValue(_controller.ReturnTaskAsDynamicValue);
|
||||
|
||||
// Act
|
||||
var result = await (Task<int>)(await ExecuteAction(
|
||||
dynamicTaskMethod,
|
||||
_controller,
|
||||
actionParameters));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParametersInRandomOrder()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var 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);
|
||||
|
||||
// Act
|
||||
var result = await ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(inputParam1, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvalidParameterValueThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam2 = "Second Parameter";
|
||||
|
||||
var actionParameters = new Dictionary<string, object> { { "i", "Some Invalid Value" }, { "s", inputParam2 } };
|
||||
var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction);
|
||||
|
||||
// Act & Assert
|
||||
// If it is an unrecognized derived type we throw an InvalidOperationException.
|
||||
var ex = await Assert.ThrowsAsync<InvalidCastException>(
|
||||
() => ExecuteAction(
|
||||
methodWithTaskOfIntReturnType,
|
||||
_controller,
|
||||
actionParameters));
|
||||
}
|
||||
|
||||
private async Task<object> ExecuteAction(
|
||||
Delegate methodDelegate,
|
||||
TestController controller,
|
||||
IDictionary<string, object> actionParameters)
|
||||
{
|
||||
var executor = ObjectMethodExecutor.Create(methodDelegate.GetMethodInfo(), _controller.GetType().GetTypeInfo());
|
||||
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
executor,
|
||||
controller,
|
||||
actionParameters);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<object> ExecuteAction(
|
||||
Delegate methodDelegate,
|
||||
TestController controller,
|
||||
object[] actionParameters)
|
||||
{
|
||||
var executor = ObjectMethodExecutor.Create(methodDelegate.GetMethodInfo(), _controller.GetType().GetTypeInfo());
|
||||
|
||||
var result = await ControllerActionExecutor.ExecuteAsync(
|
||||
executor,
|
||||
controller,
|
||||
actionParameters);
|
||||
return result;
|
||||
}
|
||||
|
||||
public class TestController
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
public async Task TaskAction(int i, string s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
#pragma warning disable 1998
|
||||
public async Task<int> TaskValueTypeAction(int i, string s)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
#pragma warning disable 1998
|
||||
public async Task<Task<int>> TaskOfTaskAction(int i, string s)
|
||||
{
|
||||
return TaskValueTypeAction(i, s);
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
public Task<int> TaskValueTypeActionWithoutAsync(int i, string s)
|
||||
{
|
||||
return TaskValueTypeAction(i, s);
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
public async Task<int> TaskActionWithException(int i, string s)
|
||||
{
|
||||
throw new NotImplementedException("Not Implemented Exception");
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
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)
|
||||
{
|
||||
return new TaskDerivedType();
|
||||
}
|
||||
|
||||
public TaskOfTDerivedType<int> TaskActionWithCustomTaskOfTReturnType(int i, string s)
|
||||
{
|
||||
return new TaskOfTDerivedType<int>(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Task{TResult}"/> instead of a <see cref="Task"/>.
|
||||
/// </summary>
|
||||
public Task UnwrappedTask(int i, string s)
|
||||
{
|
||||
return Task.Factory.StartNew(async () => await Task.Factory.StartNew(() => i));
|
||||
}
|
||||
|
||||
public string Echo(string input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
public string EchoWithException(string input)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string EchoWithDefaultValue([DefaultValue("hello")] string input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
public string EchoWithDefaultValueAndAttribute([DefaultValue("hello")] string input = "world")
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
|
@ -33,10 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
private readonly ContentResult _result = new ContentResult() { Content = "Hello, world!" };
|
||||
|
||||
private struct SampleStruct
|
||||
{
|
||||
public int x;
|
||||
}
|
||||
private readonly TestController _controller = new TestController();
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow()
|
||||
|
|
@ -1863,73 +1862,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.False(invoker.ControllerFactory.CreateCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateActionResult_ReturnsSameActionResult()
|
||||
{
|
||||
// Arrange
|
||||
var mockActionResult = new Mock<IActionResult>();
|
||||
|
||||
// Assert
|
||||
var result = ControllerActionInvoker.CreateActionResult(
|
||||
mockActionResult.Object.GetType(), mockActionResult.Object);
|
||||
|
||||
// Act
|
||||
Assert.Same(mockActionResult.Object, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[ReplaceCulture]
|
||||
public void CreateActionResult_NullActionResultReturnValueThrows()
|
||||
{
|
||||
// Arrange, Act & Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => ControllerActionInvoker.CreateActionResult(typeof(IActionResult), null),
|
||||
"Cannot return null from an action method with a return type of '"
|
||||
+ typeof(IActionResult)
|
||||
+ "'.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(void))]
|
||||
[InlineData(typeof(Task))]
|
||||
public void CreateActionResult_Types_ReturnsEmptyResultForTaskAndVoidReturnTypes(Type type)
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = ControllerActionInvoker.CreateActionResult(type, null);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<EmptyResult>(result);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> CreateActionResult_ReturnsObjectContentResultData
|
||||
{
|
||||
get
|
||||
{
|
||||
var anonymousObject = new { x1 = 10, y1 = "Hello" };
|
||||
yield return new object[] { anonymousObject.GetType(), anonymousObject, };
|
||||
yield return new object[] { typeof(int), 5 };
|
||||
yield return new object[] { typeof(string), "sample input" };
|
||||
|
||||
SampleStruct test;
|
||||
test.x = 10;
|
||||
yield return new object[] { test.GetType(), test };
|
||||
yield return new object[] { typeof(Task<int>), 5 };
|
||||
yield return new object[] { typeof(Task<string>), "Hello world" };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CreateActionResult_ReturnsObjectContentResultData))]
|
||||
public void CreateActionResult_ReturnsObjectContentResult(Type type, object input)
|
||||
{
|
||||
// Arrange & Act
|
||||
var actualResult = ControllerActionInvoker.CreateActionResult(type, input);
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(actualResult);
|
||||
Assert.Same(input, contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaxAllowedErrorsIsSet_BeforeCallingAuthorizationFilter()
|
||||
{
|
||||
|
|
@ -1946,6 +1878,488 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
await invoker.InvokeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_TaskReturnType()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskAction), actionParameters);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.IsType<EmptyResult>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_TaskOfValueReturnType()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskValueTypeAction), actionParameters);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal(inputParam1, contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_WithAsyncKeywordThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskActionWithException), actionParameters);
|
||||
|
||||
// Act and Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => invoker.InvokeAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_WithoutAsyncThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskActionWithExceptionWithoutAsync), actionParameters);
|
||||
|
||||
// Act and Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => invoker.InvokeAsync());
|
||||
}
|
||||
|
||||
public async Task InvokeAction_AsyncAction_WithExceptionsAfterAwait()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskActionThrowAfterAwait), actionParameters);
|
||||
var expectedException = "Argument Exception";
|
||||
|
||||
// Act and Assert
|
||||
var ex = await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => invoker.InvokeAsync());
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_SyncAction()
|
||||
{
|
||||
// Arrange
|
||||
var inputString = "hello";
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.Echo), new Dictionary<string, object>() { { "input", inputString } });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal(inputString, contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_SyncAction_WithException()
|
||||
{
|
||||
// Arrange
|
||||
var inputString = "hello";
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.EchoWithException),
|
||||
new Dictionary<string, object>() { { "input", inputString } });
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
() => invoker.InvokeAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_SyncMethod_WithArgumentDictionary_DefaultValueAttributeUsed()
|
||||
{
|
||||
// Arrange
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.EchoWithDefaultValue),
|
||||
new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal("hello", contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_SyncMethod_WithArgumentArray_DefaultValueAttributeIgnored()
|
||||
{
|
||||
// Arrange
|
||||
var inputString = "test";
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.EchoWithDefaultValue),
|
||||
new Dictionary<string, object>() { { "input", inputString } });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal(inputString, contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_SyncMethod_WithArgumentDictionary_DefaultParameterValueUsed()
|
||||
{
|
||||
// Arrange
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.EchoWithDefaultValueAndAttribute),
|
||||
new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal("world", contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_SyncMethod_WithArgumentDictionary_AnyValue_HasPrecedenceOverDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var inputString = "test";
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.EchoWithDefaultValueAndAttribute),
|
||||
new Dictionary<string, object>() { { "input", inputString } });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal(inputString, contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_WithCustomTaskReturnTypeThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
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 & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => invoker.InvokeAsync());
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_WithCustomTaskOfTReturnTypeThrows()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
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 & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => invoker.InvokeAsync());
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncAction_ReturningUnwrappedTask()
|
||||
{
|
||||
// Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
var actionParameters = new Dictionary<string, object> { { "i", inputParam1 }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.UnwrappedTask), actionParameters);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
Assert.IsType<EmptyResult>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncMethod_ParametersInRandomOrder()
|
||||
{
|
||||
//Arrange
|
||||
var inputParam1 = 1;
|
||||
var inputParam2 = "Second Parameter";
|
||||
|
||||
// Note that the order of parameters is reversed
|
||||
var actionParameters = new Dictionary<string, object> { { "s", inputParam2 }, { "i", inputParam1 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.TaskValueTypeAction),
|
||||
actionParameters);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<ObjectResult>(result);
|
||||
Assert.Equal(inputParam1, contentResult.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(nameof(TestController.AsynActionMethodWithTestActionResult))]
|
||||
[InlineData(nameof(TestController.ActionMethodWithTestActionResult))]
|
||||
public async Task InvokeAction_ReturnTypeAsIActionResult_ReturnsExpected(string methodName)
|
||||
{
|
||||
//Arrange
|
||||
var inputParam = 1;
|
||||
var actionParameters = new Dictionary<string, object> { { "value", inputParam } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
methodName,
|
||||
actionParameters);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
var contentResult = Assert.IsType<TestActionResult>(result);
|
||||
Assert.Equal(inputParam, contentResult.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AsyncMethod_InvalidParameterValueThrows()
|
||||
{
|
||||
//Arrange
|
||||
var inputParam2 = "Second Parameter";
|
||||
|
||||
var actionParameters = new Dictionary<string, object> { { "i", "Some Invalid Value" }, { "s", inputParam2 } };
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
nameof(TestController.TaskValueTypeAction),
|
||||
actionParameters);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidCastException>(
|
||||
() => invoker.InvokeAsync());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(nameof(TestController.ActionMethodWithNullActionResult), typeof(IActionResult))]
|
||||
[InlineData(nameof(TestController.TestActionMethodWithNullActionResult), typeof(TestActionResult))]
|
||||
[InlineData(nameof(TestController.AsyncActionMethodWithNullActionResult), typeof(IActionResult))]
|
||||
[InlineData(nameof(TestController.AsyncActionMethodWithNullTestActionResult), typeof(TestActionResult))]
|
||||
[ReplaceCulture]
|
||||
public async Task InvokeAction_WithNullActionResultThrows(string methodName, Type resultType)
|
||||
{
|
||||
// Arrange
|
||||
IActionResult result = null;
|
||||
|
||||
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
|
||||
filter
|
||||
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
|
||||
.Callback<ActionExecutedContext>(c => result = c.Result)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new[] { filter.Object },
|
||||
methodName,
|
||||
new Dictionary<string, object>());
|
||||
|
||||
// Act & Assert
|
||||
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
|
||||
() => invoker.InvokeAsync(),
|
||||
"Cannot return null from an action method with a return type of '"
|
||||
+ resultType
|
||||
+ "'.");
|
||||
}
|
||||
|
||||
private TestControllerActionInvoker CreateInvoker(
|
||||
IFilterMetadata filter,
|
||||
bool actionThrows = false,
|
||||
|
|
@ -1967,14 +2381,44 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (actionThrows)
|
||||
{
|
||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ThrowingActionMethod");
|
||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(nameof(ControllerActionInvokerTest.ThrowingActionMethod));
|
||||
}
|
||||
else
|
||||
{
|
||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod");
|
||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(nameof(ControllerActionInvokerTest.ActionMethod));
|
||||
}
|
||||
actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo();
|
||||
|
||||
return CreateInvoker(filters, actionDescriptor, null, null, maxAllowedErrorsInModelState);
|
||||
}
|
||||
|
||||
private TestControllerActionInvoker CreateInvoker(
|
||||
IFilterMetadata[] filters,
|
||||
string methodName,
|
||||
IDictionary<string, object> arguments,
|
||||
int maxAllowedErrorsInModelState = 200)
|
||||
{
|
||||
var actionDescriptor = new ControllerActionDescriptor()
|
||||
{
|
||||
FilterDescriptors = new List<FilterDescriptor>(),
|
||||
Parameters = new List<ParameterDescriptor>(),
|
||||
};
|
||||
|
||||
actionDescriptor.MethodInfo = typeof(TestController).GetMethod(methodName);
|
||||
actionDescriptor.ControllerTypeInfo = typeof(TestController).GetTypeInfo();
|
||||
|
||||
var argumentBinder = new TestControllerArgumentBinder(arguments);
|
||||
|
||||
return CreateInvoker(filters, actionDescriptor, argumentBinder, _controller, maxAllowedErrorsInModelState);
|
||||
}
|
||||
|
||||
private TestControllerActionInvoker CreateInvoker(
|
||||
IFilterMetadata[] filters,
|
||||
ControllerActionDescriptor actionDescriptor,
|
||||
IControllerArgumentBinder controllerArgumentBinder,
|
||||
object controller,
|
||||
int maxAllowedErrorsInModelState = 200)
|
||||
{
|
||||
var httpContext = new Mock<HttpContext>(MockBehavior.Loose);
|
||||
|
||||
var http = GetHttpContext();
|
||||
|
|
@ -2013,6 +2457,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
httpContext
|
||||
.Setup(o => o.RequestServices.GetService(typeof(IOptions<MvcOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
httpContext.SetupGet(c => c.Items)
|
||||
.Returns(new Dictionary<object, object>());
|
||||
|
||||
httpContext
|
||||
.Setup(o => o.RequestServices.GetService(typeof(ObjectResultExecutor)))
|
||||
.Returns(new ObjectResultExecutor(
|
||||
optionsAccessor.Object,
|
||||
new TestHttpResponseStreamWriterFactory(),
|
||||
NullLoggerFactory.Instance));
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
httpContext: httpContext.Object,
|
||||
|
|
@ -2023,27 +2476,37 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
filterProvider
|
||||
.Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>()))
|
||||
.Callback<FilterProviderContext>(context =>
|
||||
{
|
||||
foreach (var filterMetadata in filters)
|
||||
{
|
||||
foreach (var filterMetadata in filters)
|
||||
context.Results.Add(new FilterItem(new FilterDescriptor(filterMetadata, FilterScope.Action))
|
||||
{
|
||||
context.Results.Add(new FilterItem(new FilterDescriptor(filterMetadata, FilterScope.Action))
|
||||
{
|
||||
Filter = filterMetadata,
|
||||
});
|
||||
}
|
||||
});
|
||||
Filter = filterMetadata,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
filterProvider
|
||||
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
||||
.Verifiable();
|
||||
|
||||
var argumentBinder = new Mock<IControllerArgumentBinder>();
|
||||
argumentBinder
|
||||
.Setup(b => b.BindArgumentsAsync(
|
||||
It.IsAny<ControllerContext>(),
|
||||
It.IsAny<object>(),
|
||||
It.IsAny<IDictionary<string, object>>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
IControllerArgumentBinder argumentBinder = null;
|
||||
|
||||
if (controllerArgumentBinder == null)
|
||||
{
|
||||
var mockBinder = new Mock<IControllerArgumentBinder>();
|
||||
mockBinder
|
||||
.Setup(b => b.BindArgumentsAsync(
|
||||
It.IsAny<ControllerContext>(),
|
||||
It.IsAny<object>(),
|
||||
It.IsAny<IDictionary<string, object>>()))
|
||||
.Returns(TaskCache.CompletedTask);
|
||||
argumentBinder = mockBinder.Object;
|
||||
}
|
||||
else
|
||||
{
|
||||
argumentBinder = controllerArgumentBinder;
|
||||
}
|
||||
|
||||
filterProvider
|
||||
.SetupGet(fp => fp.Order)
|
||||
|
|
@ -2051,8 +2514,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
var invoker = new TestControllerActionInvoker(
|
||||
new[] { filterProvider.Object },
|
||||
new MockControllerFactory(this),
|
||||
argumentBinder.Object,
|
||||
new MockControllerFactory(controller ?? this),
|
||||
argumentBinder,
|
||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||
actionContext,
|
||||
|
|
@ -2061,6 +2524,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return invoker;
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_UsesDefaultValuesIfNotBound()
|
||||
{
|
||||
|
|
@ -2168,6 +2632,133 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
return new TestActionResult { Value = value };
|
||||
}
|
||||
|
||||
public TestActionResult ActionMethodWithTestActionResult(int value)
|
||||
{
|
||||
return new TestActionResult { Value = value };
|
||||
}
|
||||
|
||||
public async Task<TestActionResult> AsynActionMethodWithTestActionResult(int value)
|
||||
{
|
||||
return await Task.FromResult<TestActionResult>(new TestActionResult { Value = value });
|
||||
}
|
||||
|
||||
public IActionResult ActionMethodWithNullActionResult()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public TestActionResult TestActionMethodWithNullActionResult()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> AsyncActionMethodWithNullActionResult()
|
||||
{
|
||||
return await Task.FromResult<IActionResult>(null);
|
||||
}
|
||||
|
||||
public async Task<TestActionResult> AsyncActionMethodWithNullTestActionResult()
|
||||
{
|
||||
return await Task.FromResult<TestActionResult>(null);
|
||||
}
|
||||
#pragma warning disable 1998
|
||||
public async Task TaskAction(int i, string s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
#pragma warning disable 1998
|
||||
public async Task<int> TaskValueTypeAction(int i, string s)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
#pragma warning disable 1998
|
||||
public async Task<Task<int>> TaskOfTaskAction(int i, string s)
|
||||
{
|
||||
return TaskValueTypeAction(i, s);
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
public Task<int> TaskValueTypeActionWithoutAsync(int i, string s)
|
||||
{
|
||||
return TaskValueTypeAction(i, s);
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
public async Task<int> TaskActionWithException(int i, string s)
|
||||
{
|
||||
throw new NotImplementedException("Not Implemented Exception");
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
||||
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)
|
||||
{
|
||||
return new TaskDerivedType();
|
||||
}
|
||||
|
||||
public TaskOfTDerivedType<int> TaskActionWithCustomTaskOfTReturnType(int i, string s)
|
||||
{
|
||||
return new TaskOfTDerivedType<int>(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Task{TResult}"/> instead of a <see cref="Task"/>.
|
||||
/// </summary>
|
||||
public Task UnwrappedTask(int i, string s)
|
||||
{
|
||||
return Task.Factory.StartNew(async () => await Task.Factory.StartNew(() => i));
|
||||
}
|
||||
|
||||
public string Echo(string input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
public string EchoWithException(string input)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string EchoWithDefaultValue([DefaultValue("hello")] string input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
public string EchoWithDefaultValueAndAttribute([DefaultValue("hello")] string input = "world")
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
public class TaskDerivedType : Task
|
||||
{
|
||||
public TaskDerivedType()
|
||||
: base(() => Console.WriteLine("In The Constructor"))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TaskOfTDerivedType<T> : Task<T>
|
||||
{
|
||||
public TaskOfTDerivedType(T input)
|
||||
: base(() => input)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestActionResult : IActionResult
|
||||
|
|
@ -2273,5 +2864,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal(_expectedMaxAllowedErrors, context.ModelState.MaxAllowedErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestControllerArgumentBinder : IControllerArgumentBinder
|
||||
{
|
||||
private readonly IDictionary<string, object> _actionParameters;
|
||||
public TestControllerArgumentBinder(IDictionary<string, object> actionParameters)
|
||||
{
|
||||
_actionParameters = actionParameters;
|
||||
}
|
||||
|
||||
public Task BindArgumentsAsync(
|
||||
ControllerContext controllerContext,
|
||||
object controller,
|
||||
IDictionary<string, object> arguments)
|
||||
{
|
||||
foreach (var entry in _actionParameters)
|
||||
{
|
||||
arguments.Add(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
return TaskCache.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,26 +69,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
new object[] { parameter }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void ExecuteValueMethodUsingAsyncMethod()
|
||||
{
|
||||
var executor = GetExecutorForMethod("ValueMethod");
|
||||
var result = await executor.ExecuteAsync(
|
||||
_targetObject,
|
||||
new object[] { 10, 20 });
|
||||
Assert.Equal(30, (int)result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void ExecuteVoidValueMethodUsingAsyncMethod()
|
||||
{
|
||||
var executor = GetExecutorForMethod("VoidValueMethod");
|
||||
var result = await executor.ExecuteAsync(
|
||||
_targetObject,
|
||||
new object[] { 10 });
|
||||
Assert.Same(null, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void ExecuteValueMethodAsync()
|
||||
{
|
||||
|
|
@ -99,16 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.Equal(30, (int)result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void ExecuteVoidValueMethodAsync()
|
||||
{
|
||||
var executor = GetExecutorForMethod("VoidValueMethodAsync");
|
||||
var result = await executor.ExecuteAsync(
|
||||
_targetObject,
|
||||
new object[] { 10 });
|
||||
Assert.Same(null, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void ExecuteValueMethodWithReturnTypeAsync()
|
||||
{
|
||||
|
|
@ -143,33 +113,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
new object[] { parameter }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecuteMethodOfTaskDerivedTypeReturnTypeThrowsException()
|
||||
{
|
||||
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(TestObject));
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => GetExecutorForMethod("TaskActionWithCustomTaskReturnType"));
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecuteMethodOfTaskDerivedTypeOfTReturnTypeThrowsException()
|
||||
{
|
||||
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(TestObject));
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => GetExecutorForMethod("TaskActionWithCustomTaskOfTReturnType"));
|
||||
|
||||
Assert.Equal(expectedException, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("EchoWithDefaultAttributes", new object[] { "hello", true, 10 })]
|
||||
[InlineData("EchoWithDefaultValues", new object[] { "hello", true, 20 })]
|
||||
|
|
@ -186,7 +129,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
Assert.Equal(expectedValues, defaultValues);
|
||||
|
||||
}
|
||||
|
||||
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
|
||||
|
|
@ -197,16 +139,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
public class TestObject
|
||||
{
|
||||
{
|
||||
public string value;
|
||||
public int ValueMethod(int i, int j)
|
||||
{
|
||||
return i+j;
|
||||
return i + j;
|
||||
}
|
||||
|
||||
public void VoidValueMethod(int i)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
public TestObject ValueMethodWithReturnType(int i)
|
||||
{
|
||||
|
|
@ -249,16 +191,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return Task.FromResult<TestObject>(parameter);
|
||||
}
|
||||
|
||||
public TaskDerivedType TaskActionWithCustomTaskReturnType(int i, string s)
|
||||
{
|
||||
return new TaskDerivedType();
|
||||
}
|
||||
|
||||
public TaskOfTDerivedType<int> TaskActionWithCustomTaskOfTReturnType(int i, string s)
|
||||
{
|
||||
return new TaskOfTDerivedType<int>(1);
|
||||
}
|
||||
|
||||
public string EchoWithDefaultAttributes(
|
||||
[DefaultValue("hello")] string input1,
|
||||
[DefaultValue(true)] bool input2,
|
||||
|
|
@ -283,29 +215,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
public string EchoWithNoDefaultAttributesAndValues(
|
||||
string input1,
|
||||
int input2,
|
||||
string input1,
|
||||
int input2,
|
||||
bool input3,
|
||||
TestObject input4)
|
||||
{
|
||||
return input1;
|
||||
}
|
||||
|
||||
public class TaskDerivedType : Task
|
||||
{
|
||||
public TaskDerivedType()
|
||||
: base(() => Console.WriteLine("In The Constructor"))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TaskOfTDerivedType<T> : Task<T>
|
||||
{
|
||||
public TaskOfTDerivedType(T input)
|
||||
: base(() => input)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue