[Fixes #7412] AspNetCore 2.1 breaks integration with 3rd party validation libraries

This commit is contained in:
Kiran Challa 2018-03-07 07:58:09 -08:00
parent a952313f1c
commit a0b1b15101
12 changed files with 478 additions and 211 deletions

View File

@ -232,15 +232,7 @@ namespace Microsoft.Extensions.DependencyInjection
return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders);
});
services.TryAddSingleton<ClientValidatorCache>();
services.TryAddSingleton<ParameterBinder>(s =>
{
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
var loggerFactory = s.GetRequiredService<ILoggerFactory>();
var metadataProvider = s.GetRequiredService<IModelMetadataProvider>();
var modelBinderFactory = s.GetRequiredService<IModelBinderFactory>();
var modelValidatorProvider = new CompositeModelValidatorProvider(options.ModelValidatorProviders);
return new ParameterBinder(metadataProvider, modelBinderFactory, modelValidatorProvider, loggerFactory);
});
services.TryAddSingleton<ParameterBinder>();
//
// Random Infrastructure

View File

@ -1,7 +1,6 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
@ -11,12 +10,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
/// <summary>
/// The default implementation of <see cref="IObjectModelValidator"/>.
/// </summary>
public class DefaultObjectValidator : IObjectModelValidator
public class DefaultObjectValidator : ObjectModelValidator
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly ValidatorCache _validatorCache;
private readonly IModelValidatorProvider _validatorProvider;
/// <summary>
/// Initializes a new instance of <see cref="DefaultObjectValidator"/>.
/// </summary>
@ -25,44 +20,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public DefaultObjectValidator(
IModelMetadataProvider modelMetadataProvider,
IList<IModelValidatorProvider> validatorProviders)
: base(modelMetadataProvider, validatorProviders)
{
if (modelMetadataProvider == null)
{
throw new ArgumentNullException(nameof(modelMetadataProvider));
}
if (validatorProviders == null)
{
throw new ArgumentNullException(nameof(validatorProviders));
}
_modelMetadataProvider = modelMetadataProvider;
_validatorCache = new ValidatorCache();
_validatorProvider = new CompositeModelValidatorProvider(validatorProviders);
}
/// <inheritdoc />
public void Validate(
public override ValidationVisitor GetValidationVisitor(
ActionContext actionContext,
ValidationStateDictionary validationState,
string prefix,
object model)
IModelValidatorProvider validatorProvider,
ValidatorCache validatorCache,
IModelMetadataProvider metadataProvider,
ValidationStateDictionary validationState)
{
if (actionContext == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
var visitor = new ValidationVisitor(
return new ValidationVisitor(
actionContext,
_validatorProvider,
_validatorCache,
_modelMetadataProvider,
validatorProvider,
validatorCache,
metadataProvider,
validationState);
var metadata = model == null ? null : _modelMetadataProvider.GetMetadataForType(model.GetType());
visitor.Validate(metadata, prefix, model);
}
}
}

View File

@ -0,0 +1,108 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
/// <summary>
/// Provides a base <see cref="IObjectModelValidator"/> implementation for validating an object graph.
/// </summary>
public abstract class ObjectModelValidator : IObjectModelValidator
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly ValidatorCache _validatorCache;
private readonly CompositeModelValidatorProvider _validatorProvider;
/// <summary>
/// Initializes a new instance of <see cref="ObjectModelValidator"/>.
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="ModelMetadataProvider"/>.</param>
/// <param name="validatorProviders">The list of <see cref="IModelValidatorProvider"/>.</param>
public ObjectModelValidator(
IModelMetadataProvider modelMetadataProvider,
IList<IModelValidatorProvider> validatorProviders)
{
if (modelMetadataProvider == null)
{
throw new ArgumentNullException(nameof(modelMetadataProvider));
}
if (validatorProviders == null)
{
throw new ArgumentNullException(nameof(validatorProviders));
}
_modelMetadataProvider = modelMetadataProvider;
_validatorCache = new ValidatorCache();
_validatorProvider = new CompositeModelValidatorProvider(validatorProviders);
}
/// <inheritdoc />
public virtual void Validate(
ActionContext actionContext,
ValidationStateDictionary validationState,
string prefix,
object model)
{
var visitor = GetValidationVisitor(
actionContext,
_validatorProvider,
_validatorCache,
_modelMetadataProvider,
validationState);
var metadata = model == null ? null : _modelMetadataProvider.GetMetadataForType(model.GetType());
visitor.Validate(metadata, prefix, model, alwaysValidateAtTopLevel: false);
}
/// <summary>
/// Validates the provided object model.
/// If <paramref name="model"/> is <see langword="null"/> and the <paramref name="metadata"/>'s
/// <see cref="ModelMetadata.IsRequired"/> is <see langword="true"/>, will add one or more
/// model state errors that <see cref="Validate(ActionContext, ValidationStateDictionary, string, object)"/>
/// would not.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="validationState">The <see cref="ValidationStateDictionary"/>.</param>
/// <param name="prefix">The model prefix key.</param>
/// <param name="model">The model object.</param>
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
public virtual void Validate(
ActionContext actionContext,
ValidationStateDictionary validationState,
string prefix,
object model,
ModelMetadata metadata)
{
var visitor = GetValidationVisitor(
actionContext,
_validatorProvider,
_validatorCache,
_modelMetadataProvider,
validationState);
visitor.Validate(metadata, prefix, model, alwaysValidateAtTopLevel: metadata.IsRequired);
}
/// <summary>
/// Gets a <see cref="ValidationVisitor"/> that traverses the object model graph and performs validation.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
/// <param name="validatorCache">The <see cref="ValidatorCache"/>.</param>
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="validationState">The <see cref="ValidationStateDictionary"/>.</param>
/// <returns>A <see cref="ValidationVisitor"/> which traverses the object model graph.</returns>
public abstract ValidationVisitor GetValidationVisitor(
ActionContext actionContext,
IModelValidatorProvider validatorProvider,
ValidatorCache validatorCache,
IModelMetadataProvider metadataProvider,
ValidationStateDictionary validationState);
}
}

View File

@ -18,9 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly IModelBinderFactory _modelBinderFactory;
private readonly IObjectModelValidator _validatorForBackCompatOnly;
private readonly IModelValidatorProvider _validatorProvider;
private readonly ValidatorCache _validatorCache;
private readonly IObjectModelValidator _objectModelValidator;
/// <summary>
/// <para>This constructor is obsolete and will be removed in a future version. The recommended alternative
@ -29,19 +27,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
/// <param name="validator">The <see cref="IObjectModelValidator"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative"
+ " is the overload that also takes an " + nameof(ILoggerFactory) + ".")]
public ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
IModelValidatorProvider validatorProvider)
: this(
modelMetadataProvider,
modelBinderFactory,
validatorProvider,
validatorForBackCompatOnly: null,
loggerFactory: NullLoggerFactory.Instance)
IObjectModelValidator validator)
: this(modelMetadataProvider, modelBinderFactory, validator, NullLoggerFactory.Instance)
{
}
@ -50,63 +43,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
/// <param name="validator">The <see cref="IObjectModelValidator"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
IModelValidatorProvider validatorProvider,
ILoggerFactory loggerFactory)
: this(
modelMetadataProvider,
modelBinderFactory,
validatorProvider,
validatorForBackCompatOnly: null,
loggerFactory: loggerFactory)
{
if (validatorProvider == null)
{
throw new ArgumentNullException(nameof(validatorProvider));
}
}
/// <summary>
/// <para>This constructor is obsolete and will be removed in a future version. The recommended alternative
/// is the overload that also takes an <see cref="IModelValidatorProvider"/> and <see cref="ILoggerFactory"/>
/// instead of an <see cref="IObjectModelValidator"/>.</para>
/// <para>Initializes a new instance of <see cref="ParameterBinder"/>.</para>
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
/// <param name="modelBinderFactory">The <see cref="IModelBinderFactory"/>.</param>
/// <param name="validator">The <see cref="IObjectModelValidator"/>.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative"
+ " is the overload that takes an " + nameof(IModelValidatorProvider) + " and " + nameof(ILoggerFactory)
+ " instead of an " + nameof(IObjectModelValidator) + ".")]
public ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
IObjectModelValidator validator)
: this(
modelMetadataProvider,
modelBinderFactory,
validatorProvider: null,
validatorForBackCompatOnly: validator,
loggerFactory: NullLoggerFactory.Instance)
{
// Note: When this obsolete constructor overload is removed, also remember
// to remove the validatorForBackCompatOnly field.
if (validator == null)
{
throw new ArgumentNullException(nameof(validator));
}
}
private ParameterBinder(
IModelMetadataProvider modelMetadataProvider,
IModelBinderFactory modelBinderFactory,
IModelValidatorProvider validatorProvider,
IObjectModelValidator validatorForBackCompatOnly,
IObjectModelValidator validator,
ILoggerFactory loggerFactory)
{
if (modelMetadataProvider == null)
@ -119,6 +61,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(modelBinderFactory));
}
if (validator == null)
{
throw new ArgumentNullException(nameof(validator));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
@ -126,9 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
_modelMetadataProvider = modelMetadataProvider;
_modelBinderFactory = modelBinderFactory;
_validatorProvider = validatorProvider;
_validatorForBackCompatOnly = validatorForBackCompatOnly;
_validatorCache = new ValidatorCache();
_objectModelValidator = validator;
Logger = loggerFactory.CreateLogger(GetType());
}
@ -279,14 +224,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var modelBindingResult = modelBindingContext.Result;
if (_validatorForBackCompatOnly != null)
var baseObjectValidator = _objectModelValidator as ObjectModelValidator;
if (baseObjectValidator == null)
{
// Since we don't have access to an IModelValidatorProvider, fall back
// on back-compatibility logic. In this scenario, top-level validation
// attributes will be ignored like they were historically.
// For legacy implementations (which directly implemented IObjectModelValidator), fall back to the
// back-compatibility logic. In this scenario, top-level validation attributes will be ignored like
// they were historically.
if (modelBindingResult.IsModelSet)
{
_validatorForBackCompatOnly.Validate(
_objectModelValidator.Validate(
actionContext,
modelBindingContext.ValidationState,
modelBindingContext.ModelName,
@ -298,6 +244,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Logger.AttemptingToValidateParameterOrProperty(parameter, modelBindingContext);
EnforceBindRequiredAndValidate(
baseObjectValidator,
actionContext,
metadata,
modelBindingContext,
@ -309,7 +256,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
return modelBindingResult;
}
private void EnforceBindRequiredAndValidate(ActionContext actionContext, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult)
private void EnforceBindRequiredAndValidate(
ObjectModelValidator baseObjectValidator,
ActionContext actionContext,
ModelMetadata metadata,
ModelBindingContext modelBindingContext,
ModelBindingResult modelBindingResult)
{
if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired)
{
@ -321,18 +273,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
else if (modelBindingResult.IsModelSet || metadata.IsRequired)
{
// Enforce any other validation rules
var visitor = new ValidationVisitor(
baseObjectValidator.Validate(
actionContext,
_validatorProvider,
_validatorCache,
_modelMetadataProvider,
modelBindingContext.ValidationState);
visitor.Validate(
metadata,
modelBindingContext.ValidationState,
modelBindingContext.ModelName,
modelBindingResult.Model,
alwaysValidateAtTopLevel: metadata.IsRequired);
metadata);
}
}
}

View File

@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance),
modelBinderFactory,
modelMetadataProvider,

View File

@ -49,10 +49,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Verifiable();
var mockValidator = CreateMockValidator();
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var controller = new TestController();
var parameterBinder = GetParameterBinder(factory);
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -95,10 +104,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Verifiable();
var mockValidator = CreateMockValidator();
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
var factory = GetModelBinderFactory(binder.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var controller = new TestController();
var parameterBinder = GetParameterBinder(factory);
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -139,7 +156,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Returns(Task.CompletedTask);
var factory = GetModelBinderFactory(binder.Object);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController();
@ -149,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -176,7 +200,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Returns(Task.CompletedTask);
var factory = GetModelBinderFactory(binder.Object);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController();
@ -186,7 +217,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -221,7 +252,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
})
.Returns(Task.CompletedTask);
var factory = GetModelBinderFactory(binder.Object);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController();
@ -231,7 +269,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -259,7 +297,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var mockBinder = new Mock<IModelBinder>();
var factory = GetModelBinderFactory(mockBinder.Object);
var parameterBinder = GetParameterBinder(factory, CreateMockValidator().Object);
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
@ -270,6 +307,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
mockMetadataProvider
.Setup(p => p.GetMetadataForParameter(ParameterInfos.NoAttributesParameterInfo))
.Returns(modelMetadata.Object);
var parameterBinder = new ParameterBinder(
mockMetadataProvider.Object,
factory,
new DefaultObjectValidator(
mockMetadataProvider.Object,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -304,7 +348,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var mockBinder = new Mock<IModelBinder>();
var factory = GetModelBinderFactory(mockBinder.Object);
var parameterBinder = GetParameterBinder(factory, CreateMockValidator().Object);
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
@ -314,6 +357,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
mockMetadataProvider
.Setup(p => p.GetMetadataForType(typeof(Person)))
.Returns(modelMetadata.Object);
var parameterBinder = new ParameterBinder(
mockMetadataProvider.Object,
factory,
new DefaultObjectValidator(
mockMetadataProvider.Object,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -348,13 +398,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory("Hello");
var mockValidator = CreateMockValidator();
var mockValidator = new Mock<IModelValidator>();
mockValidator
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
.Returns(new[] { new ModelValidationResult("memberName", "some message") });
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = GetParameterBinder(factory, mockValidator.Object, modelMetadataProvider);
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
NullLoggerFactory.Instance);
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
@ -400,16 +456,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Returns(Task.CompletedTask);
var mockValidator = CreateMockValidator();
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
var factory = GetModelBinderFactory(binder.Object);
var controller = new TestController();
var parameterBinder = GetParameterBinder(factory, mockValidator.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -437,14 +501,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
var mockValidator = CreateMockValidator();
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
.Returns(new[] { new ModelValidationResult("memberName", "some message") });
var factory = GetModelBinderFactory("Hello");
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = GetParameterBinder(factory, mockValidator.Object, modelMetadataProvider);
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
@ -466,6 +537,51 @@ namespace Microsoft.AspNetCore.Mvc.Internal
controllerContext.ModelState["memberName"].Errors.Single().ErrorMessage);
}
[Fact]
public async Task DoesNotValidate_ForControllerProperties_IfObjectValidatorDoesNotInheritFromBase()
{
// Arrange
var actionDescriptor = GetActionDescriptor();
actionDescriptor.BoundProperties.Add(
new ParameterDescriptor
{
Name = nameof(TestController.ValidatedProperty),
ParameterType = typeof(string),
});
var controllerContext = GetControllerContext(actionDescriptor);
var controller = new TestController();
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
var factory = GetModelBinderFactory("Hello");
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<ActionContext>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
mockValidator.Object,
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
// Assert
Assert.True(controllerContext.ModelState.IsValid);
}
[Fact]
public async Task BindActionArgumentsAsync_DoesNotCallValidator_ForControllerProperties_IfModelBinderFails()
{
@ -487,15 +603,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
.Setup(b => b.BindModelAsync(It.IsAny<DefaultModelBindingContext>()))
.Returns(Task.CompletedTask);
var mockValidator = CreateMockValidator();
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
var factory = GetModelBinderFactory(binder.Object);
var parameterBinder = GetParameterBinder(factory, mockValidator.Object);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { GetModelValidatorProvider(mockValidator.Object) }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -525,14 +649,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
var factory = GetModelBinderFactory("Hello");
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -562,13 +692,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var expected = new List<string> { "Hello", "World", "!!" };
var factory = GetModelBinderFactory(expected);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -598,8 +735,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Some non default value.
controller.NonNullableProperty = -1;
@ -608,7 +751,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -636,8 +779,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Some non default value.
controller.NullableProperty = -1;
@ -646,7 +795,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -695,7 +844,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Some non default value.
controller.NullableProperty = -1;
@ -704,7 +860,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -754,7 +910,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binder = new StubModelBinder(ModelBindingResult.Success(model: null));
var factory = GetModelBinderFactory(binder);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Some non default value.
controller.NullableProperty = -1;
@ -763,7 +926,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -837,13 +1000,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var arguments = new Dictionary<string, object>(StringComparer.Ordinal);
var factory = GetModelBinderFactory(inputValue);
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -908,13 +1078,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var factory = GetModelBinderFactory(binder);
controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory());
var parameterBinder = GetParameterBinder(factory);
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
factory,
new DefaultObjectValidator(
modelMetadataProvider,
new IModelValidatorProvider[] { }),
NullLoggerFactory.Instance);
// Act
var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate(
parameterBinder,
factory,
TestModelMetadataProvider.CreateDefaultProvider(),
modelMetadataProvider,
actionDescriptor);
await binderDelegate(controllerContext, controller, arguments);
@ -1004,7 +1181,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var parameterBinder = new Mock<ParameterBinder>(
new EmptyModelMetadataProvider(),
factory,
modelValidatorProvider);
new DefaultObjectValidator(modelMetadataProvider, new[] { modelValidatorProvider }),
NullLoggerFactory.Instance);
parameterBinder.Setup(p => p.BindModelAsync(
It.IsAny<ActionContext>(),
It.IsAny<IModelBinder>(),
@ -1092,6 +1270,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
};
}
private static IModelValidatorProvider GetModelValidatorProvider(IModelValidator validator = null)
{
if (validator == null)
{
validator = Mock.Of<IModelValidator>();
}
var validatorProvider = new Mock<IModelValidatorProvider>();
validatorProvider
.Setup(p => p.CreateValidators(It.IsAny<ModelValidatorProviderContext>()))
.Callback<ModelValidatorProviderContext>(context =>
{
foreach (var result in context.Results)
{
result.Validator = validator;
result.IsReusable = true;
}
});
return validatorProvider.Object;
}
private static ModelBinderFactory GetModelBinderFactory(object model = null)
{
var binder = new Mock<IModelBinder>();
@ -1118,12 +1317,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static ParameterBinder GetParameterBinder(
IModelBinderFactory factory = null,
IModelValidator validator = null,
IModelMetadataProvider modelMetadataProvider = null)
IObjectModelValidator validator = null,
IModelMetadataProvider modelMetadataProvider = null,
IModelValidatorProvider modelValidatorProvider = null)
{
if (validator == null)
{
validator = CreateMockValidator().Object;
validator = CreateObjectValidator();
}
if (factory == null)
@ -1131,32 +1331,32 @@ namespace Microsoft.AspNetCore.Mvc.Internal
factory = TestModelBinderFactory.CreateDefault();
}
var validatorProvider = new Mock<IModelValidatorProvider>();
validatorProvider
.Setup(p => p.CreateValidators(It.IsAny<ModelValidatorProviderContext>()))
.Callback<ModelValidatorProviderContext>(context =>
{
foreach (var result in context.Results)
{
result.Validator = validator;
result.IsReusable = true;
}
});
if (modelValidatorProvider == null)
{
modelValidatorProvider = Mock.Of<IModelValidatorProvider>();
}
var metadataProvider = modelMetadataProvider ?? TestModelMetadataProvider.CreateDefaultProvider();
var objectModelValidator = new DefaultObjectValidator(
metadataProvider,
new[] { modelValidatorProvider });
return new ParameterBinder(
modelMetadataProvider ?? TestModelMetadataProvider.CreateDefaultProvider(),
metadataProvider,
factory,
validatorProvider.Object,
objectModelValidator,
NullLoggerFactory.Instance);
}
private static Mock<IModelValidator> CreateMockValidator()
private static IObjectModelValidator CreateObjectValidator()
{
var mockValidator = new Mock<IModelValidator>(MockBehavior.Strict);
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<ModelValidationContext>()));
return mockValidator;
It.IsAny<ActionContext>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
return mockValidator.Object;
}
// No need for bind-related attributes on properties in this controller class. Properties are added directly
@ -1258,5 +1458,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
}
}
private class TestObjectModelValidator : IObjectModelValidator
{
public void Validate(
ActionContext actionContext,
ValidationStateDictionary validationState,
string prefix,
object model)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -448,7 +448,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
: base(
new EmptyModelMetadataProvider(),
TestModelBinderFactory.CreateDefault(),
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance)
{
_actionParameters = actionParameters;

View File

@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var parameterBinder = new ParameterBinder(
metadataProvider,
factory.Object,
CreateMockValidatorProvider(),
Mock.Of< IObjectModelValidator>(),
NullLoggerFactory.Instance);
var controllerContext = GetControllerContext();
@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
var argumentBinder = new ParameterBinder(
metadataProvider,
factory.Object,
CreateMockValidatorProvider(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
var valueProvider = new SimpleValueProvider
@ -364,10 +364,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
return new ParameterBinder(
mockModelMetadataProvider.Object,
mockModelBinderFactory.Object,
CreateMockValidatorProvider(validator),
new DefaultObjectValidator(
mockModelMetadataProvider.Object,
new[] { GetModelValidatorProvider(validator) }),
NullLoggerFactory.Instance);
}
private static IModelValidatorProvider GetModelValidatorProvider(IModelValidator validator = null)
{
if (validator == null)
{
validator = Mock.Of<IModelValidator>();
}
var validatorProvider = new Mock<IModelValidatorProvider>();
validatorProvider
.Setup(p => p.CreateValidators(It.IsAny<ModelValidatorProviderContext>()))
.Callback<ModelValidatorProviderContext>(context =>
{
foreach (var result in context.Results)
{
result.Validator = validator;
result.IsReusable = true;
}
});
return validatorProvider.Object;
}
private static ParameterBinder CreateBackCompatParameterBinder(
ModelMetadata modelMetadata,
IObjectModelValidator validator)

View File

@ -68,7 +68,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return new ParameterBinder(
metadataProvider,
new ModelBinderFactory(metadataProvider, options, serviceProvider),
new CompositeModelValidatorProvider(GetModelValidatorProviders(options)),
new DefaultObjectValidator(
metadataProvider,
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }),
NullLoggerFactory.Instance);
}
@ -90,7 +92,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return new ParameterBinder(
metadataProvider,
new ModelBinderFactory(metadataProvider, options, services),
new CompositeModelValidatorProvider(GetModelValidatorProviders(options)),
new DefaultObjectValidator(
metadataProvider,
new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }),
NullLoggerFactory.Instance);
}

View File

@ -490,7 +490,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
TestModelBinderFactory.CreateDefault(),
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
return new PageActionInvokerProvider(

View File

@ -1239,10 +1239,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
factory = TestModelBinderFactory.CreateDefault();
}
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
return new ParameterBinder(
TestModelMetadataProvider.CreateDefaultProvider(),
metadataProvider,
factory,
validator,
new DefaultObjectValidator(metadataProvider, new[] { validator }),
NullLoggerFactory.Instance);
}

View File

@ -8,6 +8,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
@ -35,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
// Act
@ -60,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
// Act
@ -84,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
// Act
@ -109,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
// Act
@ -133,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
// Act
@ -158,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance);
// Act
@ -533,12 +534,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();
var binder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
validatorProvider,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
NullLoggerFactory.Instance);
var factory = PageBinderFactory.CreatePropertyBinder(binder, modelMetadataProvider, modelBinderFactory, actionDescriptor);
@ -657,9 +659,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelBinderFactory = TestModelBinderFactory.CreateDefault();
var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();
var parameterBinder = new ParameterBinder(modelMetadataProvider, modelBinderFactory, validatorProvider, NullLoggerFactory.Instance);
var parameterBinder = new ParameterBinder(
modelMetadataProvider,
modelBinderFactory,
new DefaultObjectValidator(
modelMetadataProvider,
new[] { TestModelValidatorProvider.CreateDefaultProvider() }),
NullLoggerFactory.Instance);
var factory = PageBinderFactory.CreateHandlerBinder(
parameterBinder,
@ -748,7 +755,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
: base(
TestModelMetadataProvider.CreateDefaultProvider(),
TestModelBinderFactory.CreateDefault(),
Mock.Of<IModelValidatorProvider>(),
Mock.Of<IObjectModelValidator>(),
NullLoggerFactory.Instance)
{
_args = args;