Reviving MinLength and MaxLength attribute adapters

This commit is contained in:
Pranav K 2014-04-23 14:43:06 -07:00
parent b0c7dc9220
commit 2ba8780ee0
9 changed files with 244 additions and 2 deletions

View File

@ -84,7 +84,11 @@
<Compile Include="Validation\IModelValidator.cs" />
<Compile Include="Validation\IModelValidatorProvider.cs" />
<Compile Include="Validation\InvalidModelValidatorProvider.cs" />
<Compile Include="Validation\MaxLengthAttributeAdapter.cs" />
<Compile Include="Validation\MinLengthAttributeAdapter.cs" />
<Compile Include="Validation\ModelClientValidationEqualToRule.cs" />
<Compile Include="Validation\ModelClientValidationMaxLengthRule.cs" />
<Compile Include="Validation\ModelClientValidationMinLengthRule.cs" />
<Compile Include="Validation\ModelClientValidationRegexRule.cs" />
<Compile Include="Validation\ModelClientValidationRule.cs" />
<Compile Include="Validation\ModelValidatedEventArgs.cs" />

View File

@ -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<Type, DataAnnotationsModelValidationFactory> 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;

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class MaxLengthAttributeAdapter : DataAnnotationsModelValidator<MaxLengthAttribute>
{
public MaxLengthAttributeAdapter(MaxLengthAttribute attribute)
: base(attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(
[NotNull] ClientModelValidationContext context)
{
var message = GetErrorMessage(context.ModelMetadata);
return new[] { new ModelClientValidationMaxLengthRule(message, Attribute.Length) };
}
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class MinLengthAttributeAdapter : DataAnnotationsModelValidator<MinLengthAttribute>
{
public MinLengthAttributeAdapter(MinLengthAttribute attribute)
: base(attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(
[NotNull] ClientModelValidationContext context)
{
var message = GetErrorMessage(context.ModelMetadata);
return new[] { new ModelClientValidationMinLengthRule(message, Attribute.Length) };
}
}
}

View File

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

View File

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

View File

@ -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<object[]> 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<DataTypeAttributeAdapter>(adapter);
Assert.Equal(expectedRuleName, dataTypeAdapter.RuleName);
}
}
[Fact]
public void UnknownValidationAttributeGetsDefaultAdapter()
{

View File

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

View File

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