Support ModelMetaData.IsRequired

* Update DataAnnotationsModelValidatorProvider to use ModelMetadata.IsRequired
* Adding tests and updating existing ones that didn't work with IsRequired

Fixes #533
This commit is contained in:
Pranav K 2014-05-27 17:46:35 -07:00
parent 95aa6ad607
commit 7396f58d99
6 changed files with 122 additions and 0 deletions

View File

@ -15,6 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
DisplayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault();
Editable = attributes.OfType<EditableAttribute>().FirstOrDefault();
Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
}
public DisplayAttribute Display { get; protected set; }
@ -22,5 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public DisplayFormatAttribute DisplayFormat { get; protected set; }
public EditableAttribute Editable { get; protected set; }
public RequiredAttribute Required { get; protected set; }
}
}

View File

@ -58,6 +58,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.ComputeIsReadOnly();
}
protected override bool ComputeIsRequired()
{
return (PrototypeCache.Required != null) || base.ComputeIsRequired();
}
public override string GetDisplayName()
{
// DisplayAttribute doesn't require you to set a name, so this could be null.

View File

@ -20,12 +20,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private string _description;
private bool _isReadOnly;
private bool _isComplexType;
private bool _isRequired;
private bool _convertEmptyStringToNullComputed;
private bool _nullDisplayTextComputed;
private bool _descriptionComputed;
private bool _isReadOnlyComputed;
private bool _isComplexTypeComputed;
private bool _isRequiredComputed;
// Constructor for creating real instances of the metadata class based on a prototype
protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor)
@ -125,6 +127,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
public sealed override bool IsRequired
{
get
{
if (!_isRequiredComputed)
{
_isRequired = ComputeIsRequired();
_isRequiredComputed = true;
}
return _isRequired;
}
set
{
_isRequired = value;
_isRequiredComputed = true;
}
}
public sealed override bool IsComplexType
{
get
@ -160,6 +180,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.IsReadOnly;
}
protected virtual bool ComputeIsRequired()
{
return base.IsRequired;
}
protected virtual bool ComputeIsComplexType()
{
return base.IsComplexType;

View File

@ -38,6 +38,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_modelAccessor = modelAccessor;
_modelType = modelType;
_propertyName = propertyName;
IsRequired = !modelType.AllowsNullValue();
}
public Type ContainerType
@ -71,6 +72,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public virtual bool IsReadOnly { get; set; }
public virtual bool IsRequired { get; set; }
public virtual int Order
{
get { return _order; }

View File

@ -92,6 +92,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.False(new ModelMetadata(provider.Object, null, null, typeof(int), null).IsNullableValueType);
}
// IsRequired
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(IDisposable))]
[InlineData(typeof(Nullable<int>))]
public void IsRequired_ReturnsFalse_ForNullableTypes(Type modelType)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var metadata = new ModelMetadata(provider.Object,
containerType: null,
modelAccessor: null,
modelType: modelType,
propertyName: null);
// Act
var isRequired = metadata.IsRequired;
// Assert
Assert.False(isRequired);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(DayOfWeek))]
public void IsRequired_ReturnsTrue_ForNonNullableTypes(Type modelType)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>();
var metadata = new ModelMetadata(provider.Object,
containerType: null,
modelAccessor: null,
modelType: modelType,
propertyName: null);
// Act
var isRequired = metadata.IsRequired;
// Assert
Assert.True(isRequired);
}
// Properties
[Fact]

View File

@ -16,6 +16,41 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
private readonly DataAnnotationsModelMetadataProvider _metadataProvider = new DataAnnotationsModelMetadataProvider();
[Fact]
public void GetValidators_ReturnsValidatorForIValidatableObject()
{
// Arrange
var provider = new DataAnnotationsModelValidatorProvider();
var mockValidatable = Mock.Of<IValidatableObject>();
var metadata = _metadataProvider.GetMetadataForType(() => null, mockValidatable.GetType());
// Act
var validators = provider.GetValidators(metadata);
// Assert
var validator = Assert.Single(validators);
Assert.IsType<ValidatableObjectAdapter>(validator);
}
[Fact]
public void GetValidators_DoesNotAddRequiredAttribute_ForNonNullableValueTypes_IfAttributeIsSpecifiedExplicitly()
{
// Arrange
var provider = new DataAnnotationsModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForProperty(() => null,
typeof(DummyRequiredAttributeHelperClass),
"WithAttribute");
// Act
var validators = provider.GetValidators(metadata);
// Assert
var validator = Assert.Single(validators);
var adapter = Assert.IsType<RequiredAttributeAdapter>(validator);
Assert.Equal("Custom Required Message", adapter.Attribute.ErrorMessage);
}
public static IEnumerable<object[]> DataAnnotationAdapters
{
get
@ -198,5 +233,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
set { base.MyProperty = value; }
}
}
private class DummyRequiredAttributeHelperClass
{
[Required(ErrorMessage = "Custom Required Message")]
public int WithAttribute { get; set; }
public int WithoutAttribute { get; set; }
}
}
}