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:
parent
927af3125e
commit
c93c168df3
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue