Revisit the unwrapping code in ControllerActionInvoker

Fixes #6679
This commit is contained in:
Pranav K 2017-09-01 12:49:20 -07:00
parent 96bd2769d0
commit f0a5af2ba7
8 changed files with 555 additions and 112 deletions

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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));
}
}