[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 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(
|
public static object[] PrepareArguments(
|
||||||
IDictionary<string, object> actionParameters,
|
IDictionary<string, object> actionParameters,
|
||||||
ObjectMethodExecutor actionMethodExecutor)
|
ObjectMethodExecutor actionMethodExecutor)
|
||||||
|
|
|
||||||
|
|
@ -573,12 +573,67 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
_logger.ActionMethodExecuting(_actionExecutingContext, arguments);
|
_logger.ActionMethodExecuting(_actionExecutingContext, arguments);
|
||||||
|
|
||||||
var actionReturnValue = await ControllerActionExecutor.ExecuteAsync(
|
var returnType = _executor.MethodReturnType;
|
||||||
_executor,
|
|
||||||
_controller,
|
|
||||||
arguments);
|
|
||||||
|
|
||||||
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);
|
_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>
|
/// <summary>
|
||||||
/// A one-way cursor for filters.
|
/// A one-way cursor for filters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -22,25 +22,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
private static readonly MethodInfo _convertOfTMethod =
|
private static readonly MethodInfo _convertOfTMethod =
|
||||||
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert));
|
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert));
|
||||||
|
|
||||||
private static readonly Expression<Func<object, Task<object>>> _createTaskFromResultExpression =
|
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
||||||
((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)
|
|
||||||
{
|
{
|
||||||
if (methodInfo == null)
|
if (methodInfo == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(methodInfo));
|
throw new ArgumentNullException(nameof(methodInfo));
|
||||||
}
|
}
|
||||||
MethodInfo = methodInfo;
|
MethodInfo = methodInfo;
|
||||||
|
TargetTypeInfo = targetTypeInfo;
|
||||||
ActionParameters = methodInfo.GetParameters();
|
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);
|
private delegate Task<object> ActionExecutorAsync(object target, object[] parameters);
|
||||||
|
|
@ -53,17 +47,40 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
public ParameterInfo[] ActionParameters { 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; }
|
||||||
|
|
||||||
|
private ActionExecutorAsync TaskOfTActionExecutorAsync
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_executorAsync == null)
|
||||||
|
{
|
||||||
|
_executorAsync = GetExecutorAsync(TaskGenericType, MethodInfo, TargetTypeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _executorAsync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
|
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._executor = GetExecutor(methodInfo, targetTypeInfo);
|
||||||
executor._executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo);
|
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<object> ExecuteAsync(object target, object[] parameters)
|
public Task<object> ExecuteAsync(object target, object[] parameters)
|
||||||
{
|
{
|
||||||
return _executorAsync(target, parameters);
|
return TaskOfTActionExecutorAsync(target, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Execute(object target, object[] 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
|
// Parameters to executor
|
||||||
var targetParameter = Expression.Parameter(typeof(object), "target");
|
var targetParameter = Expression.Parameter(typeof(object), "target");
|
||||||
|
|
@ -155,83 +172,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
|
var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType());
|
||||||
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
|
var methodCall = Expression.Call(instanceCast, methodInfo, parameters);
|
||||||
|
|
||||||
// methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)"
|
var coerceMethodCall = GetCoerceMethodCallExpression(taskInnerType, methodCall, methodInfo);
|
||||||
// Create function
|
var lambda = Expression.Lambda<ActionExecutorAsync>(coerceMethodCall, targetParameter, parametersParameter);
|
||||||
if (methodCall.Type == typeof(void))
|
return lambda.Compile();
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to CoerceResult as the object value returned from methodInfo.Invoke has to be cast to a Task<T>.
|
// 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.
|
// 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.
|
// 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.
|
// 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 castMethodCall = Expression.Convert(methodCall, typeof(object));
|
||||||
var returnType = methodCall.Type;
|
// for: public Task<T> Action()
|
||||||
|
// constructs: return (Task<object>)Convert<T>((Task<T>)result)
|
||||||
if (typeof(Task).IsAssignableFrom(returnType))
|
var genericMethodInfo = _convertOfTMethod.MakeGenericMethod(taskValueType);
|
||||||
{
|
var genericMethodCall = Expression.Call(null, genericMethodInfo, castMethodCall);
|
||||||
if (returnType == typeof(Task))
|
var convertedResult = Expression.Convert(genericMethodCall, typeof(Task<object>));
|
||||||
{
|
return convertedResult;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -71,48 +71,64 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var methodInfo = context.ViewComponentDescriptor?.MethodInfo;
|
var executor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
||||||
if (methodInfo == null)
|
|
||||||
|
var returnType = executor.MethodReturnType;
|
||||||
|
|
||||||
|
if (returnType == typeof(void) || returnType == typeof(Task))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
|
throw new InvalidOperationException(Resources.ViewComponent_MustReturnValue);
|
||||||
nameof(ViewComponentDescriptor.MethodInfo),
|
|
||||||
nameof(ViewComponentDescriptor)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isAsync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType);
|
|
||||||
IViewComponentResult result;
|
IViewComponentResult result;
|
||||||
if (isAsync)
|
if (executor.IsMethodAsync)
|
||||||
{
|
{
|
||||||
result = await InvokeAsyncCore(context);
|
result = await InvokeAsyncCore(executor, context);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
// We support falling back to synchronous if there is no InvokeAsync method, in this case we'll still
|
||||||
// execute the IViewResult asynchronously.
|
// execute the IViewResult asynchronously.
|
||||||
result = InvokeSyncCore(context);
|
result = InvokeSyncCore(executor, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
await result.ExecuteAsync(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);
|
var component = _viewComponentFactory.CreateViewComponent(context);
|
||||||
|
|
||||||
using (_logger.ViewComponentScope(context))
|
using (_logger.ViewComponentScope(context))
|
||||||
{
|
{
|
||||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, executor);
|
||||||
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
|
||||||
|
|
||||||
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, methodExecutor);
|
|
||||||
|
|
||||||
_diagnosticSource.BeforeViewComponent(context, component);
|
_diagnosticSource.BeforeViewComponent(context, component);
|
||||||
_logger.ViewComponentExecuting(context, arguments);
|
_logger.ViewComponentExecuting(context, arguments);
|
||||||
|
|
||||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
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);
|
_logger.ViewComponentExecuted(context, startTimestamp, viewComponentResult);
|
||||||
_diagnosticSource.AfterViewComponent(context, viewComponentResult, component);
|
_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);
|
var component = _viewComponentFactory.CreateViewComponent(context);
|
||||||
|
|
||||||
using (_logger.ViewComponentScope(context))
|
using (_logger.ViewComponentScope(context))
|
||||||
{
|
{
|
||||||
var method = context.ViewComponentDescriptor.MethodInfo;
|
|
||||||
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
|
|
||||||
|
|
||||||
var arguments = ControllerActionExecutor.PrepareArguments(
|
var arguments = ControllerActionExecutor.PrepareArguments(
|
||||||
context.Arguments,
|
context.Arguments,
|
||||||
methodExecutor);
|
executor);
|
||||||
|
|
||||||
_diagnosticSource.BeforeViewComponent(context, component);
|
_diagnosticSource.BeforeViewComponent(context, component);
|
||||||
_logger.ViewComponentExecuting(context, arguments);
|
_logger.ViewComponentExecuting(context, arguments);
|
||||||
|
|
||||||
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0;
|
||||||
object result;
|
object result;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = methodExecutor.Execute(component, arguments);
|
result = executor.Execute(component, arguments);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 System.Collections.Concurrent;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||||
|
|
@ -46,6 +47,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
return executor;
|
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);
|
executor = ObjectMethodExecutor.Create(viewComponentDescriptor.MethodInfo, viewComponentDescriptor.TypeInfo);
|
||||||
|
|
||||||
cache.Entries.TryAdd(viewComponentDescriptor, executor);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -33,10 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
private readonly ContentResult _result = new ContentResult() { Content = "Hello, world!" };
|
private readonly ContentResult _result = new ContentResult() { Content = "Hello, world!" };
|
||||||
|
|
||||||
private struct SampleStruct
|
private readonly TestController _controller = new TestController();
|
||||||
{
|
|
||||||
public int x;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow()
|
public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow()
|
||||||
|
|
@ -1863,73 +1862,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.False(invoker.ControllerFactory.CreateCalled);
|
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]
|
[Fact]
|
||||||
public async Task MaxAllowedErrorsIsSet_BeforeCallingAuthorizationFilter()
|
public async Task MaxAllowedErrorsIsSet_BeforeCallingAuthorizationFilter()
|
||||||
{
|
{
|
||||||
|
|
@ -1946,6 +1878,488 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
await invoker.InvokeAsync();
|
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(
|
private TestControllerActionInvoker CreateInvoker(
|
||||||
IFilterMetadata filter,
|
IFilterMetadata filter,
|
||||||
bool actionThrows = false,
|
bool actionThrows = false,
|
||||||
|
|
@ -1967,14 +2381,44 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
if (actionThrows)
|
if (actionThrows)
|
||||||
{
|
{
|
||||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ThrowingActionMethod");
|
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(nameof(ControllerActionInvokerTest.ThrowingActionMethod));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod");
|
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod(nameof(ControllerActionInvokerTest.ActionMethod));
|
||||||
}
|
}
|
||||||
actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo();
|
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 httpContext = new Mock<HttpContext>(MockBehavior.Loose);
|
||||||
|
|
||||||
var http = GetHttpContext();
|
var http = GetHttpContext();
|
||||||
|
|
@ -2013,6 +2457,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
httpContext
|
httpContext
|
||||||
.Setup(o => o.RequestServices.GetService(typeof(IOptions<MvcOptions>)))
|
.Setup(o => o.RequestServices.GetService(typeof(IOptions<MvcOptions>)))
|
||||||
.Returns(optionsAccessor.Object);
|
.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(
|
var actionContext = new ActionContext(
|
||||||
httpContext: httpContext.Object,
|
httpContext: httpContext.Object,
|
||||||
|
|
@ -2023,27 +2476,37 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
filterProvider
|
filterProvider
|
||||||
.Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>()))
|
.Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>()))
|
||||||
.Callback<FilterProviderContext>(context =>
|
.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
|
filterProvider
|
||||||
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var argumentBinder = new Mock<IControllerArgumentBinder>();
|
IControllerArgumentBinder argumentBinder = null;
|
||||||
argumentBinder
|
|
||||||
.Setup(b => b.BindArgumentsAsync(
|
if (controllerArgumentBinder == null)
|
||||||
It.IsAny<ControllerContext>(),
|
{
|
||||||
It.IsAny<object>(),
|
var mockBinder = new Mock<IControllerArgumentBinder>();
|
||||||
It.IsAny<IDictionary<string, object>>()))
|
mockBinder
|
||||||
.Returns(TaskCache.CompletedTask);
|
.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
|
filterProvider
|
||||||
.SetupGet(fp => fp.Order)
|
.SetupGet(fp => fp.Order)
|
||||||
|
|
@ -2051,8 +2514,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
var invoker = new TestControllerActionInvoker(
|
var invoker = new TestControllerActionInvoker(
|
||||||
new[] { filterProvider.Object },
|
new[] { filterProvider.Object },
|
||||||
new MockControllerFactory(this),
|
new MockControllerFactory(controller ?? this),
|
||||||
argumentBinder.Object,
|
argumentBinder,
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||||
actionContext,
|
actionContext,
|
||||||
|
|
@ -2061,6 +2524,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return invoker;
|
return invoker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Invoke_UsesDefaultValuesIfNotBound()
|
public async Task Invoke_UsesDefaultValuesIfNotBound()
|
||||||
{
|
{
|
||||||
|
|
@ -2168,6 +2632,133 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
return new TestActionResult { Value = value };
|
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
|
private sealed class TestActionResult : IActionResult
|
||||||
|
|
@ -2273,5 +2864,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Equal(_expectedMaxAllowedErrors, context.ModelState.MaxAllowedErrors);
|
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 }));
|
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]
|
[Fact]
|
||||||
public async void ExecuteValueMethodAsync()
|
public async void ExecuteValueMethodAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -99,16 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
Assert.Equal(30, (int)result);
|
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]
|
[Fact]
|
||||||
public async void ExecuteValueMethodWithReturnTypeAsync()
|
public async void ExecuteValueMethodWithReturnTypeAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -143,33 +113,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new object[] { parameter }));
|
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]
|
[Theory]
|
||||||
[InlineData("EchoWithDefaultAttributes", new object[] { "hello", true, 10 })]
|
[InlineData("EchoWithDefaultAttributes", new object[] { "hello", true, 10 })]
|
||||||
[InlineData("EchoWithDefaultValues", new object[] { "hello", true, 20 })]
|
[InlineData("EchoWithDefaultValues", new object[] { "hello", true, 20 })]
|
||||||
|
|
@ -186,7 +129,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(expectedValues, defaultValues);
|
Assert.Equal(expectedValues, defaultValues);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
|
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
|
||||||
|
|
@ -197,16 +139,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestObject
|
public class TestObject
|
||||||
{
|
{
|
||||||
public string value;
|
public string value;
|
||||||
public int ValueMethod(int i, int j)
|
public int ValueMethod(int i, int j)
|
||||||
{
|
{
|
||||||
return i+j;
|
return i + j;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void VoidValueMethod(int i)
|
public void VoidValueMethod(int i)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public TestObject ValueMethodWithReturnType(int i)
|
public TestObject ValueMethodWithReturnType(int i)
|
||||||
{
|
{
|
||||||
|
|
@ -249,16 +191,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return Task.FromResult<TestObject>(parameter);
|
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(
|
public string EchoWithDefaultAttributes(
|
||||||
[DefaultValue("hello")] string input1,
|
[DefaultValue("hello")] string input1,
|
||||||
[DefaultValue(true)] bool input2,
|
[DefaultValue(true)] bool input2,
|
||||||
|
|
@ -283,29 +215,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
public string EchoWithNoDefaultAttributesAndValues(
|
public string EchoWithNoDefaultAttributesAndValues(
|
||||||
string input1,
|
string input1,
|
||||||
int input2,
|
int input2,
|
||||||
bool input3,
|
bool input3,
|
||||||
TestObject input4)
|
TestObject input4)
|
||||||
{
|
{
|
||||||
return input1;
|
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