diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Microsoft.AspNet.Mvc.ModelBinding.kproj b/src/Microsoft.AspNet.Mvc.ModelBinding/Microsoft.AspNet.Mvc.ModelBinding.kproj index 2ac49e2f93..d560c66738 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Microsoft.AspNet.Mvc.ModelBinding.kproj +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Microsoft.AspNet.Mvc.ModelBinding.kproj @@ -84,7 +84,11 @@ + + + + diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs index b780428e99..58be51090f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider { // A factory for validators based on ValidationAttribute. - private delegate IModelValidator DataAnnotationsModelValidationFactory(ValidationAttribute attribute); + internal delegate IModelValidator DataAnnotationsModelValidationFactory(ValidationAttribute attribute); // A factory for validators based on IValidatableObject private delegate IModelValidator DataAnnotationsValidatableObjectAdapterFactory(); @@ -36,6 +36,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private static readonly DataAnnotationsValidatableObjectAdapterFactory _defaultValidatableFactory = () => new ValidatableObjectAdapter(); + internal Dictionary AttributeFactories + { + get { return _attributeFactories; } + } + private static bool AddImplicitRequiredAttributeForValueTypes { get { return _addImplicitRequiredAttributeForValueTypes; } @@ -72,6 +77,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding 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)); + AddDataTypeAttributeAdapter(dict, typeof(UrlAttribute), "url"); return dict; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs new file mode 100644 index 0000000000..b63427a3b4 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MaxLengthAttributeAdapter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class MaxLengthAttributeAdapter : DataAnnotationsModelValidator + { + public MaxLengthAttributeAdapter(MaxLengthAttribute attribute) + : base(attribute) + { + } + + public override IEnumerable GetClientValidationRules( + [NotNull] ClientModelValidationContext context) + { + var message = GetErrorMessage(context.ModelMetadata); + return new[] { new ModelClientValidationMaxLengthRule(message, Attribute.Length) }; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs new file mode 100644 index 0000000000..a8c2ef755d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/MinLengthAttributeAdapter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class MinLengthAttributeAdapter : DataAnnotationsModelValidator + { + public MinLengthAttributeAdapter(MinLengthAttribute attribute) + : base(attribute) + { + } + + public override IEnumerable GetClientValidationRules( + [NotNull] ClientModelValidationContext context) + { + var message = GetErrorMessage(context.ModelMetadata); + return new[] { new ModelClientValidationMinLengthRule(message, Attribute.Length) }; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationMaxLengthRule.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationMaxLengthRule.cs new file mode 100644 index 0000000000..1801b1fece --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationMaxLengthRule.cs @@ -0,0 +1,14 @@ +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class ModelClientValidationMaxLengthRule : ModelClientValidationRule + { + private const string MaxLengthValidationType = "maxlength"; + private const string MaxLengthValidationParameter = "max"; + + public ModelClientValidationMaxLengthRule([NotNull] string errorMessage, int maximumLength) + : base(MaxLengthValidationType, errorMessage) + { + ValidationParameters[MaxLengthValidationParameter] = maximumLength; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationMinLengthRule.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationMinLengthRule.cs new file mode 100644 index 0000000000..9a97818878 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationMinLengthRule.cs @@ -0,0 +1,14 @@ +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class ModelClientValidationMinLengthRule : ModelClientValidationRule + { + private const string MinLengthValidationType = "minlength"; + private const string MinLengthValidationParameter = "min"; + + public ModelClientValidationMinLengthRule([NotNull] string errorMessage, int minimumLength) + : base(MinLengthValidationType, errorMessage) + { + ValidationParameters[MinLengthValidationParameter] = minimumLength; + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs index 067dd92d0b..d81fff46dc 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; #if NET45 @@ -12,6 +13,51 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider(); + public static IEnumerable KnownAdapterTypeData + { + get + { + yield return new object[] { typeof(RegularExpressionAttribute), + new RegularExpressionAttribute("abc"), + typeof(RegularExpressionAttributeAdapter), null }; + + yield return new object[] { typeof(MaxLengthAttribute), + new MaxLengthAttribute(), + typeof(MaxLengthAttributeAdapter), null }; + + yield return new object[] { typeof(MinLengthAttribute), + new MinLengthAttribute(1), + typeof(MinLengthAttributeAdapter), null }; + + yield return new object[] { typeof(UrlAttribute), + new UrlAttribute(), + typeof(DataTypeAttributeAdapter), "url" }; + } + } + + [Theory] + [MemberData("KnownAdapterTypeData")] + public void AdapterForKnownTypeRegistered(Type attributeType, + ValidationAttribute validationAttr, + Type expectedAdapterType, + string expectedRuleName) + { + // Arrange + var adapters = new DataAnnotationsModelValidatorProvider().AttributeFactories; + var adapterFactory = adapters.Single(kvp => kvp.Key == attributeType).Value; + + // Act + var adapter = adapterFactory(validationAttr); + + // Assert + Assert.IsType(expectedAdapterType, adapter); + if (expectedRuleName != null) + { + var dataTypeAdapter = Assert.IsType(adapter); + Assert.Equal(expectedRuleName, dataTypeAdapter.RuleName); + } + } + [Fact] public void UnknownValidationAttributeGetsDefaultAdapter() { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs new file mode 100644 index 0000000000..971e1a559c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MaxLengthAttributeAdapterTest.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Testing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class MaxLengthAttributeAdapterTest + { + [Fact] + [ReplaceCulture] + public void ClientRulesWithMaxLengthAttribute() + { + // Arrange + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), "Length"); + var attribute = new MaxLengthAttribute(10); + var adapter = new MaxLengthAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("maxlength", rule.ValidationType); + Assert.Equal(1, rule.ValidationParameters.Count); + Assert.Equal(10, rule.ValidationParameters["max"]); + Assert.Equal("The field Length must be a string or array type with a maximum length of '10'.", rule.ErrorMessage); + } + + [Fact] + [ReplaceCulture] + public void ClientRulesWithMaxLengthAttributeAndCustomMessage() + { + // Arrange + var propertyName = "Length"; + var message = "{0} must be at most {1}"; + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), propertyName); + var attribute = new MaxLengthAttribute(5) { ErrorMessage = message }; + var adapter = new MaxLengthAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("maxlength", rule.ValidationType); + Assert.Equal(1, rule.ValidationParameters.Count); + Assert.Equal(5, rule.ValidationParameters["max"]); + Assert.Equal("Length must be at most 5", rule.ErrorMessage); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs new file mode 100644 index 0000000000..ec47994540 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/MinLengthAttributeAdapterTest.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Testing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class MinLengthAttributeAdapterTest + { + [Fact] + [ReplaceCulture] + public void ClientRulesWithMinLengthAttribute() + { + // Arrange + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), "Length"); + var attribute = new MinLengthAttribute(6); + var adapter = new MinLengthAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("minlength", rule.ValidationType); + Assert.Equal(1, rule.ValidationParameters.Count); + Assert.Equal(6, rule.ValidationParameters["min"]); + Assert.Equal("The field Length must be a string or array type with a minimum length of '6'.", rule.ErrorMessage); + } + + [Fact] + [ReplaceCulture] + public void ClientRulesWithMinLengthAttributeAndCustomMessage() + { + // Arrange + var propertyName = "Length"; + var message = "Array must have at least {1} items."; + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), propertyName); + var attribute = new MinLengthAttribute(2) { ErrorMessage = message }; + var adapter = new MinLengthAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("minlength", rule.ValidationType); + Assert.Equal(1, rule.ValidationParameters.Count); + Assert.Equal(2, rule.ValidationParameters["min"]); + Assert.Equal("Array must have at least 2 items.", rule.ErrorMessage); + } + } +} \ No newline at end of file