parent
96bd2769d0
commit
f0a5af2ba7
|
|
@ -0,0 +1,211 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
internal abstract class ActionMethodExecutor
|
||||
{
|
||||
private static readonly ActionMethodExecutor[] Executors = new ActionMethodExecutor[]
|
||||
{
|
||||
// Executors for sync methods
|
||||
new VoidResultExecutor(),
|
||||
new SyncActionResultExecutor(),
|
||||
new SyncObjectResultExecutor(),
|
||||
|
||||
// Executors for async methods
|
||||
new AwaitableResultExecutor(),
|
||||
new TaskResultExecutor(),
|
||||
new TaskOfIActionResultExecutor(),
|
||||
new TaskOfActionResultExecutor(),
|
||||
new AwaitableObjectResultExecutor(),
|
||||
};
|
||||
|
||||
public abstract ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments);
|
||||
|
||||
protected abstract bool CanExecute(ObjectMethodExecutor executor);
|
||||
|
||||
public static ActionMethodExecutor GetExecutor(ObjectMethodExecutor executor)
|
||||
{
|
||||
for (var i = 0; i < Executors.Length; i++)
|
||||
{
|
||||
if (Executors[i].CanExecute(executor))
|
||||
{
|
||||
return Executors[i];
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Fail("Should not get here");
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
// void LogMessage(..)
|
||||
private class VoidResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
executor.Execute(controller, arguments);
|
||||
return new ValueTask<IActionResult>(new EmptyResult());
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor)
|
||||
=> !executor.IsMethodAsync && executor.MethodReturnType == typeof(void);
|
||||
}
|
||||
|
||||
// IActionResult Post(..)
|
||||
// CreatedAtResult Put(..)
|
||||
private class SyncActionResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
var actionResult = (IActionResult)executor.Execute(controller, arguments);
|
||||
EnsureActionResultNotNull(executor, actionResult);
|
||||
|
||||
return new ValueTask<IActionResult>(actionResult);
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor)
|
||||
=> !executor.IsMethodAsync && typeof(IActionResult).IsAssignableFrom(executor.MethodReturnType);
|
||||
}
|
||||
|
||||
// Person GetPerson(..)
|
||||
// object Index(..)
|
||||
private class SyncObjectResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
// Sync method returning arbitrary object
|
||||
var returnValue = executor.Execute(controller, arguments);
|
||||
var actionResult = ConvertToActionResult(returnValue, executor.MethodReturnType);
|
||||
return new ValueTask<IActionResult>(actionResult);
|
||||
}
|
||||
|
||||
// Catch-all for sync methods
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor) => !executor.IsMethodAsync;
|
||||
}
|
||||
|
||||
// Task SaveState(..)
|
||||
private class TaskResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
await (Task)executor.Execute(controller, arguments);
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor) => executor.MethodReturnType == typeof(Task);
|
||||
}
|
||||
|
||||
// CustomAsync PerformActionAsync(..)
|
||||
// Custom task-like type with no return value.
|
||||
private class AwaitableResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
await executor.ExecuteAsync(controller, arguments);
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor)
|
||||
{
|
||||
// Async method returning void
|
||||
return executor.IsMethodAsync && executor.AsyncResultType == typeof(void);
|
||||
}
|
||||
}
|
||||
|
||||
// Task<IActionResult> Post(..)
|
||||
private class TaskOfIActionResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
// Async method returning Task<IActionResult>
|
||||
// Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task<IActionResult>.
|
||||
var returnValue = executor.Execute(controller, arguments);
|
||||
var actionResult = await (Task<IActionResult>)returnValue;
|
||||
EnsureActionResultNotNull(executor, actionResult);
|
||||
|
||||
return actionResult;
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor)
|
||||
=> typeof(Task<IActionResult>).IsAssignableFrom(executor.MethodReturnType);
|
||||
}
|
||||
|
||||
// Task<PhysicalfileResult> DownloadFile(..)
|
||||
// ValueTask<ViewResult> GetViewsAsync(..)
|
||||
private class TaskOfActionResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
// Async method returning awaitable-of-IActionResult (e.g., Task<ViewResult>)
|
||||
// We have to use ExecuteAsync because we don't know the awaitable's type at compile time.
|
||||
var actionResult = (IActionResult)await executor.ExecuteAsync(controller, arguments);
|
||||
EnsureActionResultNotNull(executor, actionResult);
|
||||
return actionResult;
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor)
|
||||
{
|
||||
// Async method returning awaitable-of - IActionResult(e.g., Task<ViewResult>)
|
||||
return executor.IsMethodAsync && typeof(IActionResult).IsAssignableFrom(executor.AsyncResultType);
|
||||
}
|
||||
}
|
||||
|
||||
// Task<object> GetPerson(..)
|
||||
// Task<Customer> GetCustomerAsync(..)
|
||||
private class AwaitableObjectResultExecutor : ActionMethodExecutor
|
||||
{
|
||||
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments)
|
||||
{
|
||||
// Async method returning awaitable-of-nonvoid
|
||||
var returnValue = await executor.ExecuteAsync(controller, arguments);
|
||||
var actionResult = ConvertToActionResult(returnValue, executor.MethodReturnType);
|
||||
return actionResult;
|
||||
}
|
||||
|
||||
protected override bool CanExecute(ObjectMethodExecutor executor) => true;
|
||||
}
|
||||
|
||||
private static void EnsureActionResultNotNull(ObjectMethodExecutor executor, IActionResult actionResult)
|
||||
{
|
||||
if (actionResult == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(executor.AsyncResultType ?? executor.MethodReturnType));
|
||||
}
|
||||
}
|
||||
|
||||
private static IActionResult ConvertToActionResult(object returnValue, Type declaredType)
|
||||
{
|
||||
IActionResult result;
|
||||
switch (returnValue)
|
||||
{
|
||||
case IActionResult actionResult:
|
||||
result = actionResult;
|
||||
break;
|
||||
case IConvertToActionResult convertToActionResult:
|
||||
result = convertToActionResult.Convert();
|
||||
break;
|
||||
default:
|
||||
result = new ObjectResult(returnValue)
|
||||
{
|
||||
DeclaredType = declaredType,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(declaredType));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
private readonly ControllerActionInvokerCacheEntry _cacheEntry;
|
||||
private readonly ControllerContext _controllerContext;
|
||||
|
||||
|
||||
private Dictionary<string, object> _arguments;
|
||||
|
||||
private ActionExecutingContext _actionExecutingContext;
|
||||
|
|
@ -305,14 +305,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
private async Task InvokeActionMethodAsync()
|
||||
{
|
||||
var controllerContext = _controllerContext;
|
||||
var executor = _cacheEntry.ActionMethodExecutor;
|
||||
var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
|
||||
var controller = _instance;
|
||||
var arguments = _arguments;
|
||||
var orderedArguments = PrepareArguments(arguments, executor);
|
||||
var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
|
||||
var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);
|
||||
|
||||
var diagnosticSource = _diagnosticSource;
|
||||
var logger = _logger;
|
||||
var returnType = executor.MethodReturnType;
|
||||
|
||||
IActionResult result = null;
|
||||
try
|
||||
|
|
@ -323,92 +323,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
controller);
|
||||
logger.ActionMethodExecuting(controllerContext, orderedArguments);
|
||||
|
||||
if (returnType == typeof(void))
|
||||
var actionResultValueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments);
|
||||
if (actionResultValueTask.IsCompletedSuccessfully)
|
||||
{
|
||||
// Sync method returning void
|
||||
executor.Execute(controller, orderedArguments);
|
||||
result = new EmptyResult();
|
||||
}
|
||||
else if (returnType == typeof(Task))
|
||||
{
|
||||
// Async method returning Task
|
||||
// Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task.
|
||||
await (Task)executor.Execute(controller, orderedArguments);
|
||||
result = new EmptyResult();
|
||||
}
|
||||
else if (returnType == typeof(Task<IActionResult>))
|
||||
{
|
||||
// Async method returning Task<IActionResult>
|
||||
// Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task<IActionResult>.
|
||||
result = await (Task<IActionResult>)executor.Execute(controller, orderedArguments);
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
|
||||
}
|
||||
}
|
||||
else if (IsConvertibleToActionResult(executor))
|
||||
{
|
||||
IConvertToActionResult convertToActionResult;
|
||||
if (executor.IsMethodAsync)
|
||||
{
|
||||
// Async method returning awaitable-of-ActionResult<T> (e.g., Task<ActionResult<Person>>)
|
||||
// We have to use ExecuteAsync because we don't know the awaitable's type at compile time.
|
||||
convertToActionResult = (IConvertToActionResult)await executor.ExecuteAsync(controller, orderedArguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sync method returning ActionResult<T>
|
||||
convertToActionResult = (IConvertToActionResult)executor.Execute(controller, orderedArguments);
|
||||
}
|
||||
|
||||
result = convertToActionResult.Convert();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IConvertToActionResult)));
|
||||
}
|
||||
|
||||
}
|
||||
else if (IsResultIActionResult(executor))
|
||||
{
|
||||
if (executor.IsMethodAsync)
|
||||
{
|
||||
// Async method returning awaitable-of-IActionResult (e.g., Task<ViewResult>)
|
||||
// We have to use ExecuteAsync because we don't know the awaitable's type at compile time.
|
||||
result = (IActionResult)await executor.ExecuteAsync(controller, orderedArguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sync method returning IActionResult (e.g., ViewResult)
|
||||
result = (IActionResult)executor.Execute(controller, orderedArguments);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatActionResult_ActionReturnValueCannotBeNull(executor.AsyncResultType ?? returnType));
|
||||
}
|
||||
}
|
||||
else if (!executor.IsMethodAsync)
|
||||
{
|
||||
// Sync method returning arbitrary object
|
||||
var resultAsObject = executor.Execute(controller, orderedArguments);
|
||||
ConvertToActionResult(resultAsObject);
|
||||
|
||||
}
|
||||
else if (executor.AsyncResultType == typeof(void))
|
||||
{
|
||||
// Async method returning awaitable-of-void
|
||||
await executor.ExecuteAsync(controller, orderedArguments);
|
||||
result = new EmptyResult();
|
||||
result = actionResultValueTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Async method returning awaitable-of-nonvoid
|
||||
var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
|
||||
ConvertToActionResult(resultAsObject);
|
||||
result = await actionResultValueTask;
|
||||
}
|
||||
|
||||
_result = result;
|
||||
|
|
@ -422,25 +344,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
controllerContext,
|
||||
result);
|
||||
}
|
||||
|
||||
void ConvertToActionResult(object resultAsObject)
|
||||
{
|
||||
if (resultAsObject is IActionResult actionResult)
|
||||
{
|
||||
result = actionResult;
|
||||
}
|
||||
else if (resultAsObject is IConvertToActionResult convertToActionResult)
|
||||
{
|
||||
result = convertToActionResult.Convert();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new ObjectResult(resultAsObject)
|
||||
{
|
||||
DeclaredType = returnType,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsResultIActionResult(ObjectMethodExecutor executor)
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var parameterDefaultValues = ParameterDefaultValues
|
||||
.GetParameterDefaultValues(actionDescriptor.MethodInfo);
|
||||
|
||||
var executor = ObjectMethodExecutor.Create(
|
||||
var objectMethodExecutor = ObjectMethodExecutor.Create(
|
||||
actionDescriptor.MethodInfo,
|
||||
actionDescriptor.ControllerTypeInfo,
|
||||
parameterDefaultValues);
|
||||
|
|
@ -84,12 +84,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
_modelMetadataProvider,
|
||||
actionDescriptor);
|
||||
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
cacheEntry = new ControllerActionInvokerCacheEntry(
|
||||
filterFactoryResult.CacheableFilters,
|
||||
controllerFactory,
|
||||
controllerReleaser,
|
||||
propertyBinderFactory,
|
||||
executor);
|
||||
objectMethodExecutor,
|
||||
actionMethodExecutor);
|
||||
cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Func<ControllerContext, object> controllerFactory,
|
||||
Action<ControllerContext, object> controllerReleaser,
|
||||
ControllerBinderDelegate controllerBinderDelegate,
|
||||
ObjectMethodExecutor actionMethodExecutor)
|
||||
ObjectMethodExecutor objectMethodExecutor,
|
||||
ActionMethodExecutor actionMethodExecutor)
|
||||
{
|
||||
ControllerFactory = controllerFactory;
|
||||
ControllerReleaser = controllerReleaser;
|
||||
ControllerBinderDelegate = controllerBinderDelegate;
|
||||
CachedFilters = cachedFilters;
|
||||
ObjectMethodExecutor = objectMethodExecutor;
|
||||
ActionMethodExecutor = actionMethodExecutor;
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
public ControllerBinderDelegate ControllerBinderDelegate { get; }
|
||||
|
||||
internal ObjectMethodExecutor ActionMethodExecutor { get; }
|
||||
internal ObjectMethodExecutor ObjectMethodExecutor { get; }
|
||||
|
||||
internal ActionMethodExecutor ActionMethodExecutor { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute</Description>
|
|||
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.SecurityHelper.Sources" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,313 @@
|
|||
// 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.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Core.Internal
|
||||
{
|
||||
public class ActionMethodExecutorTest
|
||||
{
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesVoidActions()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.VoidAction));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.True(controller.Executed);
|
||||
Assert.IsType<EmptyResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturningIActionResult()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResult));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.True(valueTask.IsCompleted);
|
||||
Assert.IsType<ContentResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturningSubTypeOfActionResult()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsIActionResultSubType));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.IsType<PartialViewResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturningActionResultOfT()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsActionResultOfT));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
var result = Assert.IsType<ObjectResult>(valueTask.Result);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.IsType<TestModel>(result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturningModelAsModel()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsModel));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
var result = Assert.IsType<ObjectResult>(valueTask.Result);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.IsType<TestModel>(result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturningModelAsObject()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnModelAsObject));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
var result = Assert.IsType<ObjectResult>(valueTask.Result);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.IsType<TestModel>(result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturningActionResultAsObject()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsIActionResultSubType));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.IsType<PartialViewResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsReturnTask()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsTask));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.True(controller.Executed);
|
||||
Assert.IsType<EmptyResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutorExecutesActionsAsynchronouslyReturningIActionResult()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsync));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ViewResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningActionResultSubType()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsync));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
await valueTask;
|
||||
Assert.IsType<ViewResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningModel()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsModelAsync));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
var result = Assert.IsType<ObjectResult>(valueTask.Result);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.IsType<TestModel>(result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningModelAsObject()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsObjectAsync));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
var result = Assert.IsType<ObjectResult>(valueTask.Result);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.IsType<TestModel>(result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningIActionResultAsObject()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsObjectAsync));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
Assert.IsType<OkResult>(valueTask.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningActionResultOfT()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnActionResultOFTAsync));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act
|
||||
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>());
|
||||
|
||||
// Assert
|
||||
var result = Assert.IsType<ObjectResult>(valueTask.Result);
|
||||
Assert.NotNull(result.Value);
|
||||
Assert.IsType<TestModel>(result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActionMethodExecutor_ThrowsIfIConvertFromIActionResult_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var controller = new TestController();
|
||||
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsCustomConvertibleFromIActionResult));
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()));
|
||||
|
||||
Assert.Equal($"Cannot return null from an action method with a return type of '{typeof(CustomConvertibleFromAction)}'.", ex.Message);
|
||||
}
|
||||
|
||||
private static ObjectMethodExecutor GetExecutor(string methodName)
|
||||
{
|
||||
var type = typeof(TestController);
|
||||
var methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
|
||||
Assert.NotNull(methodInfo);
|
||||
return ObjectMethodExecutor.Create(methodInfo, type.GetTypeInfo());
|
||||
}
|
||||
|
||||
private class TestController
|
||||
{
|
||||
public bool Executed { get; set; }
|
||||
|
||||
public void VoidAction() => Executed = true;
|
||||
|
||||
public IActionResult ReturnIActionResult() => new ContentResult();
|
||||
|
||||
public PartialViewResult ReturnsIActionResultSubType() => new PartialViewResult();
|
||||
|
||||
public ActionResult<TestModel> ReturnsActionResultOfT() => new ActionResult<TestModel>(new TestModel());
|
||||
|
||||
public CustomConvertibleFromAction ReturnsCustomConvertibleFromIActionResult() => new CustomConvertibleFromAction();
|
||||
|
||||
public TestModel ReturnsModelAsModel() => new TestModel();
|
||||
|
||||
public object ReturnModelAsObject() => new TestModel();
|
||||
|
||||
public object ReturnIActionResultAsObject() => new RedirectResult("/foo");
|
||||
|
||||
public Task ReturnsTask()
|
||||
{
|
||||
Executed = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IActionResult> ReturnIActionResultAsync() => Task.FromResult((IActionResult)new ViewResult());
|
||||
|
||||
public Task<ViewResult> ReturnsIActionResultSubTypeAsync() => Task.FromResult(new ViewResult());
|
||||
|
||||
public Task<TestModel> ReturnsModelAsModelAsync() => Task.FromResult(new TestModel());
|
||||
|
||||
public Task<object> ReturnsModelAsObjectAsync() => Task.FromResult((object)new TestModel());
|
||||
|
||||
public Task<object> ReturnIActionResultAsObjectAsync() => Task.FromResult((object)new OkResult());
|
||||
|
||||
public Task<ActionResult<TestModel>> ReturnActionResultOFTAsync() => Task.FromResult(new ActionResult<TestModel>(new TestModel()));
|
||||
}
|
||||
|
||||
private class TestModel
|
||||
{
|
||||
}
|
||||
|
||||
private class CustomConvertibleFromAction : IConvertToActionResult
|
||||
{
|
||||
public IActionResult Convert() => null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1370,12 +1370,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionDescriptor.ControllerTypeInfo,
|
||||
ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo));
|
||||
|
||||
var controllerMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
var cacheEntry = new ControllerActionInvokerCacheEntry(
|
||||
new FilterItem[0],
|
||||
_ => new TestController(),
|
||||
(_, __) => { },
|
||||
(_, __, ___) => Task.CompletedTask,
|
||||
actionMethodExecutor: objectMethodExecutor);
|
||||
objectMethodExecutor,
|
||||
controllerMethodExecutor);
|
||||
|
||||
var invoker = new ControllerActionInvoker(
|
||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||
|
|
@ -1632,6 +1635,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
actionDescriptor.ControllerTypeInfo,
|
||||
ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo));
|
||||
|
||||
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
|
||||
|
||||
var cacheEntry = new ControllerActionInvokerCacheEntry(
|
||||
new FilterItem[0],
|
||||
(c) => controller,
|
||||
|
|
@ -1645,7 +1650,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
objectMethodExecutor);
|
||||
objectMethodExecutor,
|
||||
actionMethodExecutor);
|
||||
|
||||
var actionContext = new ActionContext(httpContext, routeData, actionDescriptor);
|
||||
var controllerContext = new ControllerContext(actionContext)
|
||||
|
|
|
|||
|
|
@ -430,12 +430,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
ControllerActionDescriptor actionDescriptor,
|
||||
MockControllerFactory controllerFactory)
|
||||
{
|
||||
var objectMethodExecutor = CreateExecutor(actionDescriptor);
|
||||
return new ControllerActionInvokerCacheEntry(
|
||||
new FilterItem[0],
|
||||
controllerFactory.CreateController,
|
||||
controllerFactory.ReleaseController,
|
||||
null,
|
||||
CreateExecutor(actionDescriptor));
|
||||
objectMethodExecutor,
|
||||
ActionMethodExecutor.GetExecutor(objectMethodExecutor));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue