Refactor Argument Binder
This change just rearranges some code in the argument binder with a mind towards performance and clarity. We're removing a few Task<T>'s here as well in certain cases, but not yet all of them. We additionally save a dictionary in the case where you have bound properties. Hopefully these changes break the code into more discrete and sensible units without multiple levels of indirection without abstraction. - Main 'driver' code - BindModel - ActivateProperty
This commit is contained in:
parent
4d63ffa879
commit
128d74e2a0
|
|
@ -9,15 +9,20 @@ namespace Microsoft.AspNetCore.Mvc.Controllers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a dictionary of action arguments.
|
/// Provides a dictionary of action arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IControllerActionArgumentBinder
|
public interface IControllerArgumentBinder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a dictionary of the parameter-argument name-value pairs,
|
/// Asyncronously binds a dictionary of the parameter-argument name-value pairs,
|
||||||
/// which can be used to invoke the action. Also binds properties explicitly marked properties on the
|
/// which can be used to invoke the action. Also binds properties explicitly marked properties on the
|
||||||
/// <paramref name="controller"/>.
|
/// <paramref name="controller"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The <see cref="ControllerContext"/> associated with the current action.</param>
|
/// <param name="controllerContext">The <see cref="ControllerContext"/> associated with the current action.</param>
|
||||||
/// <param name="controller">The controller object which contains the action.</param>
|
/// <param name="controller">The controller object which contains the action.</param>
|
||||||
Task<IDictionary<string, object>> BindActionArgumentsAsync(ControllerContext context, object controller);
|
/// <param name="arguments">The arguments dictionary.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> which, when completed signals the completion of argument binding.</returns>
|
||||||
|
Task BindArgumentsAsync(
|
||||||
|
ControllerContext controllerContext,
|
||||||
|
object controller,
|
||||||
|
IDictionary<string, object> arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
|
ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
|
||||||
|
|
||||||
// These are stateless
|
// These are stateless
|
||||||
services.TryAddSingleton<IControllerActionArgumentBinder, ControllerArgumentBinder>();
|
services.TryAddSingleton<IControllerArgumentBinder, ControllerArgumentBinder>();
|
||||||
services.TryAddSingleton<ControllerActionInvokerCache>();
|
services.TryAddSingleton<ControllerActionInvokerCache>();
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
|
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
private readonly ControllerActionDescriptor _descriptor;
|
private readonly ControllerActionDescriptor _descriptor;
|
||||||
private readonly IControllerFactory _controllerFactory;
|
private readonly IControllerFactory _controllerFactory;
|
||||||
private readonly IControllerActionArgumentBinder _argumentBinder;
|
private readonly IControllerArgumentBinder _argumentBinder;
|
||||||
|
|
||||||
public ControllerActionInvoker(
|
public ControllerActionInvoker(
|
||||||
ActionContext actionContext,
|
ActionContext actionContext,
|
||||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
IControllerFactory controllerFactory,
|
IControllerFactory controllerFactory,
|
||||||
ControllerActionDescriptor descriptor,
|
ControllerActionDescriptor descriptor,
|
||||||
IControllerActionArgumentBinder argumentBinder,
|
IControllerArgumentBinder argumentBinder,
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DiagnosticSource diagnosticSource,
|
DiagnosticSource diagnosticSource,
|
||||||
|
|
@ -111,9 +111,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return actionResult;
|
return actionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<IDictionary<string, object>> BindActionArgumentsAsync()
|
protected override Task BindActionArgumentsAsync(IDictionary<string, object> arguments)
|
||||||
{
|
{
|
||||||
return _argumentBinder.BindActionArgumentsAsync(Context, Instance);
|
if (arguments == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _argumentBinder.BindArgumentsAsync(Context, Instance, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marking as internal for Unit Testing purposes.
|
// Marking as internal for Unit Testing purposes.
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
public class ControllerActionInvokerProvider : IActionInvokerProvider
|
public class ControllerActionInvokerProvider : IActionInvokerProvider
|
||||||
{
|
{
|
||||||
private readonly IControllerActionArgumentBinder _argumentBinder;
|
private readonly IControllerArgumentBinder _argumentBinder;
|
||||||
private readonly IControllerFactory _controllerFactory;
|
private readonly IControllerFactory _controllerFactory;
|
||||||
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
|
||||||
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
public ControllerActionInvokerProvider(
|
public ControllerActionInvokerProvider(
|
||||||
IControllerFactory controllerFactory,
|
IControllerFactory controllerFactory,
|
||||||
ControllerActionInvokerCache controllerActionInvokerCache,
|
ControllerActionInvokerCache controllerActionInvokerCache,
|
||||||
IControllerActionArgumentBinder argumentBinder,
|
IControllerArgumentBinder argumentBinder,
|
||||||
IOptions<MvcOptions> optionsAccessor,
|
IOptions<MvcOptions> optionsAccessor,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
DiagnosticSource diagnosticSource)
|
DiagnosticSource diagnosticSource)
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ using Microsoft.Extensions.Internal;
|
||||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a default implementation of <see cref="IControllerActionArgumentBinder"/>.
|
/// Provides a default implementation of <see cref="IControllerArgumentBinder"/>.
|
||||||
/// Uses ModelBinding to populate action parameters.
|
/// Uses ModelBinding to populate action parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControllerArgumentBinder : IControllerActionArgumentBinder
|
public class ControllerArgumentBinder : IControllerArgumentBinder
|
||||||
{
|
{
|
||||||
private static readonly MethodInfo CallPropertyAddRangeOpenGenericMethod =
|
private static readonly MethodInfo CallPropertyAddRangeOpenGenericMethod =
|
||||||
typeof(ControllerArgumentBinder).GetTypeInfo().GetDeclaredMethod(
|
typeof(ControllerArgumentBinder).GetTypeInfo().GetDeclaredMethod(
|
||||||
|
|
@ -38,9 +38,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
_validator = validator;
|
_validator = validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IDictionary<string, object>> BindActionArgumentsAsync(
|
public Task BindArgumentsAsync(
|
||||||
ControllerContext controllerContext,
|
ControllerContext controllerContext,
|
||||||
object controller)
|
object controller,
|
||||||
|
IDictionary<string, object> arguments)
|
||||||
{
|
{
|
||||||
if (controllerContext == null)
|
if (controllerContext == null)
|
||||||
{
|
{
|
||||||
|
|
@ -65,53 +66,54 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
if (actionDescriptor.BoundProperties.Count == 0 &&
|
if (actionDescriptor.BoundProperties.Count == 0 &&
|
||||||
actionDescriptor.Parameters.Count == 0)
|
actionDescriptor.Parameters.Count == 0)
|
||||||
{
|
{
|
||||||
return Task.FromResult<IDictionary<string, object>>(
|
return TaskCache.CompletedTask;
|
||||||
new Dictionary<string, object>(StringComparer.Ordinal));
|
|
||||||
}
|
|
||||||
else if (actionDescriptor.BoundProperties.Count == 0)
|
|
||||||
{
|
|
||||||
return BindActionArgumentsCoreAsync(controllerContext, actionDescriptor);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return BindActionArgumentsAndPropertiesCoreAsync(
|
|
||||||
controllerContext,
|
|
||||||
controller,
|
|
||||||
actionDescriptor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return BindArgumentsCoreAsync(controllerContext, controller, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IDictionary<string, object>> BindActionArgumentsCoreAsync(
|
private async Task BindArgumentsCoreAsync(
|
||||||
ControllerContext controllerContext,
|
|
||||||
ControllerActionDescriptor actionDescriptor)
|
|
||||||
{
|
|
||||||
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
|
|
||||||
|
|
||||||
var actionArguments = await PopulateArgumentsAsync(
|
|
||||||
controllerContext,
|
|
||||||
actionDescriptor.Parameters,
|
|
||||||
valueProvider);
|
|
||||||
return actionArguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IDictionary<string, object>> BindActionArgumentsAndPropertiesCoreAsync(
|
|
||||||
ControllerContext controllerContext,
|
ControllerContext controllerContext,
|
||||||
object controller,
|
object controller,
|
||||||
ControllerActionDescriptor actionDescriptor)
|
IDictionary<string, object> arguments)
|
||||||
{
|
{
|
||||||
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
|
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
|
||||||
|
|
||||||
var controllerProperties = await PopulateArgumentsAsync(
|
var parameters = controllerContext.ActionDescriptor.Parameters;
|
||||||
controllerContext,
|
for (var i = 0; i < parameters.Count; i++)
|
||||||
actionDescriptor.BoundProperties,
|
{
|
||||||
valueProvider);
|
var parameter = parameters[i];
|
||||||
ActivateProperties(actionDescriptor, controller, controllerProperties);
|
|
||||||
|
|
||||||
var actionArguments = await PopulateArgumentsAsync(
|
var result = await BindModelAsync(parameter, controllerContext, valueProvider);
|
||||||
controllerContext,
|
if (result != null && result.Value.IsModelSet)
|
||||||
actionDescriptor.Parameters,
|
{
|
||||||
valueProvider);
|
arguments[parameter.Name] = result.Value.Model;
|
||||||
return actionArguments;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var properties = controllerContext.ActionDescriptor.BoundProperties;
|
||||||
|
if (properties.Count == 0)
|
||||||
|
{
|
||||||
|
// Perf: Early exit to avoid PropertyHelper lookup in the (common) case where we have no
|
||||||
|
// bound properties.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyHelpers = PropertyHelper.GetProperties(controller);
|
||||||
|
for (var i = 0; i < properties.Count; i++)
|
||||||
|
{
|
||||||
|
var property = properties[i];
|
||||||
|
|
||||||
|
var result = await BindModelAsync(property, controllerContext, valueProvider);
|
||||||
|
if (result != null && result.Value.IsModelSet)
|
||||||
|
{
|
||||||
|
var propertyHelper = FindPropertyHelper(propertyHelpers, property);
|
||||||
|
if (propertyHelper != null)
|
||||||
|
{
|
||||||
|
ActivateProperty(property, propertyHelper, controller, result.Value.Model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ModelBindingResult?> BindModelAsync(
|
public async Task<ModelBindingResult?> BindModelAsync(
|
||||||
|
|
@ -199,6 +201,52 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
return modelBindingResult;
|
return modelBindingResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ActivateProperty(
|
||||||
|
ParameterDescriptor property,
|
||||||
|
PropertyHelper propertyHelper,
|
||||||
|
object controller,
|
||||||
|
object value)
|
||||||
|
{
|
||||||
|
var propertyType = propertyHelper.Property.PropertyType;
|
||||||
|
var metadata = _modelMetadataProvider.GetMetadataForType(propertyType);
|
||||||
|
|
||||||
|
if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPublic == true)
|
||||||
|
{
|
||||||
|
// Handle settable property. Do not set the property to null if the type is a non-nullable type.
|
||||||
|
if (value != null || metadata.IsReferenceOrNullableType)
|
||||||
|
{
|
||||||
|
propertyHelper.SetValue(controller, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyType.IsArray)
|
||||||
|
{
|
||||||
|
// Do not attempt to copy values into an array because an array's length is immutable. This choice
|
||||||
|
// is also consistent with MutableObjectModelBinder's handling of a read-only array property.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = propertyHelper.GetValue(controller);
|
||||||
|
if (value == null || target == null)
|
||||||
|
{
|
||||||
|
// Nothing to do when source or target is null.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metadata.IsCollectionType)
|
||||||
|
{
|
||||||
|
// Not a collection model.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle a read-only collection property.
|
||||||
|
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
||||||
|
metadata.ElementMetadata.ModelType);
|
||||||
|
propertyAddRange.Invoke(obj: null, parameters: new[] { target, value });
|
||||||
|
}
|
||||||
|
|
||||||
// Called via reflection.
|
// Called via reflection.
|
||||||
private static void CallPropertyAddRange<TElement>(object target, object source)
|
private static void CallPropertyAddRange<TElement>(object target, object source)
|
||||||
{
|
{
|
||||||
|
|
@ -214,92 +262,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ActivateProperties(
|
private static PropertyHelper FindPropertyHelper(PropertyHelper[] propertyHelpers, ParameterDescriptor property)
|
||||||
ControllerActionDescriptor actionDescriptor,
|
|
||||||
object controller,
|
|
||||||
IDictionary<string, object> properties)
|
|
||||||
{
|
{
|
||||||
var propertyHelpers = PropertyHelper.GetProperties(controller);
|
for (var i = 0; i < propertyHelpers.Length; i++)
|
||||||
for (var i = 0; i < actionDescriptor.BoundProperties.Count; i++)
|
|
||||||
{
|
{
|
||||||
var property = actionDescriptor.BoundProperties[i];
|
var propertyHelper = propertyHelpers[i];
|
||||||
|
if (string.Equals(propertyHelper.Name, property.Name, StringComparison.Ordinal))
|
||||||
PropertyHelper propertyHelper = null;
|
|
||||||
for (var j = 0; j < propertyHelpers.Length; j++)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(propertyHelpers[j].Name, property.Name, StringComparison.Ordinal))
|
return propertyHelper;
|
||||||
{
|
|
||||||
propertyHelper = propertyHelpers[j];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object value;
|
|
||||||
if (propertyHelper == null || !properties.TryGetValue(property.Name, out value))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var propertyType = propertyHelper.Property.PropertyType;
|
|
||||||
var metadata = _modelMetadataProvider.GetMetadataForType(propertyType);
|
|
||||||
|
|
||||||
if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPublic == true)
|
|
||||||
{
|
|
||||||
// Handle settable property. Do not set the property to null if the type is a non-nullable type.
|
|
||||||
if (value != null || metadata.IsReferenceOrNullableType)
|
|
||||||
{
|
|
||||||
propertyHelper.SetValue(controller, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyType.IsArray)
|
|
||||||
{
|
|
||||||
// Do not attempt to copy values into an array because an array's length is immutable. This choice
|
|
||||||
// is also consistent with MutableObjectModelBinder's handling of a read-only array property.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var target = propertyHelper.GetValue(controller);
|
|
||||||
if (value == null || target == null)
|
|
||||||
{
|
|
||||||
// Nothing to do when source or target is null.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!metadata.IsCollectionType)
|
|
||||||
{
|
|
||||||
// Not a collection model.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a read-only collection property.
|
|
||||||
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
|
||||||
metadata.ElementMetadata.ModelType);
|
|
||||||
propertyAddRange.Invoke(obj: null, parameters: new[] { target, value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IDictionary<string, object>> PopulateArgumentsAsync(
|
|
||||||
ControllerContext controllerContext,
|
|
||||||
IList<ParameterDescriptor> parameters,
|
|
||||||
IValueProvider valueProvider)
|
|
||||||
{
|
|
||||||
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
// Perf: Avoid allocations
|
|
||||||
for (var i = 0; i < parameters.Count; i++)
|
|
||||||
{
|
|
||||||
var parameter = parameters[i];
|
|
||||||
var modelBindingResult = await BindModelAsync(parameter, controllerContext, valueProvider);
|
|
||||||
if (modelBindingResult != null && modelBindingResult.Value.IsModelSet)
|
|
||||||
{
|
|
||||||
arguments[parameter.Name] = modelBindingResult.Value.Model;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return arguments;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
protected abstract Task<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext);
|
protected abstract Task<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext);
|
||||||
|
|
||||||
protected abstract Task<IDictionary<string, object>> BindActionArgumentsAsync();
|
protected abstract Task BindActionArgumentsAsync(IDictionary<string, object> arguments);
|
||||||
|
|
||||||
public virtual async Task InvokeAsync()
|
public virtual async Task InvokeAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -456,13 +456,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
|
|
||||||
Instance = CreateInstance();
|
Instance = CreateInstance();
|
||||||
|
|
||||||
var arguments = await BindActionArgumentsAsync();
|
var arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
await BindActionArgumentsAsync(arguments);
|
||||||
_actionExecutingContext = new ActionExecutingContext(
|
_actionExecutingContext = new ActionExecutingContext(Context, _filters, arguments, Instance);
|
||||||
Context,
|
|
||||||
_filters,
|
|
||||||
arguments,
|
|
||||||
Instance);
|
|
||||||
|
|
||||||
await InvokeActionFilterAsync();
|
await InvokeActionFilterAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2037,10 +2037,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>()))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var actionArgumentsBinder = new Mock<IControllerActionArgumentBinder>();
|
var argumentBinder = new Mock<IControllerArgumentBinder>();
|
||||||
actionArgumentsBinder.Setup(
|
argumentBinder
|
||||||
b => b.BindActionArgumentsAsync(It.IsAny<ControllerContext>(), It.IsAny<object>()))
|
.Setup(b => b.BindArgumentsAsync(
|
||||||
.Returns(Task.FromResult<IDictionary<string, object>>(new Dictionary<string, object>()));
|
It.IsAny<ControllerContext>(),
|
||||||
|
It.IsAny<object>(),
|
||||||
|
It.IsAny<IDictionary<string, object>>()))
|
||||||
|
.Returns(TaskCache.CompletedTask);
|
||||||
|
|
||||||
filterProvider
|
filterProvider
|
||||||
.SetupGet(fp => fp.Order)
|
.SetupGet(fp => fp.Order)
|
||||||
|
|
@ -2051,7 +2054,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
new[] { filterProvider.Object },
|
new[] { filterProvider.Object },
|
||||||
new MockControllerFactory(this),
|
new MockControllerFactory(this),
|
||||||
actionDescriptor,
|
actionDescriptor,
|
||||||
actionArgumentsBinder.Object,
|
argumentBinder.Object,
|
||||||
new IValueProviderFactory[0],
|
new IValueProviderFactory[0],
|
||||||
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
|
||||||
new DiagnosticListener("Microsoft.AspNetCore"),
|
new DiagnosticListener("Microsoft.AspNetCore"),
|
||||||
|
|
@ -2227,7 +2230,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
IFilterProvider[] filterProviders,
|
IFilterProvider[] filterProviders,
|
||||||
MockControllerFactory controllerFactory,
|
MockControllerFactory controllerFactory,
|
||||||
ControllerActionDescriptor descriptor,
|
ControllerActionDescriptor descriptor,
|
||||||
IControllerActionArgumentBinder controllerActionArgumentBinder,
|
IControllerArgumentBinder argumentBinder,
|
||||||
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DiagnosticSource diagnosticSource,
|
DiagnosticSource diagnosticSource,
|
||||||
|
|
@ -2237,7 +2240,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
CreateFilterCache(filterProviders),
|
CreateFilterCache(filterProviders),
|
||||||
controllerFactory,
|
controllerFactory,
|
||||||
descriptor,
|
descriptor,
|
||||||
controllerActionArgumentBinder,
|
argumentBinder,
|
||||||
valueProviderFactories,
|
valueProviderFactories,
|
||||||
logger,
|
logger,
|
||||||
diagnosticSource,
|
diagnosticSource,
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(result);
|
Assert.Empty(arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -68,12 +70,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(result);
|
Assert.Empty(arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -106,13 +110,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(1, result.Count);
|
Assert.Equal(1, arguments.Count);
|
||||||
Assert.Equal(value, result["foo"]);
|
Assert.Equal(value, arguments["foo"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -140,9 +146,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
It.IsAny<object>()));
|
It.IsAny<object>()));
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -169,6 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
|
|
@ -184,10 +193,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
It.IsAny<object>()));
|
It.IsAny<object>()));
|
||||||
|
|
||||||
var factory = GetModelBinderFactory(binder.Object);
|
var factory = GetModelBinderFactory(binder.Object);
|
||||||
|
var controller = new TestController();
|
||||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -212,6 +222,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -225,7 +237,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -251,6 +263,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
var binder = new Mock<IModelBinder>();
|
||||||
binder
|
binder
|
||||||
|
|
@ -269,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
var argumentBinder = GetArgumentBinder(factory, mockValidator.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
mockValidator
|
mockValidator
|
||||||
|
|
@ -295,14 +309,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var factory = GetModelBinderFactory("Hello");
|
var factory = GetModelBinderFactory("Hello");
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Hello", controller.StringProperty);
|
Assert.Equal("Hello", controller.StringProperty);
|
||||||
|
|
@ -324,15 +339,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var expected = new List<string> { "Hello", "World", "!!" };
|
var expected = new List<string> { "Hello", "World", "!!" };
|
||||||
var factory = GetModelBinderFactory(expected);
|
var factory = GetModelBinderFactory(expected);
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, controller.CollectionProperty);
|
Assert.Equal(expected, controller.CollectionProperty);
|
||||||
|
|
@ -356,18 +371,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var binder = new StubModelBinder(ModelBindingResult.Success(string.Empty, model: null));
|
var binder = new StubModelBinder(ModelBindingResult.Success(string.Empty, model: null));
|
||||||
var factory = GetModelBinderFactory(binder);
|
var factory = GetModelBinderFactory(binder);
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
// Some non default value.
|
// Some non default value.
|
||||||
controller.NonNullableProperty = -1;
|
controller.NonNullableProperty = -1;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(-1, controller.NonNullableProperty);
|
Assert.Equal(-1, controller.NonNullableProperty);
|
||||||
|
|
@ -387,18 +403,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var binder = new StubModelBinder(ModelBindingResult.Success(key: string.Empty, model: null));
|
var binder = new StubModelBinder(ModelBindingResult.Success(key: string.Empty, model: null));
|
||||||
var factory = GetModelBinderFactory(binder);
|
var factory = GetModelBinderFactory(binder);
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
// Some non default value.
|
// Some non default value.
|
||||||
controller.NullableProperty = -1;
|
controller.NullableProperty = -1;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(controller.NullableProperty);
|
Assert.Null(controller.NullableProperty);
|
||||||
|
|
@ -463,14 +480,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
});
|
});
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var factory = GetModelBinderFactory(inputValue);
|
var factory = GetModelBinderFactory(inputValue);
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
|
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedValue, propertyAccessor(controller));
|
Assert.Equal(expectedValue, propertyAccessor(controller));
|
||||||
|
|
@ -520,6 +538,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
var controllerContext = GetControllerContext(actionDescriptor);
|
var controllerContext = GetControllerContext(actionDescriptor);
|
||||||
|
var controller = new TestController();
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var binder = new StubModelBinder(bindingContext =>
|
var binder = new StubModelBinder(bindingContext =>
|
||||||
{
|
{
|
||||||
|
|
@ -540,10 +560,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory());
|
controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory());
|
||||||
|
|
||||||
var argumentBinder = GetArgumentBinder(factory);
|
var argumentBinder = GetArgumentBinder(factory);
|
||||||
var controller = new TestController();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await argumentBinder.BindActionArgumentsAsync(controllerContext, controller);
|
await argumentBinder.BindArgumentsAsync(controllerContext, controller, arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new string[] { "goodbye" }, controller.ArrayProperty); // Skipped
|
Assert.Equal(new string[] { "goodbye" }, controller.ArrayProperty); // Skipped
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
},
|
},
|
||||||
actionDescriptor: actionDescriptor);
|
actionDescriptor: actionDescriptor);
|
||||||
|
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
var modelState = testContext.ModelState;
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var arguments = await argumentBinder.BindActionArgumentsAsync(testContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(testContext, new TestController(), arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(modelState.IsValid);
|
Assert.False(modelState.IsValid);
|
||||||
|
|
@ -138,10 +139,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||||
},
|
},
|
||||||
actionDescriptor: actionDescriptor);
|
actionDescriptor: actionDescriptor);
|
||||||
|
|
||||||
|
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
var modelState = testContext.ModelState;
|
var modelState = testContext.ModelState;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var arguments = await argumentBinder.BindActionArgumentsAsync(testContext, new TestController());
|
await argumentBinder.BindArgumentsAsync(testContext, new TestController(), arguments);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue