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 d560c66738..f7812e207f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Microsoft.AspNet.Mvc.ModelBinding.kproj +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Microsoft.AspNet.Mvc.ModelBinding.kproj @@ -71,6 +71,9 @@ + + + @@ -89,8 +92,11 @@ + + + diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs index 58be51090f..9a2b5e26c3 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs @@ -86,6 +86,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding 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; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRangeRule.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRangeRule.cs new file mode 100644 index 0000000000..26e610e9ec --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRangeRule.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class ModelClientValidationRangeRule : ModelClientValidationRule + { + private const string RangeValidationType = "range"; + private const string MinValidationParameter = "min"; + private const string MaxValidationParameter = "max"; + + public ModelClientValidationRangeRule([NotNull] string errorMessage, + [NotNull] object minValue, + [NotNull] object maxValue) + : base(RangeValidationType, errorMessage) + { + ValidationParameters[MinValidationParameter] = minValue; + ValidationParameters[MaxValidationParameter] = maxValue; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRequiredRule.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRequiredRule.cs new file mode 100644 index 0000000000..6784b94425 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRequiredRule.cs @@ -0,0 +1,12 @@ +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class ModelClientValidationRequiredRule : ModelClientValidationRule + { + private const string RequiredValidationType = "required"; + + public ModelClientValidationRequiredRule(string errorMessage) : + base(RequiredValidationType, errorMessage) + { + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationStringLengthRule.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationStringLengthRule.cs new file mode 100644 index 0000000000..c0131694bb --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationStringLengthRule.cs @@ -0,0 +1,23 @@ +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class ModelClientValidationStringLengthRule : ModelClientValidationRule + { + private const string LengthValidationType = "length"; + private const string MinValidationParameter = "min"; + private const string MaxValidationParameter = "max"; + + public ModelClientValidationStringLengthRule(string errorMessage, int minimumLength, int maximumLength) + : base(LengthValidationType, errorMessage) + { + if (minimumLength != 0) + { + ValidationParameters[MinValidationParameter] = minimumLength; + } + + if (maximumLength != int.MaxValue) + { + ValidationParameters[MaxValidationParameter] = maximumLength; + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs new file mode 100644 index 0000000000..8e9e1d559d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RangeAttributeAdapter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class RangeAttributeAdapter : DataAnnotationsModelValidator + { + public RangeAttributeAdapter(RangeAttribute attribute) + : base(attribute) + { + } + + public override IEnumerable GetClientValidationRules( + [NotNull] ClientModelValidationContext context) + { + var errorMessage = GetErrorMessage(context.ModelMetadata); + return new[] { new ModelClientValidationRangeRule(errorMessage, Attribute.Minimum, Attribute.Maximum) }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs new file mode 100644 index 0000000000..5074114c0c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/RequiredAttributeAdapter.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class RequiredAttributeAdapter : DataAnnotationsModelValidator + { + public RequiredAttributeAdapter(RequiredAttribute attribute) + : base(attribute) + { + } + + public override IEnumerable GetClientValidationRules( + [NotNull] ClientModelValidationContext context) + { + var errorMessage = GetErrorMessage(context.ModelMetadata); + return new[] { new ModelClientValidationRequiredRule(errorMessage) }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs new file mode 100644 index 0000000000..f261891b40 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/StringLengthAttributeAdapter.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class StringLengthAttributeAdapter : DataAnnotationsModelValidator + { + public StringLengthAttributeAdapter(StringLengthAttribute attribute) + : base(attribute) + { + } + + public override IEnumerable GetClientValidationRules( + [NotNull] ClientModelValidationContext context) + { + var errorMessage = GetErrorMessage(context.ModelMetadata); + return new[] { new ModelClientValidationStringLengthRule(errorMessage, + Attribute.MinimumLength, + Attribute.MaximumLength) }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj index 18fcdd0d84..26bca548b4 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Microsoft.AspNet.Mvc.ModelBinding.Test.kproj @@ -39,6 +39,9 @@ + + + diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs index d81fff46dc..c4a71adf62 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DataAnnotationsModelValidatorProviderTest.cs @@ -13,51 +13,74 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider(); - public static IEnumerable KnownAdapterTypeData + public static IEnumerable DataAnnotationAdapters { get { - yield return new object[] { typeof(RegularExpressionAttribute), - new RegularExpressionAttribute("abc"), - typeof(RegularExpressionAttributeAdapter), null }; + yield return new object[] { new RegularExpressionAttribute("abc"), + typeof(RegularExpressionAttributeAdapter) }; - yield return new object[] { typeof(MaxLengthAttribute), - new MaxLengthAttribute(), - typeof(MaxLengthAttributeAdapter), null }; + yield return new object[] { new MaxLengthAttribute(), + typeof(MaxLengthAttributeAdapter) }; - yield return new object[] { typeof(MinLengthAttribute), - new MinLengthAttribute(1), - typeof(MinLengthAttributeAdapter), null }; + yield return new object[] { new MinLengthAttribute(1), + typeof(MinLengthAttributeAdapter) }; - yield return new object[] { typeof(UrlAttribute), - new UrlAttribute(), - typeof(DataTypeAttributeAdapter), "url" }; + 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("KnownAdapterTypeData")] - public void AdapterForKnownTypeRegistered(Type attributeType, - ValidationAttribute validationAttr, - Type expectedAdapterType, - string expectedRuleName) + [MemberData("DataAnnotationAdapters")] + public void AdapterFactory_RegistersAdapters_ForDataAnnotationAttributes(ValidationAttribute attribute, + Type expectedAdapterType) { // Arrange var adapters = new DataAnnotationsModelValidatorProvider().AttributeFactories; - var adapterFactory = adapters.Single(kvp => kvp.Key == attributeType).Value; + var adapterFactory = adapters.Single(kvp => kvp.Key == attribute.GetType()).Value; // Act - var adapter = adapterFactory(validationAttr); + var adapter = adapterFactory(attribute); // Assert Assert.IsType(expectedAdapterType, adapter); - if (expectedRuleName != null) + } + + public static IEnumerable DataTypeAdapters + { + get { - var dataTypeAdapter = Assert.IsType(adapter); - Assert.Equal(expectedRuleName, dataTypeAdapter.RuleName); + 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("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/RangeAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RangeAttributeAdapterTest.cs new file mode 100644 index 0000000000..ecabdad352 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RangeAttributeAdapterTest.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Testing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class RangeAttributeAdapterTest + { + [Fact] + [ReplaceCulture] + public void GetClientValidationRules_ReturnsValidationParameters() + { + // Arrange + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), "Length"); + var attribute = new RangeAttribute(typeof(decimal), "0", "100"); + var adapter = new RangeAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("range", rule.ValidationType); + Assert.Equal(2, rule.ValidationParameters.Count); + Assert.Equal(0m, rule.ValidationParameters["min"]); + Assert.Equal(100m, rule.ValidationParameters["max"]); + Assert.Equal(@"The field Length must be between 0 and 100.", rule.ErrorMessage); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs new file mode 100644 index 0000000000..40d06ed744 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/RequiredAttributeAdapterTest.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Testing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class RequiredAttributeAdapterTest + { + [Fact] + [ReplaceCulture] + public void GetClientValidationRules_ReturnsValidationParameters() + { + // Arrange + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), "Length"); + var attribute = new RequiredAttribute(); + var adapter = new RequiredAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("required", rule.ValidationType); + Assert.Empty(rule.ValidationParameters); + Assert.Equal("The Length field is required.", rule.ErrorMessage); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs new file mode 100644 index 0000000000..6deb63270a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/StringLengthAttributeAdapterTest.cs @@ -0,0 +1,56 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Testing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class StringLengthAttributeAdapterTest + { + [Fact] + [ReplaceCulture] + public void GetClientValidationRules_WithMaxLength_ReturnsValidationParameters() + { + // Arrange + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), "Length"); + var attribute = new StringLengthAttribute(8); + var adapter = new StringLengthAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("length", rule.ValidationType); + Assert.Equal(1, rule.ValidationParameters.Count); + Assert.Equal(8, rule.ValidationParameters["max"]); + Assert.Equal("The field Length must be a string with a maximum length of 8.", + rule.ErrorMessage); + } + + [Fact] + [ReplaceCulture] + public void GetClientValidationRules_WithMinAndMaxLength_ReturnsValidationParameters() + { + // Arrange + var provider = new DataAnnotationsModelMetadataProvider(); + var metadata = provider.GetMetadataForProperty(() => null, typeof(string), "Length"); + var attribute = new StringLengthAttribute(10) { MinimumLength = 3 }; + var adapter = new StringLengthAttributeAdapter(attribute); + var context = new ClientModelValidationContext(metadata, provider); + + // Act + var rules = adapter.GetClientValidationRules(context); + + // Assert + var rule = Assert.Single(rules); + Assert.Equal("length", rule.ValidationType); + Assert.Equal(2, rule.ValidationParameters.Count); + Assert.Equal(3, rule.ValidationParameters["min"]); + Assert.Equal(10, rule.ValidationParameters["max"]); + Assert.Equal("The field Length must be a string with a minimum length of 3 and a maximum length of 10.", + rule.ErrorMessage); + } + } +}