From c67236141bc2d3d439c26e43c6785a81e1a27fae Mon Sep 17 00:00:00 2001 From: Harsh Gupta Date: Mon, 6 Apr 2015 15:55:55 -0700 Subject: [PATCH] Fixes 2304: The fix splits client validation and model validation into two separate hierarchies. Introduced ClientModelValidatorProvider in MvcOptions, which can be iterated to produce IClientModelValidators. As a result of this, HtmlGenerator code can be free of ActionBindingContext and directly consumes options. This also means that we do not modify the client validations during resource filters. --- src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs | 6 + .../Rendering/Html/DefaultHtmlGenerator.cs | 25 +- .../DataAnnotationsMetadataProvider.cs | 9 +- .../Metadata/DefaultModelMetadata.cs | 2 +- .../DefaultValidationMetadataProvider.cs | 14 +- .../Metadata/ValidationMetadata.cs | 2 +- .../ClientValidatorProviderContext.cs | 50 ++++ .../Validation/CompareAttributeAdapter.cs | 2 +- .../CompositeClientModelValidatorProvider.cs | 39 +++ .../CompositeModelValidatorProvider.cs | 7 +- ...tationsClientModelValidatorOfTAttribute.cs | 50 ++++ ...AnnotationsClientModelValidatorProvider.cs | 96 +++++++ .../DataAnnotationsModelValidator.cs | 19 +- ...taAnnotationsModelValidatorOfTAttribute.cs | 21 -- .../DataAnnotationsModelValidatorProvider.cs | 95 +------ .../Validation/DataTypeAttributeAdapter.cs | 2 +- .../DefaultClientModelValidatorProvider.cs | 28 ++ .../IClientModelValidatorProvider.cs | 18 ++ .../ICompositeModelValidatorProvider.cs | 12 - .../Validation/MaxLengthAttributeAdapter.cs | 2 +- .../Validation/MinLengthAttributeAdapter.cs | 2 +- .../Validation/RangeAttributeAdapter.cs | 2 +- .../RegularExpressionAttributeAdapter.cs | 2 +- .../Validation/RequiredAttributeAdapter.cs | 2 +- .../StringLengthAttributeAdapter.cs | 2 +- src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 4 + .../Rendering/DefaultHtmlGeneratorTest.cs | 7 +- .../Rendering/DefaultTemplatesUtilities.cs | 15 +- .../DataAnnotationsMetadataProviderTest.cs | 48 ++++ .../Metadata/DefaultValidationMetadataTest.cs | 116 ++++++++ ...tationsClientModelValidatorProviderTest.cs | 154 ++++++++++ ...taAnnotationsModelValidatorProviderTest.cs | 105 ------- .../DataAnnotationsModelValidatorTest.cs | 57 ---- ...DefaultModelClientValidatorProviderTest.cs | 269 ++++++++++++++++++ .../DefaultModelValidatorProviderTest.cs | 12 +- .../TestableHtmlGenerator.cs | 15 +- .../MvcOptionsSetupTest.cs | 16 ++ .../TestClientModelValidatorProvider.cs | 27 ++ 38 files changed, 1007 insertions(+), 347 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientValidatorProviderContext.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeClientModelValidatorProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorOfTAttribute.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorProvider.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorOfTAttribute.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultClientModelValidatorProvider.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IClientModelValidatorProvider.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ICompositeModelValidatorProvider.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultValidationMetadataTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsClientModelValidatorProviderTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelClientValidatorProviderTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index 19f1453b35..e94558a520 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -32,6 +32,7 @@ namespace Microsoft.AspNet.Mvc ValidationExcludeFilters = new List(); ModelMetadataDetailsProviders = new List(); ModelValidatorProviders = new List(); + ClientModelValidatorProviders = new List(); CacheProfiles = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -114,6 +115,11 @@ namespace Microsoft.AspNet.Mvc /// public IList ModelValidatorProviders { get; } + /// + /// Gets a list of instances. + /// + public IList ClientModelValidatorProviders { get; } + /// /// Gets a list of descriptors that represent used /// by this application. diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs index bacbfa51c8..3fc2d8cde7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs @@ -14,6 +14,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.Rendering.Expressions; using Microsoft.Framework.Internal; +using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders; namespace Microsoft.AspNet.Mvc.Rendering @@ -25,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.Rendering typeof(DefaultHtmlGenerator).GetTypeInfo().GetDeclaredMethod(nameof(ConvertEnumFromString)); private readonly AntiForgery _antiForgery; - private readonly IScopedInstance _bindingContextAccessor; + private readonly IClientModelValidatorProvider _clientModelValidatorProvider; private readonly IModelMetadataProvider _metadataProvider; private readonly IUrlHelper _urlHelper; private readonly IHtmlEncoder _htmlEncoder; @@ -33,15 +34,22 @@ namespace Microsoft.AspNet.Mvc.Rendering /// /// Initializes a new instance of the class. /// + /// The instance which is used to generate anti-forgery + /// tokens. + /// The accessor for . + /// The . + /// The . + /// The . public DefaultHtmlGenerator( [NotNull] AntiForgery antiForgery, - [NotNull] IScopedInstance bindingContextAccessor, + [NotNull] IOptions optionsAccessor, [NotNull] IModelMetadataProvider metadataProvider, [NotNull] IUrlHelper urlHelper, [NotNull] IHtmlEncoder htmlEncoder) { _antiForgery = antiForgery; - _bindingContextAccessor = bindingContextAccessor; + var clientValidatorProviders = optionsAccessor.Options.ClientModelValidatorProviders; + _clientModelValidatorProvider = new CompositeClientModelValidatorProvider(clientValidatorProviders); _metadataProvider = metadataProvider; _urlHelper = urlHelper; _htmlEncoder = htmlEncoder; @@ -715,7 +723,6 @@ namespace Microsoft.AspNet.Mvc.Rendering ModelExplorer modelExplorer, string expression) { - var validatorProvider = _bindingContextAccessor.Value.ValidatorProvider; modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider); var validationContext = new ClientModelValidationContext( @@ -723,15 +730,11 @@ namespace Microsoft.AspNet.Mvc.Rendering _metadataProvider, viewContext.HttpContext.RequestServices); - var validatorProviderContext = new ModelValidatorProviderContext(modelExplorer.Metadata); - validatorProvider.GetValidators(validatorProviderContext); + var validatorProviderContext = new ClientValidatorProviderContext(modelExplorer.Metadata); + _clientModelValidatorProvider.GetValidators(validatorProviderContext); var validators = validatorProviderContext.Validators; - - return - validators - .OfType() - .SelectMany(v => v.GetClientValidationRules(validationContext)); + return validators.SelectMany(v => v.GetClientValidationRules(validationContext)); } /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs index 10233763d6..6f5a2d0ab3 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs @@ -213,7 +213,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { foreach (var attribute in context.Attributes.OfType()) { - context.ValidationMetadata.ValiatorMetadata.Add(attribute); + // If another provider has already added this attribute, do not repeat it. + // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and + // IClientModelValidator) to be added to the ValidationMetadata twice. + // This is to ensure we do not end up with duplication validation rules on the client side. + if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) + { + context.ValidationMetadata.ValidatorMetadata.Add(attribute); + } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs index f995540ccf..a1f7857865 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs @@ -405,7 +405,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { if (_validatorMetadata == null) { - _validatorMetadata = new ReadOnlyCollection(ValidationMetadata.ValiatorMetadata); + _validatorMetadata = new ReadOnlyCollection(ValidationMetadata.ValidatorMetadata); } return _validatorMetadata; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultValidationMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultValidationMetadataProvider.cs index cb6b919511..42aa7a769d 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultValidationMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultValidationMetadataProvider.cs @@ -15,9 +15,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// public void GetValidationMetadata([NotNull] ValidationMetadataProviderContext context) { - foreach (var attribute in context.Attributes.OfType()) + foreach (var attribute in context.Attributes) { - context.ValidationMetadata.ValiatorMetadata.Add(attribute); + if (attribute is IModelValidator || attribute is IClientModelValidator) + { + // If another provider has already added this attribute, do not repeat it. + // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and + // IClientModelValidator) to be added to the ValidationMetadata twice. + // This is to ensure we do not end up with duplication validation rules on the client side. + if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) + { + context.ValidationMetadata.ValidatorMetadata.Add(attribute); + } + } } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs index 51b8775a7e..f6031272e6 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ValidationMetadata.cs @@ -17,6 +17,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// implementations should store metadata items /// in this list, to be consumed later by an . /// - public IList ValiatorMetadata { get; } = new List(); + public IList ValidatorMetadata { get; } = new List(); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientValidatorProviderContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientValidatorProviderContext.cs new file mode 100644 index 0000000000..289a143691 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientValidatorProviderContext.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// A context for . + /// + public class ClientValidatorProviderContext + { + /// + /// Creates a new . + /// + /// The for the model being validated. + /// + public ClientValidatorProviderContext(ModelMetadata modelMetadata) + { + ModelMetadata = modelMetadata; + } + + /// + /// Gets the . + /// + public ModelMetadata ModelMetadata { get; } + + /// + /// Gets the validator metadata. + /// + /// + /// This property provides convenience access to . + /// + public IReadOnlyList ValidatorMetadata + { + get + { + return ModelMetadata.ValidatorMetadata; + } + } + + /// + /// Gets the list of instances. + /// instances should add validators to this list when + /// + /// is called. + /// + public IList Validators { get; } = new List(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompareAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompareAttributeAdapter.cs index 2239c7d4a7..50a51f2e63 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompareAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompareAttributeAdapter.cs @@ -8,7 +8,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class CompareAttributeAdapter : DataAnnotationsModelValidator + public class CompareAttributeAdapter : DataAnnotationsClientModelValidator { public CompareAttributeAdapter([NotNull] CompareAttribute attribute) : base(new CompareAttributeWrapper(attribute)) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeClientModelValidatorProvider.cs new file mode 100644 index 0000000000..37f3d176aa --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeClientModelValidatorProvider.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// Aggregate of s that delegates to its underlying providers. + /// + public class CompositeClientModelValidatorProvider : IClientModelValidatorProvider + { + /// + /// Initializes a new instance of . + /// + /// + /// A collection of instances. + /// + public CompositeClientModelValidatorProvider([NotNull] IEnumerable providers) + { + ValidatorProviders = new List(providers); + } + + /// + /// Gets a list of . + /// + public IReadOnlyList ValidatorProviders { get; } + + /// + public void GetValidators(ClientValidatorProviderContext context) + { + foreach (var validatorProvider in ValidatorProviders) + { + validatorProvider.GetValidators(context); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs index ff4751429a..faab02823f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/CompositeModelValidatorProvider.cs @@ -7,9 +7,9 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { /// - /// Default implementation for . + /// Aggregate of s that delegates to its underlying providers. /// - public class CompositeModelValidatorProvider : ICompositeModelValidatorProvider + public class CompositeModelValidatorProvider : IModelValidatorProvider { /// /// Initializes a new instance of . @@ -22,6 +22,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation ValidatorProviders = new List(providers); } + /// + /// Gets the list of . + /// public IReadOnlyList ValidatorProviders { get; } public void GetValidators(ModelValidatorProviderContext context) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorOfTAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorOfTAttribute.cs new file mode 100644 index 0000000000..3401dec76a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorOfTAttribute.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 System.ComponentModel.DataAnnotations; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// An implementation of which understands data annotation attributes. + /// + /// The type of the attribute. + public abstract class DataAnnotationsClientModelValidator : IClientModelValidator + where TAttribute : ValidationAttribute + { + /// + /// Create a new instance of . + /// + /// The instance to validate. + public DataAnnotationsClientModelValidator(TAttribute attribute) + { + Attribute = attribute; + } + + /// + /// Gets the type of the associated with this instance. + /// + public TAttribute Attribute + { + get; + } + + /// + public abstract IEnumerable GetClientValidationRules( + ClientModelValidationContext context); + + /// + /// Gets the error message formatted using the . + /// + /// The associated with the model annotated with + /// . + /// Formatted error string. + protected virtual string GetErrorMessage([NotNull] ModelMetadata modelMetadata) + { + return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName()); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorProvider.cs new file mode 100644 index 0000000000..9691877d7c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsClientModelValidatorProvider.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// An implementation of which provides client validators + /// for attributes which derive from . It also provides + /// a validator for types which implement . + /// The logic to support + /// is implemented in . + /// + public class DataAnnotationsClientModelValidatorProvider : IClientModelValidatorProvider + { + // A factory for validators based on ValidationAttribute. + internal delegate IClientModelValidator + DataAnnotationsClientModelValidationFactory(ValidationAttribute attribute); + + private readonly Dictionary _attributeFactories = + BuildAttributeFactoriesDictionary(); + + internal Dictionary AttributeFactories + { + get { return _attributeFactories; } + } + + /// + public void GetValidators(ClientValidatorProviderContext context) + { + foreach (var attribute in context.ValidatorMetadata.OfType()) + { + DataAnnotationsClientModelValidationFactory factory; + if (_attributeFactories.TryGetValue(attribute.GetType(), out factory)) + { + context.Validators.Add(factory(attribute)); + } + } + } + + private static Dictionary BuildAttributeFactoriesDictionary() + { + return new Dictionary() + { + { + typeof(RegularExpressionAttribute), + (attribute) => new RegularExpressionAttributeAdapter((RegularExpressionAttribute)attribute) + }, + { + typeof(MaxLengthAttribute), + (attribute) => new MaxLengthAttributeAdapter((MaxLengthAttribute)attribute) + }, + { + typeof(MinLengthAttribute), + (attribute) => new MinLengthAttributeAdapter((MinLengthAttribute)attribute) + }, + { + typeof(CompareAttribute), + (attribute) => new CompareAttributeAdapter((CompareAttribute)attribute) + }, + { + typeof(RequiredAttribute), + (attribute) => new RequiredAttributeAdapter((RequiredAttribute)attribute) + }, + { + typeof(RangeAttribute), + (attribute) => new RangeAttributeAdapter((RangeAttribute)attribute) + }, + { + typeof(StringLengthAttribute), + (attribute) => new StringLengthAttributeAdapter((StringLengthAttribute)attribute) + }, + { + typeof(CreditCardAttribute), + (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "creditcard") + }, + { + typeof(EmailAddressAttribute), + (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "email") + }, + { + typeof(PhoneAttribute), + (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "phone") + }, + { + typeof(UrlAttribute), + (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "url") + } + }; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidator.cs index f01b2c3d13..de0d77752a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidator.cs @@ -9,7 +9,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class DataAnnotationsModelValidator : IModelValidator, IClientModelValidator + public class DataAnnotationsModelValidator : IModelValidator { public DataAnnotationsModelValidator([NotNull] ValidationAttribute attribute) { @@ -61,22 +61,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation return Enumerable.Empty(); } - - public virtual IEnumerable GetClientValidationRules( - [NotNull] ClientModelValidationContext context) - { - var customValidator = Attribute as IClientModelValidator; - if (customValidator != null) - { - return customValidator.GetClientValidationRules(context); - } - - return Enumerable.Empty(); - } - - protected virtual string GetErrorMessage([NotNull] ModelMetadata modelMetadata) - { - return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName()); - } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorOfTAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorOfTAttribute.cs deleted file mode 100644 index 653dc58b1a..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorOfTAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.AspNet.Mvc.ModelBinding.Validation -{ - public class DataAnnotationsModelValidator : DataAnnotationsModelValidator - where TAttribute : ValidationAttribute - { - public DataAnnotationsModelValidator(TAttribute attribute) - : base(attribute) - { - } - - protected new TAttribute Attribute - { - get { return (TAttribute)base.Attribute; } - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs index 8487181e25..9354a96c4b 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs @@ -5,114 +5,29 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { /// - /// An implementation of which providers validators + /// An implementation of which provides validators /// for attributes which derive from . It also provides - /// a validator for types which implement . To support - /// client side validation, you can either register adapters through the static methods - /// on this class, or by having your validation attributes implement - /// . The logic to support - /// is implemented in . + /// a validator for types which implement . /// public class DataAnnotationsModelValidatorProvider : IModelValidatorProvider { - // A factory for validators based on ValidationAttribute. - internal delegate IModelValidator DataAnnotationsModelValidationFactory(ValidationAttribute attribute); - - // Factories for validation attributes - private static readonly DataAnnotationsModelValidationFactory _defaultAttributeFactory = - (attribute) => new DataAnnotationsModelValidator(attribute); - - // Factories for IValidatableObject models - private static readonly DataAnnotationsValidatableObjectAdapterFactory _defaultValidatableFactory = - () => new ValidatableObjectAdapter(); - - private readonly Dictionary _attributeFactories = - BuildAttributeFactoriesDictionary(); - - // A factory for validators based on IValidatableObject - private delegate IModelValidator DataAnnotationsValidatableObjectAdapterFactory(); - - internal Dictionary AttributeFactories - { - get { return _attributeFactories; } - } - public void GetValidators(ModelValidatorProviderContext context) { foreach (var attribute in context.ValidatorMetadata.OfType()) { - DataAnnotationsModelValidationFactory factory; - if (!_attributeFactories.TryGetValue(attribute.GetType(), out factory)) - { - factory = _defaultAttributeFactory; - } - - context.Validators.Add(factory(attribute)); + context.Validators.Add(new DataAnnotationsModelValidator(attribute)); } // Produce a validator if the type supports IValidatableObject if (typeof(IValidatableObject).IsAssignableFrom(context.ModelMetadata.ModelType)) { - context.Validators.Add(_defaultValidatableFactory()); + context.Validators.Add(new ValidatableObjectAdapter()); } } - - private static Dictionary BuildAttributeFactoriesDictionary() - { - var dict = new Dictionary(); - AddValidationAttributeAdapter(dict, typeof(RegularExpressionAttribute), - (attribute) => new RegularExpressionAttributeAdapter((RegularExpressionAttribute)attribute)); - - AddValidationAttributeAdapter(dict, typeof(MaxLengthAttribute), - (attribute) => new MaxLengthAttributeAdapter((MaxLengthAttribute)attribute)); - - AddValidationAttributeAdapter(dict, typeof(MinLengthAttribute), - (attribute) => new MinLengthAttributeAdapter((MinLengthAttribute)attribute)); - - AddValidationAttributeAdapter(dict, typeof(CompareAttribute), - (attribute) => new CompareAttributeAdapter((CompareAttribute)attribute)); - - AddValidationAttributeAdapter(dict, typeof(RequiredAttribute), - (attribute) => new RequiredAttributeAdapter((RequiredAttribute)attribute)); - - AddValidationAttributeAdapter(dict, typeof(RangeAttribute), - (attribute) => new RangeAttributeAdapter((RangeAttribute)attribute)); - - AddValidationAttributeAdapter(dict, typeof(StringLengthAttribute), - (attribute) => new StringLengthAttributeAdapter((StringLengthAttribute)attribute)); - - AddDataTypeAttributeAdapter(dict, typeof(CreditCardAttribute), "creditcard"); - AddDataTypeAttributeAdapter(dict, typeof(EmailAddressAttribute), "email"); - AddDataTypeAttributeAdapter(dict, typeof(PhoneAttribute), "phone"); - AddDataTypeAttributeAdapter(dict, typeof(UrlAttribute), "url"); - - return dict; - } - - private static void AddValidationAttributeAdapter( - Dictionary dictionary, - Type validationAttributeType, - DataAnnotationsModelValidationFactory factory) - { - if (validationAttributeType != null) - { - dictionary.Add(validationAttributeType, factory); - } - } - - private static void AddDataTypeAttributeAdapter( - Dictionary dictionary, - Type attributeType, - string ruleName) - { - AddValidationAttributeAdapter( - dictionary, - attributeType, - (attribute) => new DataTypeAttributeAdapter((DataTypeAttribute)attribute, ruleName)); - } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataTypeAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataTypeAttributeAdapter.cs index ba6aad4407..a28bc4cc00 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataTypeAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataTypeAttributeAdapter.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation /// A validation adapter that is used to map 's to a single client side validation /// rule. /// - public class DataTypeAttributeAdapter : DataAnnotationsModelValidator + public class DataTypeAttributeAdapter : DataAnnotationsClientModelValidator { public DataTypeAttributeAdapter(DataTypeAttribute attribute, [NotNull] string ruleName) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultClientModelValidatorProvider.cs new file mode 100644 index 0000000000..6e443b5e14 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultClientModelValidatorProvider.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// A default implementation of . + /// + /// + /// The provides validators from + /// instances in . + /// + public class DefaultClientModelValidatorProvider : IClientModelValidatorProvider + { + /// + public void GetValidators(ClientValidatorProviderContext context) + { + foreach (var metadata in context.ValidatorMetadata) + { + var validator = metadata as IClientModelValidator; + if (validator != null) + { + context.Validators.Add(validator); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IClientModelValidatorProvider.cs new file mode 100644 index 0000000000..88f9b95ca7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IClientModelValidatorProvider.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + /// + /// Provides a collection of . + /// + public interface IClientModelValidatorProvider + { + /// + /// Gets set of s + /// by updating . + /// + /// The associated with this call. + void GetValidators(ClientValidatorProviderContext context); + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ICompositeModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ICompositeModelValidatorProvider.cs deleted file mode 100644 index 96497865cd..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ICompositeModelValidatorProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.Mvc.ModelBinding.Validation -{ - /// - /// Represents aggregate of s that delegates to its underlying providers. - /// - public interface ICompositeModelValidatorProvider : IModelValidatorProvider - { - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs index d777f8f2db..21433e2e20 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class MaxLengthAttributeAdapter : DataAnnotationsModelValidator + public class MaxLengthAttributeAdapter : DataAnnotationsClientModelValidator { public MaxLengthAttributeAdapter(MaxLengthAttribute attribute) : base(attribute) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs index bf6fcca7f1..72ca78c5eb 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class MinLengthAttributeAdapter : DataAnnotationsModelValidator + public class MinLengthAttributeAdapter : DataAnnotationsClientModelValidator { public MinLengthAttributeAdapter(MinLengthAttribute attribute) : base(attribute) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs index b83cb2c7c9..2b1dc67b43 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class RangeAttributeAdapter : DataAnnotationsModelValidator + public class RangeAttributeAdapter : DataAnnotationsClientModelValidator { public RangeAttributeAdapter(RangeAttribute attribute) : base(attribute) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RegularExpressionAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RegularExpressionAttributeAdapter.cs index 094ff2feb3..f449f16c2e 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RegularExpressionAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RegularExpressionAttributeAdapter.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class RegularExpressionAttributeAdapter : DataAnnotationsModelValidator + public class RegularExpressionAttributeAdapter : DataAnnotationsClientModelValidator { public RegularExpressionAttributeAdapter(RegularExpressionAttribute attribute) : base(attribute) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs index fb6940634f..fe008dfcc5 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class RequiredAttributeAdapter : DataAnnotationsModelValidator + public class RequiredAttributeAdapter : DataAnnotationsClientModelValidator { public RequiredAttributeAdapter(RequiredAttribute attribute) : base(attribute) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs index dc18c738d2..51fad17e2f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs @@ -7,7 +7,7 @@ using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class StringLengthAttributeAdapter : DataAnnotationsModelValidator + public class StringLengthAttributeAdapter : DataAnnotationsClientModelValidator { public StringLengthAttributeAdapter(StringLengthAttribute attribute) : base(attribute) diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index e057662157..84507e2de9 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -72,6 +72,10 @@ namespace Microsoft.AspNet.Mvc options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider()); + // Set up client validators + options.ClientModelValidatorProviders.Add(new DefaultClientModelValidatorProvider()); + options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider()); + // Add types to be excluded from Validation options.ValidationExcludeFilters.Add(new SimpleTypesExcludeFilter()); options.ValidationExcludeFilters.Add(typeof(XObject)); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs index f863aeca69..11f3caa88c 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs @@ -585,9 +585,14 @@ namespace Microsoft.AspNet.Mvc.Rendering htmlEncoder, dataOptionsAccessor.Object); + var optionsAccessor = new Mock>(); + optionsAccessor + .SetupGet(o => o.Options) + .Returns(new MvcOptions()); + return new DefaultHtmlGenerator( antiForgery, - Mock.Of>(), + optionsAccessor.Object, metadataProvider, Mock.Of(), htmlEncoder); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs index c6d5e7c110..be82e85402 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultTemplatesUtilities.cs @@ -167,13 +167,12 @@ namespace Microsoft.AspNet.Mvc.Rendering var httpContext = new DefaultHttpContext(); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); - var bindingContext = new ActionBindingContext() - { - ValidatorProvider = new DataAnnotationsModelValidatorProvider(), - }; - - var bindingContextAccessor = new MockScopedInstance(); - bindingContextAccessor.Value = bindingContext; + var options = new MvcOptions(); + options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider()); + var optionsAccessor = new Mock>(); + optionsAccessor + .SetupGet(o => o.Options) + .Returns(options); var serviceProvider = new Mock(); serviceProvider @@ -191,7 +190,7 @@ namespace Microsoft.AspNet.Mvc.Rendering { htmlGenerator = new DefaultHtmlGenerator( GetAntiForgeryInstance(), - bindingContextAccessor, + optionsAccessor.Object, provider, urlHelper, new HtmlEncoder()); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataProviderTest.cs index a9c994d46e..96f0a510d8 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DataAnnotationsMetadataProviderTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata @@ -619,6 +620,53 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Equal(initialValue, context.BindingMetadata.IsReadOnly); } + [Fact] + public void GetValidationDetails_ValidatableObject_ReturnsObject() + { + // Arrange + var provider = new DataAnnotationsMetadataProvider(); + + var attribute = new TestValidationAttribute(); + var attributes = new Attribute[] { attribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, attributes); + + // Act + provider.GetValidationMetadata(context); + + // Assert + var validatorMetadata = Assert.Single(context.ValidationMetadata.ValidatorMetadata); + Assert.Same(attribute, validatorMetadata); + } + + [Fact] + public void GetValidationDetails_ValidatableObject_AlreadyInContext_Ignores() + { + // Arrange + var provider = new DataAnnotationsMetadataProvider(); + + var attribute = new TestValidationAttribute(); + var attributes = new Attribute[] { attribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, attributes); + context.ValidationMetadata.ValidatorMetadata.Add(attribute); + + // Act + provider.GetValidationMetadata(context); + + // Assert + var validatorMetadata = Assert.Single(context.ValidationMetadata.ValidatorMetadata); + Assert.Same(attribute, validatorMetadata); + } + + private class TestValidationAttribute : ValidationAttribute, IClientModelValidator + { + public IEnumerable GetClientValidationRules(ClientModelValidationContext context) + { + throw new NotImplementedException(); + } + } + private class EmptyClass { } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultValidationMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultValidationMetadataTest.cs new file mode 100644 index 0000000000..7eeb2ed5bb --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultValidationMetadataTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding.Validation; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata +{ + public class DefaultValidationMetadataTest + { + [Fact] + public void GetValidationDetails_MarkedWithClientValidator_ReturnsValidator() + { + // Arrange + var provider = new DefaultValidationMetadataProvider(); + + var attribute = new TestClientModelValidationAttribute(); + var attributes = new Attribute[] { attribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, attributes); + + // Act + provider.GetValidationMetadata(context); + + // Assert + var validatorMetadata = Assert.Single(context.ValidationMetadata.ValidatorMetadata); + Assert.Same(attribute, validatorMetadata); + } + + [Fact] + public void GetValidationDetails_MarkedWithModelValidator_ReturnsValidator() + { + // Arrange + var provider = new DefaultValidationMetadataProvider(); + + var attribute = new TestModelValidationAttribute(); + var attributes = new Attribute[] { attribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, attributes); + + // Act + provider.GetValidationMetadata(context); + + // Assert + var validatorMetadata = Assert.Single(context.ValidationMetadata.ValidatorMetadata); + Assert.Same(attribute, validatorMetadata); + } + + [Fact] + public void GetValidationDetails_Validator_AlreadyInContext_Ignores() + { + // Arrange + var provider = new DefaultValidationMetadataProvider(); + + var attribute = new TestValidationAttribute(); + var attributes = new Attribute[] { attribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, attributes); + context.ValidationMetadata.ValidatorMetadata.Add(attribute); + + // Act + provider.GetValidationMetadata(context); + + // Assert + var validatorMetadata = Assert.Single(context.ValidationMetadata.ValidatorMetadata); + Assert.Same(attribute, validatorMetadata); + } + + private class TestModelValidationAttribute : Attribute, IModelValidator + { + public bool IsRequired + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable Validate(ModelValidationContext context) + { + throw new NotImplementedException(); + } + } + + private class TestClientModelValidationAttribute : Attribute, IClientModelValidator + { + public IEnumerable GetClientValidationRules(ClientModelValidationContext context) + { + throw new NotImplementedException(); + } + } + + private class TestValidationAttribute : Attribute, IModelValidator, IClientModelValidator + { + public bool IsRequired + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable GetClientValidationRules(ClientModelValidationContext context) + { + throw new NotImplementedException(); + } + + public IEnumerable Validate(ModelValidationContext context) + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsClientModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsClientModelValidatorProviderTest.cs new file mode 100644 index 0000000000..9937a912aa --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsClientModelValidatorProviderTest.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 System.ComponentModel.DataAnnotations; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + public class DataAnnotationsClientModelValidatorProviderTest + { + private readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + + [Fact] + public void GetValidators_DoesNotAddRequiredAttribute_IfAttributeIsSpecifiedExplicitly() + { + // Arrange + var provider = new DataAnnotationsClientModelValidatorProvider(); + var metadata = _metadataProvider.GetMetadataForProperty(typeof(DummyRequiredAttributeHelperClass), + "WithAttribute"); + + var providerContext = new ClientValidatorProviderContext(metadata); + + // Act + provider.GetValidators(providerContext); + + // Assert + var validator = Assert.Single(providerContext.Validators); + var adapter = Assert.IsType(validator); + Assert.Equal("Custom Required Message", adapter.Attribute.ErrorMessage); + } + + public static IEnumerable DataAnnotationAdapters + { + get + { + yield return new object[] + { + new RegularExpressionAttribute("abc"), + typeof(RegularExpressionAttributeAdapter) + }; + + yield return new object[] + { + new MaxLengthAttribute(), + typeof(MaxLengthAttributeAdapter) + }; + + yield return new object[] + { + new MinLengthAttribute(1), + typeof(MinLengthAttributeAdapter) + }; + + yield return new object[] + { + new RangeAttribute(1, 100), + typeof(RangeAttributeAdapter) + }; + + yield return new object[] + { + new StringLengthAttribute(6), + typeof(StringLengthAttributeAdapter) + }; + + yield return new object[] + { + new RequiredAttribute(), + typeof(RequiredAttributeAdapter) + }; + } + } + + [Theory] + [MemberData(nameof(DataAnnotationAdapters))] + public void AdapterFactory_RegistersAdapters_ForDataAnnotationAttributes(ValidationAttribute attribute, + Type expectedAdapterType) + { + // Arrange + var adapters = new DataAnnotationsClientModelValidatorProvider().AttributeFactories; + var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; + + // Act + var adapter = adapterFactory(attribute); + + // Assert + Assert.IsType(expectedAdapterType, adapter); + } + + public static IEnumerable DataTypeAdapters + { + get + { + yield return new object[] { new UrlAttribute(), "url" }; + yield return new object[] { new CreditCardAttribute(), "creditcard" }; + yield return new object[] { new EmailAddressAttribute(), "email" }; + yield return new object[] { new PhoneAttribute(), "phone" }; + } + } + + [Theory] + [MemberData(nameof(DataTypeAdapters))] + public void AdapterFactory_RegistersAdapters_ForDataTypeAttributes(ValidationAttribute attribute, + string expectedRuleName) + { + // Arrange + var adapters = new DataAnnotationsClientModelValidatorProvider().AttributeFactories; + var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; + + // Act + var adapter = adapterFactory(attribute); + + // Assert + var dataTypeAdapter = Assert.IsType(adapter); + Assert.Equal(expectedRuleName, dataTypeAdapter.RuleName); + } + + [Fact] + public void UnknownValidationAttribute_IsNotAddedAsValidator() + { + // Arrange + var provider = new DataAnnotationsClientModelValidatorProvider(); + var metadata = _metadataProvider.GetMetadataForType(typeof(DummyClassWithDummyValidationAttribute)); + + var providerContext = new ClientValidatorProviderContext(metadata); + + // Act + provider.GetValidators(providerContext); + + // Assert + Assert.Empty(providerContext.Validators); + } + + private class DummyValidationAttribute : ValidationAttribute + { + } + + [DummyValidation] + private class DummyClassWithDummyValidationAttribute + { + } + + private class DummyRequiredAttributeHelperClass + { + [Required(ErrorMessage = "Custom Required Message")] + public int WithAttribute { get; set; } + + public int WithoutAttribute { get; set; } + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs index 47bc1184ac..43795bc8e0 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs @@ -36,111 +36,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation } #endif - [Fact] - public void GetValidators_DoesNotAddRequiredAttribute_ForNonNullableValueTypes_IfAttributeIsSpecifiedExplicitly() - { - // Arrange - var provider = new DataAnnotationsModelValidatorProvider(); - var metadata = _metadataProvider.GetMetadataForProperty(typeof(DummyRequiredAttributeHelperClass), - "WithAttribute"); - - var providerContext = new ModelValidatorProviderContext(metadata); - - // Act - provider.GetValidators(providerContext); - - // Assert - var validator = Assert.Single(providerContext.Validators); - var adapter = Assert.IsType(validator); - Assert.Equal("Custom Required Message", adapter.Attribute.ErrorMessage); - } - - public static IEnumerable DataAnnotationAdapters - { - get - { - yield return new object[] - { - new RegularExpressionAttribute("abc"), - typeof(RegularExpressionAttributeAdapter) - }; - - yield return new object[] - { - new MaxLengthAttribute(), - typeof(MaxLengthAttributeAdapter) - }; - - yield return new object[] - { - new MinLengthAttribute(1), - typeof(MinLengthAttributeAdapter) - }; - - yield return new object[] - { - new RangeAttribute(1, 100), - typeof(RangeAttributeAdapter) - }; - - yield return new object[] - { - new StringLengthAttribute(6), - typeof(StringLengthAttributeAdapter) - }; - - yield return new object[] - { - new RequiredAttribute(), - typeof(RequiredAttributeAdapter) - }; - } - } - - [Theory] - [MemberData(nameof(DataAnnotationAdapters))] - public void AdapterFactory_RegistersAdapters_ForDataAnnotationAttributes(ValidationAttribute attribute, - Type expectedAdapterType) - { - // Arrange - var adapters = new DataAnnotationsModelValidatorProvider().AttributeFactories; - var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; - - // Act - var adapter = adapterFactory(attribute); - - // Assert - Assert.IsType(expectedAdapterType, adapter); - } - - public static IEnumerable DataTypeAdapters - { - get - { - yield return new object[] { new UrlAttribute(), "url" }; - yield return new object[] { new CreditCardAttribute(), "creditcard" }; - yield return new object[] { new EmailAddressAttribute(), "email" }; - yield return new object[] { new PhoneAttribute(), "phone" }; - } - } - - [Theory] - [MemberData(nameof(DataTypeAdapters))] - public void AdapterFactory_RegistersAdapters_ForDataTypeAttributes(ValidationAttribute attribute, - string expectedRuleName) - { - // Arrange - var adapters = new DataAnnotationsModelValidatorProvider().AttributeFactories; - var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; - - // Act - var adapter = adapterFactory(attribute); - - // Assert - var dataTypeAdapter = Assert.IsType(adapter); - Assert.Equal(expectedRuleName, dataTypeAdapter.RuleName); - } - [Fact] public void UnknownValidationAttributeGetsDefaultAdapter() { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs index ccdf812adc..708fbcb4e6 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorTest.cs @@ -219,55 +219,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation } #endif - [Fact] - public void GetClientValidationRules_ReturnsEmptyRuleSet() - { - // Arrange - var attribute = new FileExtensionsAttribute(); - var validator = new DataAnnotationsModelValidator(attribute); - - var metadata = _metadataProvider.GetMetadataForProperty( - containerType: typeof(string), - propertyName: nameof(string.Length)); - - var serviceCollection = new ServiceCollection(); - var requestServices = serviceCollection.BuildServiceProvider(); - - var context = new ClientModelValidationContext(metadata, _metadataProvider, requestServices); - - // Act - var results = validator.GetClientValidationRules(context); - - // Assert - Assert.Empty(results); - } - - [Fact] - public void GetClientValidationRules_WithIClientModelValidator_CallsAttribute() - { - // Arrange - var attribute = new TestableAttribute(); - var validator = new DataAnnotationsModelValidator(attribute); - - var metadata = _metadataProvider.GetMetadataForProperty( - containerType: typeof(string), - propertyName: nameof(string.Length)); - - var serviceCollection = new ServiceCollection(); - var requestServices = serviceCollection.BuildServiceProvider(); - - var context = new ClientModelValidationContext(metadata, _metadataProvider, requestServices); - - // Act - var results = validator.GetClientValidationRules(context); - - // Assert - var rule = Assert.Single(results); - Assert.Equal("an error", rule.ErrorMessage); - Assert.Empty(rule.ValidationParameters); - Assert.Equal("testable", rule.ValidationType); - } - [Fact] public void IsRequiredTests() { @@ -295,13 +246,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { public string Name { get; set; } } - - private class TestableAttribute : ValidationAttribute, IClientModelValidator - { - public IEnumerable GetClientValidationRules(ClientModelValidationContext context) - { - return new[] { new ModelClientValidationRule(validationType: "testable", errorMessage: "an error") }; - } - } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelClientValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelClientValidatorProviderTest.cs new file mode 100644 index 0000000000..9b9bb5d183 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelClientValidatorProviderTest.cs @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 System.ComponentModel.DataAnnotations; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + // Integration tests for the default configuration of ModelMetadata and Validation providers + public class DefaultModelClientValidatorProviderTest + { + [Fact] + public void GetValidators_ForIValidatableObject() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForType(typeof(ValidatableObject)); + var context = new ModelValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + var validator = Assert.Single(validators); + Assert.IsType(validator); + } + + [Fact] + public void GetValidators_ClientModelValidatorAttributeOnClass() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForType(typeof(ModelValidatorAttributeOnClass)); + var context = new ModelValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + var validator = Assert.Single(validators); + var customModelValidator = Assert.IsType(validator); + Assert.Equal("Class", customModelValidator.Tag); + } + + [Fact] + public void GetValidators_ClientModelValidatorAttributeOnProperty() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForProperty( + typeof(ModelValidatorAttributeOnProperty), + nameof(ModelValidatorAttributeOnProperty.Property)); + var context = new ModelValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + var validator = Assert.IsType(Assert.Single(validators)); + Assert.Equal("Property", validator.Tag); + } + + [Fact] + public void GetValidators_ClientModelValidatorAttributeOnPropertyAndClass() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForProperty( + typeof(ModelValidatorAttributeOnPropertyAndClass), + nameof(ModelValidatorAttributeOnPropertyAndClass.Property)); + var context = new ModelValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + Assert.Equal(2, validators.Count); + Assert.Single(validators, v => Assert.IsType(v).Tag == "Class"); + Assert.Single(validators, v => Assert.IsType(v).Tag == "Property"); + } + + // RangeAttribute is an example of a ValidationAttribute with it's own adapter type. + [Fact] + public void GetValidators_ClientValidatorAttribute_SpecificAdapter() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForProperty( + typeof(RangeAttributeOnProperty), + nameof(RangeAttributeOnProperty.Property)); + var context = new ClientValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + Assert.IsType(Assert.Single(validators)); + } + + [Fact] + public void GetValidators_ClientValidatorAttribute_DefaultAdapter() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForProperty( + typeof(CustomValidationAttributeOnProperty), + nameof(CustomValidationAttributeOnProperty.Property)); + var context = new ClientValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + Assert.IsType(Assert.Single(validators)); + } + + [Fact] + public void GetValidators_FromModelMetadataType_SingleValidator() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForProperty( + typeof(ProductViewModel), + nameof(ProductViewModel.Id)); + var context = new ClientValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + Assert.IsType(Assert.Single(validators)); + } + + [Fact] + public void GetValidators_FromModelMetadataType_MergedValidators() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); + + var metadata = metadataProvider.GetMetadataForProperty( + typeof(ProductViewModel), + nameof(ProductViewModel.Name)); + var context = new ClientValidatorProviderContext(metadata); + + // Act + validatorProvider.GetValidators(context); + + // Assert + var validators = context.Validators; + + Assert.Equal(2, validators.Count); + Assert.Single(validators, v => v is RegularExpressionAttributeAdapter); + Assert.Single(validators, v => v is StringLengthAttributeAdapter); + } + + private class ValidatableObject : IValidatableObject + { + public IEnumerable Validate(ValidationContext validationContext) + { + return null; + } + } + + [CustomModelValidator(Tag = "Class")] + private class ModelValidatorAttributeOnClass + { + } + + private class ModelValidatorAttributeOnProperty + { + [CustomModelValidator(Tag = "Property")] + public string Property { get; set; } + } + + private class ModelValidatorAttributeOnPropertyAndClass + { + [CustomModelValidator(Tag = "Property")] + public ModelValidatorAttributeOnClass Property { get; set; } + } + + private class CustomModelValidatorAttribute : Attribute, IModelValidator + { + public string Tag { get; set; } + + public bool IsRequired + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable Validate(ModelValidationContext context) + { + throw new NotImplementedException(); + } + } + + private class RangeAttributeOnProperty + { + [Range(0, 10)] + public int Property { get; set; } + } + + private class CustomValidationAttribute : Attribute, IClientModelValidator + { + public IEnumerable GetClientValidationRules(ClientModelValidationContext context) + { + return Enumerable.Empty(); + } + } + + private class CustomValidationAttributeOnProperty + { + [CustomValidation] + public int Property { get; set; } + } + + private class ProductEntity + { + [Range(0, 10)] + public int Id { get; set; } + + [RegularExpression(".*")] + public string Name { get; set; } + } + + [ModelMetadataType(typeof(ProductEntity))] + private class ProductViewModel + { + public int Id { get; set; } + + [StringLength(4)] + public string Name { get; set; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs index cdab98fcdb..202d40b7e0 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultModelValidatorProviderTest.cs @@ -102,12 +102,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { // Arrange var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty( typeof(RangeAttributeOnProperty), nameof(RangeAttributeOnProperty.Property)); - var context = new ModelValidatorProviderContext(metadata); + var context = new ClientValidatorProviderContext(metadata); // Act validatorProvider.GetValidators(context); @@ -144,12 +144,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { // Arrange var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty( typeof(ProductViewModel), nameof(ProductViewModel.Id)); - var context = new ModelValidatorProviderContext(metadata); + var context = new ClientValidatorProviderContext(metadata); // Act validatorProvider.GetValidators(context); @@ -165,12 +165,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { // Arrange var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider(); + var validatorProvider = TestClientModelValidatorProvider.CreateDefaultProvider(); var metadata = metadataProvider.GetMetadataForProperty( typeof(ProductViewModel), nameof(ProductViewModel.Name)); - var context = new ModelValidatorProviderContext(metadata); + var context = new ClientValidatorProviderContext(metadata); // Act validatorProvider.GetValidators(context); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs index 23e933b196..852e02db2b 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public TestableHtmlGenerator(IModelMetadataProvider metadataProvider, IUrlHelper urlHelper) : this( metadataProvider, - Mock.Of>(), + GetOptions(), urlHelper, validationAttributes: new Dictionary(StringComparer.OrdinalIgnoreCase)) { @@ -36,10 +36,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public TestableHtmlGenerator( IModelMetadataProvider metadataProvider, - IScopedInstance bindingContextAccessor, + IOptions options, IUrlHelper urlHelper, IDictionary validationAttributes) - : base(GetAntiForgery(), bindingContextAccessor, metadataProvider, urlHelper, new HtmlEncoder()) + : base(GetAntiForgery(), options, metadataProvider, urlHelper, new HtmlEncoder()) { _validationAttributes = validationAttributes; } @@ -90,6 +90,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers return ValidationAttributes; } + private static IOptions GetOptions() + { + var mockOptions = new Mock>(); + mockOptions + .SetupGet(options => options.Options) + .Returns(new MvcOptions()); + + return mockOptions.Object; + } private static AntiForgery GetAntiForgery() { // AntiForgery must be passed to TestableHtmlGenerator constructor but will never be called. diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs index 1b750be26e..0d0ead02f7 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs @@ -124,6 +124,22 @@ namespace Microsoft.AspNet.Mvc Assert.IsType(mvcOptions.ModelValidatorProviders[1]); } + [Fact] + public void Setup_SetsUpClientModelValidatorProviders() + { + // Arrange + var mvcOptions = new MvcOptions(); + var setup = new MvcOptionsSetup(); + + // Act + setup.Configure(mvcOptions); + + // Assert + Assert.Equal(2, mvcOptions.ClientModelValidatorProviders.Count); + Assert.IsType(mvcOptions.ClientModelValidatorProviders[0]); + Assert.IsType(mvcOptions.ClientModelValidatorProviders[1]); + } + [Fact] public void Setup_IgnoresAcceptHeaderHavingWildCardMediaAndSubMediaTypes() { diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs b/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs new file mode 100644 index 0000000000..d82c0f683e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestCommon/TestClientModelValidatorProvider.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Validation +{ + internal class TestClientModelValidatorProvider : CompositeClientModelValidatorProvider + { + // Creates a provider with all the defaults - includes data annotations + public static IClientModelValidatorProvider CreateDefaultProvider() + { + var providers = new IClientModelValidatorProvider[] + { + new DefaultClientModelValidatorProvider(), + new DataAnnotationsClientModelValidatorProvider(), + }; + + return new TestClientModelValidatorProvider(providers); + } + + public TestClientModelValidatorProvider(IEnumerable providers) + : base(providers) + { + } + } +} \ No newline at end of file