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:
Ryan Nowak 2016-04-26 11:17:57 -07:00
parent 4d63ffa879
commit 128d74e2a0
9 changed files with 178 additions and 174 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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