Add mapping service for action results

This allows the use of custom 'envelope' types like ActionResult<> with
a corresponding API Explorer implementation.

Basically this PR services to decouple a bunch of infrastructure from
ActionResult<>.
This commit is contained in:
Ryan Nowak 2018-03-21 22:22:58 -07:00
parent 927af3125e
commit c93c168df3
17 changed files with 317 additions and 78 deletions

View File

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@ -25,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
public class DefaultApiDescriptionProvider : IApiDescriptionProvider public class DefaultApiDescriptionProvider : IApiDescriptionProvider
{ {
private readonly MvcOptions _mvcOptions; private readonly MvcOptions _mvcOptions;
private readonly IActionResultTypeMapper _mapper;
private readonly IInlineConstraintResolver _constraintResolver; private readonly IInlineConstraintResolver _constraintResolver;
private readonly IModelMetadataProvider _modelMetadataProvider; private readonly IModelMetadataProvider _modelMetadataProvider;
@ -35,6 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
/// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/> used for resolving inline /// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/> used for resolving inline
/// constraints.</param> /// constraints.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future release.")]
public DefaultApiDescriptionProvider( public DefaultApiDescriptionProvider(
IOptions<MvcOptions> optionsAccessor, IOptions<MvcOptions> optionsAccessor,
IInlineConstraintResolver constraintResolver, IInlineConstraintResolver constraintResolver,
@ -45,6 +48,26 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
_modelMetadataProvider = modelMetadataProvider; _modelMetadataProvider = modelMetadataProvider;
} }
/// <summary>
/// Creates a new instance of <see cref="DefaultApiDescriptionProvider"/>.
/// </summary>
/// <param name="optionsAccessor">The accessor for <see cref="MvcOptions"/>.</param>
/// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/> used for resolving inline
/// constraints.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="mapper"> The <see cref="IActionResultTypeMapper"/>.</param>
public DefaultApiDescriptionProvider(
IOptions<MvcOptions> optionsAccessor,
IInlineConstraintResolver constraintResolver,
IModelMetadataProvider modelMetadataProvider,
IActionResultTypeMapper mapper)
{
_mvcOptions = optionsAccessor.Value;
_constraintResolver = constraintResolver;
_modelMetadataProvider = modelMetadataProvider;
_mapper = mapper;
}
/// <inheritdoc /> /// <inheritdoc />
public int Order => -1000; public int Order => -1000;
@ -481,12 +504,14 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{ {
return typeof(void); return typeof(void);
} }
// Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled. // Unwrap the type if it's a Task<T>. The Task (non-generic) case was already handled.
var unwrappedType = UnwrapGenericType(declaredReturnType, typeof(Task<>)); Type unwrappedType = declaredReturnType;
if (declaredReturnType.IsGenericType &&
// Unwrap the type if it's ActionResult<T> or Task<ActionResult<T>>. declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>))
unwrappedType = UnwrapGenericType(unwrappedType, typeof(ActionResult<>)); {
unwrappedType = declaredReturnType.GetGenericArguments()[0];
}
// If the method is declared to return IActionResult or a derived class, that information // If the method is declared to return IActionResult or a derived class, that information
// isn't valuable to the formatter. // isn't valuable to the formatter.
@ -494,16 +519,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{ {
return null; return null;
} }
else
{
return unwrappedType;
}
Type UnwrapGenericType(Type type, Type queryType) // If we get here, the type should be a user-defined data type or an envelope type
{ // like ActionResult<T>. The mapper service will unwrap envelopes.
var genericType = ClosedGenericMatcher.ExtractGenericInterface(type, queryType); unwrappedType = _mapper.GetResultDataType(unwrappedType);
return genericType?.GenericTypeArguments[0] ?? type; return unwrappedType;
}
} }
private Type GetRuntimeReturnType(Type declaredReturnType) private Type GetRuntimeReturnType(Type declaredReturnType)

View File

@ -202,6 +202,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<ControllerActionInvokerCache>(); services.TryAddSingleton<ControllerActionInvokerCache>();
services.TryAddEnumerable( services.TryAddEnumerable(
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>()); ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
services.TryAddSingleton<IActionResultTypeMapper, ActionResultTypeMapper>();
// //
// Request body limit filters // Request body limit filters

View File

@ -0,0 +1,48 @@
// 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.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
/// <summary>
/// Provides a mapping from the return value of an action to an <see cref="IActionResult"/>
/// for request processing.
/// </summary>
/// <remarks>
/// The default implementation of this service handles the conversion of
/// <see cref="ActionResult{TValue}"/> to an <see cref="IActionResult"/> during request
/// processing as well as the mapping of <see cref="ActionResult{TValue}"/> to <c>TValue</c>
/// during API Explorer processing.
/// </remarks>
public interface IActionResultTypeMapper
{
/// <summary>
/// Gets the result data type that corresponds to <paramref name="returnType"/>. This
/// method will not be called for actions that return <c>void</c> or an <see cref="IActionResult"/>
/// type.
/// </summary>
/// <param name="returnType">The declared return type of an action.</param>
/// <returns>A <see cref="Type"/> that represents the response data.</returns>
/// <remarks>
/// Prior to calling this method, the infrastructure will unwrap <see cref="Task{TResult}"/> or
/// other task-like types.
/// </remarks>
Type GetResultDataType(Type returnType);
/// <summary>
/// Converts the result of an action to an <see cref="IActionResult"/> for response processing.
/// This method will be not be called when a method returns <c>void</c> or an
/// <see cref="IActionResult"/> value.
/// </summary>
/// <param name="value">The action return value. May be <c>null</c>.</param>
/// <param name="returnType">The declared return type.</param>
/// <returns>An <see cref="IActionResult"/> for response processing.</returns>
/// <remarks>
/// Prior to calling this method, the infrastructure will unwrap <see cref="Task{TResult}"/> or
/// other task-like types.
/// </remarks>
IActionResult Convert(object value, Type returnType);
}
}

View File

@ -27,7 +27,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new AwaitableObjectResultExecutor(), new AwaitableObjectResultExecutor(),
}; };
public abstract ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments); public abstract ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments);
protected abstract bool CanExecute(ObjectMethodExecutor executor); protected abstract bool CanExecute(ObjectMethodExecutor executor);
@ -48,7 +52,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// void LogMessage(..) // void LogMessage(..)
private class VoidResultExecutor : ActionMethodExecutor private class VoidResultExecutor : ActionMethodExecutor
{ {
public override ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
executor.Execute(controller, arguments); executor.Execute(controller, arguments);
return new ValueTask<IActionResult>(new EmptyResult()); return new ValueTask<IActionResult>(new EmptyResult());
@ -62,7 +70,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// CreatedAtResult Put(..) // CreatedAtResult Put(..)
private class SyncActionResultExecutor : ActionMethodExecutor private class SyncActionResultExecutor : ActionMethodExecutor
{ {
public override ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
var actionResult = (IActionResult)executor.Execute(controller, arguments); var actionResult = (IActionResult)executor.Execute(controller, arguments);
EnsureActionResultNotNull(executor, actionResult); EnsureActionResultNotNull(executor, actionResult);
@ -78,11 +90,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// object Index(..) // object Index(..)
private class SyncObjectResultExecutor : ActionMethodExecutor private class SyncObjectResultExecutor : ActionMethodExecutor
{ {
public override ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
// Sync method returning arbitrary object // Sync method returning arbitrary object
var returnValue = executor.Execute(controller, arguments); var returnValue = executor.Execute(controller, arguments);
var actionResult = ConvertToActionResult(returnValue, executor.MethodReturnType); var actionResult = ConvertToActionResult(mapper, returnValue, executor.MethodReturnType);
return new ValueTask<IActionResult>(actionResult); return new ValueTask<IActionResult>(actionResult);
} }
@ -93,7 +109,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Task SaveState(..) // Task SaveState(..)
private class TaskResultExecutor : ActionMethodExecutor private class TaskResultExecutor : ActionMethodExecutor
{ {
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override async ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
await (Task)executor.Execute(controller, arguments); await (Task)executor.Execute(controller, arguments);
return new EmptyResult(); return new EmptyResult();
@ -106,7 +126,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Custom task-like type with no return value. // Custom task-like type with no return value.
private class AwaitableResultExecutor : ActionMethodExecutor private class AwaitableResultExecutor : ActionMethodExecutor
{ {
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override async ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
await executor.ExecuteAsync(controller, arguments); await executor.ExecuteAsync(controller, arguments);
return new EmptyResult(); return new EmptyResult();
@ -122,7 +146,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Task<IActionResult> Post(..) // Task<IActionResult> Post(..)
private class TaskOfIActionResultExecutor : ActionMethodExecutor private class TaskOfIActionResultExecutor : ActionMethodExecutor
{ {
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override async ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
// Async method returning Task<IActionResult> // Async method returning Task<IActionResult>
// Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task<IActionResult>. // Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task<IActionResult>.
@ -141,7 +169,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// ValueTask<ViewResult> GetViewsAsync(..) // ValueTask<ViewResult> GetViewsAsync(..)
private class TaskOfActionResultExecutor : ActionMethodExecutor private class TaskOfActionResultExecutor : ActionMethodExecutor
{ {
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override async ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
// Async method returning awaitable-of-IActionResult (e.g., Task<ViewResult>) // 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. // We have to use ExecuteAsync because we don't know the awaitable's type at compile time.
@ -161,11 +193,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Task<Customer> GetCustomerAsync(..) // Task<Customer> GetCustomerAsync(..)
private class AwaitableObjectResultExecutor : ActionMethodExecutor private class AwaitableObjectResultExecutor : ActionMethodExecutor
{ {
public override async ValueTask<IActionResult> Execute(ObjectMethodExecutor executor, object controller, object[] arguments) public override async ValueTask<IActionResult> Execute(
IActionResultTypeMapper mapper,
ObjectMethodExecutor executor,
object controller,
object[] arguments)
{ {
// Async method returning awaitable-of-nonvoid // Async method returning awaitable-of-nonvoid
var returnValue = await executor.ExecuteAsync(controller, arguments); var returnValue = await executor.ExecuteAsync(controller, arguments);
var actionResult = ConvertToActionResult(returnValue, executor.MethodReturnType); var actionResult = ConvertToActionResult(mapper, returnValue, executor.MethodReturnType);
return actionResult; return actionResult;
} }
@ -176,30 +212,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
if (actionResult == null) if (actionResult == null)
{ {
throw new InvalidOperationException( var type = executor.AsyncResultType ?? executor.MethodReturnType;
Resources.FormatActionResult_ActionReturnValueCannotBeNull(executor.AsyncResultType ?? executor.MethodReturnType)); throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(type));
} }
} }
private static IActionResult ConvertToActionResult(object returnValue, Type declaredType) private IActionResult ConvertToActionResult(IActionResultTypeMapper mapper, object returnValue, Type declaredType)
{ {
IActionResult result; var result = (returnValue as IActionResult) ?? mapper.Convert(returnValue, declaredType);
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) if (result == null)
{ {
throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(declaredType)); throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(declaredType));

View File

@ -0,0 +1,45 @@
// 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 Microsoft.AspNetCore.Mvc.Infrastructure;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ActionResultTypeMapper : IActionResultTypeMapper
{
public Type GetResultDataType(Type returnType)
{
if (returnType == null)
{
throw new ArgumentNullException(nameof(returnType));
}
if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(ActionResult<>))
{
return returnType.GetGenericArguments()[0];
}
return returnType;
}
public IActionResult Convert(object value, Type returnType)
{
if (returnType == null)
{
throw new ArgumentNullException(nameof(returnType));
}
if (value is IConvertToActionResult converter)
{
return converter.Convert();
}
return new ObjectResult(value)
{
DeclaredType = returnType,
};
}
}
}

View File

@ -28,10 +28,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
internal ControllerActionInvoker( internal ControllerActionInvoker(
ILogger logger, ILogger logger,
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
IActionResultTypeMapper mapper,
ControllerContext controllerContext, ControllerContext controllerContext,
ControllerActionInvokerCacheEntry cacheEntry, ControllerActionInvokerCacheEntry cacheEntry,
IFilterMetadata[] filters) IFilterMetadata[] filters)
: base(diagnosticSource, logger, controllerContext, filters, controllerContext.ValueProviderFactories) : base(diagnosticSource, logger, mapper, controllerContext, filters, controllerContext.ValueProviderFactories)
{ {
if (cacheEntry == null) if (cacheEntry == null)
{ {
@ -347,7 +348,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controller); controller);
logger.ActionMethodExecuting(controllerContext, orderedArguments); logger.ActionMethodExecuting(controllerContext, orderedArguments);
var stopwatch = ValueStopwatch.StartNew(); var stopwatch = ValueStopwatch.StartNew();
var actionResultValueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments); var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, controller, orderedArguments);
if (actionResultValueTask.IsCompletedSuccessfully) if (actionResultValueTask.IsCompletedSuccessfully)
{ {
result = actionResultValueTask.Result; result = actionResultValueTask.Result;
@ -370,18 +371,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
private static bool IsResultIActionResult(ObjectMethodExecutor executor)
{
var resultType = executor.AsyncResultType ?? executor.MethodReturnType;
return typeof(IActionResult).IsAssignableFrom(resultType);
}
private bool IsConvertibleToActionResult(ObjectMethodExecutor executor)
{
var resultType = executor.AsyncResultType ?? executor.MethodReturnType;
return typeof(IConvertToActionResult).IsAssignableFrom(resultType);
}
/// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the /// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
/// variables in this method represent.</remarks> /// variables in this method represent.</remarks>
protected override async Task InvokeInnerFilterAsync() protected override async Task InvokeInnerFilterAsync()

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -20,18 +21,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private readonly int _maxModelValidationErrors; private readonly int _maxModelValidationErrors;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly DiagnosticSource _diagnosticSource; private readonly DiagnosticSource _diagnosticSource;
private readonly IActionResultTypeMapper _mapper;
public ControllerActionInvokerProvider( public ControllerActionInvokerProvider(
ControllerActionInvokerCache controllerActionInvokerCache, ControllerActionInvokerCache controllerActionInvokerCache,
IOptions<MvcOptions> optionsAccessor, IOptions<MvcOptions> optionsAccessor,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
DiagnosticSource diagnosticSource) DiagnosticSource diagnosticSource,
IActionResultTypeMapper mapper)
{ {
_controllerActionInvokerCache = controllerActionInvokerCache; _controllerActionInvokerCache = controllerActionInvokerCache;
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray(); _valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
_maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors; _maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors;
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>(); _logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
_diagnosticSource = diagnosticSource; _diagnosticSource = diagnosticSource;
_mapper = mapper;
} }
public int Order => -1000; public int Order => -1000;
@ -56,6 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var invoker = new ControllerActionInvoker( var invoker = new ControllerActionInvoker(
_logger, _logger,
_diagnosticSource, _diagnosticSource,
_mapper,
controllerContext, controllerContext,
cacheResult.cacheEntry, cacheResult.cacheEntry,
cacheResult.filters); cacheResult.filters);

View File

@ -8,6 +8,7 @@ using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -18,6 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
protected readonly DiagnosticSource _diagnosticSource; protected readonly DiagnosticSource _diagnosticSource;
protected readonly ILogger _logger; protected readonly ILogger _logger;
protected readonly IActionResultTypeMapper _mapper;
protected readonly ActionContext _actionContext; protected readonly ActionContext _actionContext;
protected readonly IFilterMetadata[] _filters; protected readonly IFilterMetadata[] _filters;
protected readonly IList<IValueProviderFactory> _valueProviderFactories; protected readonly IList<IValueProviderFactory> _valueProviderFactories;
@ -38,12 +40,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public ResourceInvoker( public ResourceInvoker(
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
ILogger logger, ILogger logger,
IActionResultTypeMapper mapper,
ActionContext actionContext, ActionContext actionContext,
IFilterMetadata[] filters, IFilterMetadata[] filters,
IList<IValueProviderFactory> valueProviderFactories) IList<IValueProviderFactory> valueProviderFactories)
{ {
_diagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource)); _diagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext)); _actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext));
_filters = filters ?? throw new ArgumentNullException(nameof(filters)); _filters = filters ?? throw new ArgumentNullException(nameof(filters));

View File

@ -9,6 +9,7 @@ using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
@ -43,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
IPageHandlerMethodSelector handlerMethodSelector, IPageHandlerMethodSelector handlerMethodSelector,
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
ILogger logger, ILogger logger,
IActionResultTypeMapper mapper,
PageContext pageContext, PageContext pageContext,
IFilterMetadata[] filterMetadata, IFilterMetadata[] filterMetadata,
PageActionInvokerCacheEntry cacheEntry, PageActionInvokerCacheEntry cacheEntry,
@ -52,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
: base( : base(
diagnosticSource, diagnosticSource,
logger, logger,
mapper,
pageContext, pageContext,
filterMetadata, filterMetadata,
pageContext.ValueProviderFactories) pageContext.ValueProviderFactories)

View File

@ -42,6 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private readonly RazorProjectFileSystem _razorFileSystem; private readonly RazorProjectFileSystem _razorFileSystem;
private readonly DiagnosticSource _diagnosticSource; private readonly DiagnosticSource _diagnosticSource;
private readonly ILogger<PageActionInvoker> _logger; private readonly ILogger<PageActionInvoker> _logger;
private readonly IActionResultTypeMapper _mapper;
private volatile InnerCache _currentCache; private volatile InnerCache _currentCache;
public PageActionInvokerProvider( public PageActionInvokerProvider(
@ -60,7 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
IPageHandlerMethodSelector selector, IPageHandlerMethodSelector selector,
RazorProjectFileSystem razorFileSystem, RazorProjectFileSystem razorFileSystem,
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory,
IActionResultTypeMapper mapper)
{ {
_loader = loader; _loader = loader;
_pageFactoryProvider = pageFactoryProvider; _pageFactoryProvider = pageFactoryProvider;
@ -79,6 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_razorFileSystem = razorFileSystem; _razorFileSystem = razorFileSystem;
_diagnosticSource = diagnosticSource; _diagnosticSource = diagnosticSource;
_logger = loggerFactory.CreateLogger<PageActionInvoker>(); _logger = loggerFactory.CreateLogger<PageActionInvoker>();
_mapper = mapper;
} }
public int Order { get; } = -1000; public int Order { get; } = -1000;
@ -158,6 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_selector, _selector,
_diagnosticSource, _diagnosticSource,
_logger, _logger,
_mapper,
pageContext, pageContext,
filters, filters,
cacheEntry, cacheEntry,

View File

@ -1449,7 +1449,8 @@ namespace Microsoft.AspNetCore.Mvc.Description
var provider = new DefaultApiDescriptionProvider( var provider = new DefaultApiDescriptionProvider(
optionsAccessor, optionsAccessor,
constraintResolver.Object, constraintResolver.Object,
modelMetadataProvider); modelMetadataProvider,
new ActionResultTypeMapper());
provider.OnProvidersExecuting(context); provider.OnProvidersExecuting(context);
provider.OnProvidersExecuted(context); provider.OnProvidersExecuted(context);

View File

@ -17,12 +17,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesVoidActions() public void ActionMethodExecutor_ExecutesVoidActions()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.VoidAction)); var objectMethodExecutor = GetExecutor(nameof(TestController.VoidAction));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.True(controller.Executed); Assert.True(controller.Executed);
@ -33,12 +34,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturningIActionResult() public void ActionMethodExecutor_ExecutesActionsReturningIActionResult()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResult)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResult));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.True(valueTask.IsCompleted); Assert.True(valueTask.IsCompleted);
@ -49,12 +51,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturningSubTypeOfActionResult() public void ActionMethodExecutor_ExecutesActionsReturningSubTypeOfActionResult()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsIActionResultSubType)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsIActionResultSubType));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.IsType<PartialViewResult>(valueTask.Result); Assert.IsType<PartialViewResult>(valueTask.Result);
@ -64,12 +67,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturningActionResultOfT() public void ActionMethodExecutor_ExecutesActionsReturningActionResultOfT()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsActionResultOfT)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsActionResultOfT));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
var result = Assert.IsType<ObjectResult>(valueTask.Result); var result = Assert.IsType<ObjectResult>(valueTask.Result);
@ -81,12 +85,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturningModelAsModel() public void ActionMethodExecutor_ExecutesActionsReturningModelAsModel()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsModel)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsModel));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
var result = Assert.IsType<ObjectResult>(valueTask.Result); var result = Assert.IsType<ObjectResult>(valueTask.Result);
@ -98,12 +103,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturningModelAsObject() public void ActionMethodExecutor_ExecutesActionsReturningModelAsObject()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnModelAsObject)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnModelAsObject));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
var result = Assert.IsType<ObjectResult>(valueTask.Result); var result = Assert.IsType<ObjectResult>(valueTask.Result);
@ -115,12 +121,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturningActionResultAsObject() public void ActionMethodExecutor_ExecutesActionsReturningActionResultAsObject()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsIActionResultSubType)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsIActionResultSubType));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.IsType<PartialViewResult>(valueTask.Result); Assert.IsType<PartialViewResult>(valueTask.Result);
@ -130,12 +137,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsReturnTask() public void ActionMethodExecutor_ExecutesActionsReturnTask()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsTask)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsTask));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.True(controller.Executed); Assert.True(controller.Executed);
@ -146,12 +154,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutorExecutesActionsAsynchronouslyReturningIActionResult() public void ActionMethodExecutorExecutesActionsAsynchronouslyReturningIActionResult()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsync)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsync));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.IsType<ViewResult>(valueTask.Result); Assert.IsType<ViewResult>(valueTask.Result);
@ -161,12 +170,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public async Task ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningActionResultSubType() public async Task ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningActionResultSubType()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsync)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsync));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
await valueTask; await valueTask;
@ -177,12 +187,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningModel() public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningModel()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsModelAsync)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsModelAsync));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
var result = Assert.IsType<ObjectResult>(valueTask.Result); var result = Assert.IsType<ObjectResult>(valueTask.Result);
@ -194,12 +205,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningModelAsObject() public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningModelAsObject()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsObjectAsync)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsModelAsObjectAsync));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
var result = Assert.IsType<ObjectResult>(valueTask.Result); var result = Assert.IsType<ObjectResult>(valueTask.Result);
@ -211,12 +223,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningIActionResultAsObject() public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningIActionResultAsObject()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsObjectAsync)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnIActionResultAsObjectAsync));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
Assert.IsType<OkResult>(valueTask.Result); Assert.IsType<OkResult>(valueTask.Result);
@ -226,12 +239,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningActionResultOfT() public void ActionMethodExecutor_ExecutesActionsAsynchronouslyReturningActionResultOfT()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnActionResultOFTAsync)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnActionResultOFTAsync));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act // Act
var valueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>()); var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>());
// Assert // Assert
var result = Assert.IsType<ObjectResult>(valueTask.Result); var result = Assert.IsType<ObjectResult>(valueTask.Result);
@ -243,13 +257,14 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal
public void ActionMethodExecutor_ThrowsIfIConvertFromIActionResult_ReturnsNull() public void ActionMethodExecutor_ThrowsIfIConvertFromIActionResult_ReturnsNull()
{ {
// Arrange // Arrange
var mapper = new ActionResultTypeMapper();
var controller = new TestController(); var controller = new TestController();
var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsCustomConvertibleFromIActionResult)); var objectMethodExecutor = GetExecutor(nameof(TestController.ReturnsCustomConvertibleFromIActionResult));
var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
// Act & Assert // Act & Assert
var ex = Assert.Throws<InvalidOperationException>( var ex = Assert.Throws<InvalidOperationException>(
() => actionMethodExecutor.Execute(objectMethodExecutor, controller, Array.Empty<object>())); () => actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty<object>()));
Assert.Equal($"Cannot return null from an action method with a return type of '{typeof(CustomConvertibleFromAction)}'.", ex.Message); Assert.Equal($"Cannot return null from an action method with a return type of '{typeof(CustomConvertibleFromAction)}'.", ex.Message);
} }

View File

@ -0,0 +1,75 @@
// 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 Microsoft.AspNetCore.Mvc.Infrastructure;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ActionResultTypeMapperTest
{
[Fact]
public void Convert_WithIConvertToActionResult_DelegatesToInterface()
{
// Arrange
var mapper = new ActionResultTypeMapper();
var expected = new EmptyResult();
var returnValue = Mock.Of<IConvertToActionResult>(r => r.Convert() == expected);
// Act
var result = mapper.Convert(returnValue, typeof(string));
// Assert
Assert.Same(expected, result);
}
[Fact]
public void Convert_WithRegularType_CreatesObjectResult()
{
// Arrange
var mapper = new ActionResultTypeMapper();
var returnValue = "hello";
// Act
var result = mapper.Convert(returnValue, typeof(string));
// Assert
var objectResult = Assert.IsType<ObjectResult>(result);
Assert.Same(returnValue, objectResult.Value);
Assert.Equal(typeof(string), objectResult.DeclaredType);
}
[Fact]
public void GetResultDataType_WithActionResultOfT_UnwrapsType()
{
// Arrange
var mapper = new ActionResultTypeMapper();
var returnType = typeof(ActionResult<string>);
// Act
var result = mapper.GetResultDataType(returnType);
// Assert
Assert.Equal(typeof(string), result);
}
[Fact]
public void GetResultDataType_WithRegularType_ReturnsType()
{
// Arrange
var mapper = new ActionResultTypeMapper();
var returnType = typeof(string);
// Act
var result = mapper.GetResultDataType(returnType);
// Assert
Assert.Equal(typeof(string), result);
}
}
}

View File

@ -1345,6 +1345,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var invoker = new ControllerActionInvoker( var invoker = new ControllerActionInvoker(
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(), new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new DiagnosticListener("Microsoft.AspNetCore"), new DiagnosticListener("Microsoft.AspNetCore"),
new ActionResultTypeMapper(),
controllerContext, controllerContext,
cacheEntry, cacheEntry,
new IFilterMetadata[0]); new IFilterMetadata[0]);
@ -1624,6 +1625,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var invoker = new ControllerActionInvoker( var invoker = new ControllerActionInvoker(
logger, logger,
diagnosticSource, diagnosticSource,
new ActionResultTypeMapper(),
controllerContext, controllerContext,
cacheEntry, cacheEntry,
filters); filters);

View File

@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@ -285,6 +286,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new MockControllerFactory(controller ?? this), new MockControllerFactory(controller ?? this),
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(), new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
diagnosticSource, diagnosticSource,
new ActionResultTypeMapper(),
actionContext, actionContext,
new List<IValueProviderFactory>(), new List<IValueProviderFactory>(),
maxAllowedErrorsInModelState: 200); maxAllowedErrorsInModelState: 200);
@ -389,12 +391,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
MockControllerFactory controllerFactory, MockControllerFactory controllerFactory,
ILogger logger, ILogger logger,
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
IActionResultTypeMapper mapper,
ActionContext actionContext, ActionContext actionContext,
IReadOnlyList<IValueProviderFactory> valueProviderFactories, IReadOnlyList<IValueProviderFactory> valueProviderFactories,
int maxAllowedErrorsInModelState) int maxAllowedErrorsInModelState)
: base( : base(
logger, logger,
diagnosticSource, diagnosticSource,
mapper,
CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState), CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory), CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory),
filters) filters)

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
@ -515,7 +516,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Mock.Of<IPageHandlerMethodSelector>(), Mock.Of<IPageHandlerMethodSelector>(),
fileSystem, fileSystem,
new DiagnosticListener("Microsoft.AspNetCore"), new DiagnosticListener("Microsoft.AspNetCore"),
NullLoggerFactory.Instance); NullLoggerFactory.Instance,
new ActionResultTypeMapper());
} }
private IActionDescriptorCollectionProvider CreateActionDescriptorCollection(PageActionDescriptor descriptor) private IActionDescriptorCollectionProvider CreateActionDescriptorCollection(PageActionDescriptor descriptor)

View File

@ -1217,6 +1217,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
selector.Object, selector.Object,
diagnosticListener ?? new DiagnosticListener("Microsoft.AspNetCore"), diagnosticListener ?? new DiagnosticListener("Microsoft.AspNetCore"),
logger ?? NullLogger.Instance, logger ?? NullLogger.Instance,
new ActionResultTypeMapper(),
pageContext, pageContext,
filters ?? Array.Empty<IFilterMetadata>(), filters ?? Array.Empty<IFilterMetadata>(),
cacheEntry, cacheEntry,