From f3679f214e85b64bc4d7387e34753f4b8e01beb8 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 16 Apr 2015 14:53:57 -0700 Subject: [PATCH] Fix #1712 - remove reflection from validation code in MutableObjectModelBinder This change moves [BindingBehavior(...)] and friends into the model metadata layer, and removes the reflection code from MutableObjectModelBinder that was looking for them previously. --- .../Binders/MutableObjectModelBinder.cs | 44 +- .../Metadata/BindingMetadata.cs | 9 +- .../DefaultBindingMetadataProvider.cs | 22 + .../Metadata/DefaultModelMetadata.cs | 25 +- .../ModelMetadata.cs | 12 +- .../DefaultBindingMetadataProviderTest.cs | 387 ++++++++++++++++++ .../Metadata/ModelMetadataProviderTest.cs | 94 +++++ .../Metadata/ModelMetadataTest.cs | 8 + 8 files changed, 566 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 064473fdd9..7d7d25bcaf 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding.Internal; -using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.ModelBinding.Validation; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -313,19 +312,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding internal static PropertyValidationInfo GetPropertyValidationInfo(ModelBindingContext bindingContext) { var validationInfo = new PropertyValidationInfo(); - var modelTypeInfo = bindingContext.ModelType.GetTypeInfo(); - var typeAttribute = modelTypeInfo.GetCustomAttribute(); - var properties = bindingContext.ModelType.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (var property in properties) + + foreach (var propertyMetadata in bindingContext.ModelMetadata.Properties) { - var propertyName = property.Name; - var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName]; - if (propertyMetadata == null) + var propertyName = propertyMetadata.PropertyName; + + if (!propertyMetadata.IsBindingAllowed) { - // Skip indexer properties and others ModelMetadata ignores. + // Nothing to do here if binding is not allowed. + validationInfo.SkipProperties.Add(propertyName); continue; } + if (propertyMetadata.IsBindingRequired) + { + validationInfo.RequiredProperties.Add(propertyName); + } + var validatorProviderContext = new ModelValidatorProviderContext(propertyMetadata); bindingContext.OperationBindingContext.ValidatorProvider.GetValidators(validatorProviderContext); @@ -334,29 +337,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding if (requiredValidator != null) { validationInfo.RequiredValidators[propertyName] = requiredValidator; - } - - var propertyAttribute = property.GetCustomAttribute(); - var bindingBehaviorAttribute = propertyAttribute ?? typeAttribute; - if (bindingBehaviorAttribute != null) - { - switch (bindingBehaviorAttribute.Behavior) - { - case BindingBehavior.Required: - validationInfo.RequiredProperties.Add(propertyName); - break; - - case BindingBehavior.Never: - validationInfo.SkipProperties.Add(propertyName); - break; - } - } - else if (requiredValidator != null) - { - validationInfo.RequiredProperties.Add(propertyName); - } - else if (propertyMetadata.IsBindingRequired) - { validationInfo.RequiredProperties.Add(propertyName); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs index d44cc74175..17a972fdc4 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/BindingMetadata.cs @@ -28,9 +28,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// public Type BinderType { get; set; } + /// + /// Gets or sets a value indicating whether or not the property can be model bound. + /// Will be ignored if the model metadata being created does not represent a property. + /// See . + /// + public bool IsBindingAllowed { get; set; } = true; + /// /// Gets or sets a value indicating whether or not the request must contain a value for the model. - /// Will be ignored if the model metadata being created is not a property. + /// Will be ignored if the model metadata being created does not represent a property. /// See . /// public bool IsBindingRequired { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs index 05c18d3dd0..e393ba9e54 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata @@ -53,6 +54,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata context.BindingMetadata.PropertyBindingPredicateProvider = new CompositePredicateProvider( predicateProviders); } + + if (context.Key.MetadataKind == ModelMetadataKind.Property) + { + // BindingBehavior can fall back to attributes on the Container Type, but we should ignore + // attributes on the Property Type. + var bindingBehavior = context.PropertyAttributes.OfType().FirstOrDefault(); + if (bindingBehavior == null) + { + bindingBehavior = + context.Key.ContainerType.GetTypeInfo() + .GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true) + .OfType() + .FirstOrDefault(); + } + + if (bindingBehavior != null) + { + context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never; + context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required; + } + } } private class CompositePredicateProvider : IPropertyBindingPredicateProvider diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs index c94dc86cd7..45e1edabaa 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadata.cs @@ -255,12 +255,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } } + /// + public override bool IsBindingAllowed + { + get + { + if (MetadataKind == ModelMetadataKind.Property) + { + return BindingMetadata.IsBindingAllowed; + } + else + { + return true; + } + } + } + /// public override bool IsBindingRequired { get { - return BindingMetadata.IsBindingRequired; + if (MetadataKind == ModelMetadataKind.Property) + { + return BindingMetadata.IsBindingRequired; + } + else + { + return false; + } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs index e60d183ed1..a9e88cb866 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelMetadata.cs @@ -166,12 +166,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public abstract bool HideSurroundingHtml { get; } + /// + /// Gets a value indicating whether or not the model value can be bound by model binding. This is only + /// applicable when the current instance represents a property. + /// + /// + /// If true then the model value is considered supported by model binding and can be set + /// based on provided input in the request. + /// + public abstract bool IsBindingAllowed { get; } + /// /// Gets a value indicating whether or not the model value is required by model binding. This is only /// applicable when the current instance represents a property. /// /// - /// If true then the model value is considered required by model-binding and must have a value + /// If true then the model value is considered required by model binding and must have a value /// supplied in the request to be considered valid. /// public abstract bool IsBindingRequired { get; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs index 56f32618ed..42036a0a99 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs @@ -147,5 +147,392 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata // Assert Assert.Equal(BindingSource.Body, context.BindingMetadata.BindingSource); } + + [Fact] + public void GetBindingDetails_FindsBindingBehaviorNever_OnProperty() + { + // Arrange + var propertyAttributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Never), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.False(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindNever_OnProperty() + { + // Arrange + var propertyAttributes = new object[] + { + new BindNeverAttribute(), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.False(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindingBehaviorOptional_OnProperty() + { + // Arrange + var propertyAttributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Optional), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindingBehaviorRequired_OnProperty() + { + // Arrange + var propertyAttributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Required), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindRequired_OnProperty() + { + // Arrange + var propertyAttributes = new object[] + { + new BindRequiredAttribute(), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + // These attributes have conflicting behavior - the 'required' behavior should be used because + // of ordering. + [Fact] + public void GetBindingDetails_UsesFirstAttribute() + { + // Arrange + var propertyAttributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Required), + new BindNeverAttribute(), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindRequired_OnContainerClass() + { + // Arrange + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)), + new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindNever_OnContainerClass() + { + // Arrange + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), + new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.False(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_FindsBindNever_OnBaseClass() + { + // Arrange + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)), + new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.False(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_OverrideBehaviorOnClass_OverrideWithOptional() + { + // Arrange + var propertyAttributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Optional) + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_OverrideBehaviorOnClass_OverrideWithRequired() + { + // Arrange + var propertyAttributes = new object[] + { + new BindRequiredAttribute() + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindNeverOnClass)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_OverrideInheritedBehaviorOnClass_OverrideWithRequired() + { + // Arrange + var propertyAttributes = new object[] + { + new BindRequiredAttribute() + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(InheritedBindNeverOnClass)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + [Fact] + public void GetBindingDetails_OverrideBehaviorOnClass_OverrideWithNever() + { + // Arrange + var propertyAttributes = new object[] + { + new BindNeverAttribute(), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOnClass)), + new ModelAttributes(propertyAttributes, typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.False(context.BindingMetadata.IsBindingAllowed); + Assert.False(context.BindingMetadata.IsBindingRequired); + } + + // This overrides an inherited class-level attribute with a different class-level attribute. + [Fact] + public void GetBindingDetails_OverrideBehaviorOnBaseClass_OverrideWithRequired_OnClass() + { + // Arrange + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(string), "Property", typeof(BindRequiredOverridesInheritedBindNever)), + new ModelAttributes(propertyAttributes: new object[0], typeAttributes: new object[0])); + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.True(context.BindingMetadata.IsBindingAllowed); + Assert.True(context.BindingMetadata.IsBindingRequired); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetBindingDetails_BindingBehaviorLeftAlone_ForTypeMetadata(bool initialValue) + { + // Arrange + var attributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Required), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForType(typeof(string)), + new ModelAttributes(attributes)); + + // These values shouldn't be changed since this is a Type-Metadata + context.BindingMetadata.IsBindingAllowed = initialValue; + context.BindingMetadata.IsBindingRequired = initialValue; + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(initialValue, context.BindingMetadata.IsBindingAllowed); + Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired); + } + + // Unlike most model metadata settings, BindingBehavior can be specified on the *container type* + // but not on the property type. + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetBindingDetails_BindingBehaviorLeftAlone_ForAttributeOnPropertyType(bool initialValue) + { + // Arrange + var typeAttributes = new object[] + { + new BindingBehaviorAttribute(BindingBehavior.Required), + }; + + var context = new BindingMetadataProviderContext( + ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)), + new ModelAttributes(propertyAttributes: new object[0], typeAttributes: typeAttributes)); + + // These values shouldn't be changed since this is a Type-Metadata + context.BindingMetadata.IsBindingAllowed = initialValue; + context.BindingMetadata.IsBindingRequired = initialValue; + + var provider = new DefaultBindingMetadataProvider(); + + // Act + provider.GetBindingMetadata(context); + + // Assert + Assert.Equal(initialValue, context.BindingMetadata.IsBindingAllowed); + Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired); + } + + [BindNever] + private class BindNeverOnClass + { + public string Property { get; set; } + } + + private class InheritedBindNeverOnClass : BindNeverOnClass + { + } + + [BindRequired] + private class BindRequiredOnClass + { + public string Property { get; set; } + } + + [BindRequired] + private class BindRequiredOverridesInheritedBindNever : BindNeverOnClass + { + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs index 22b99c4b99..6ff37ac636 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs @@ -726,6 +726,85 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Null(metadata.Properties[propertyName]); } + [Fact] + public void BindingBehavior_GetMetadataForType_UsesDefaultValues() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var type = typeof(BindingBehaviorModel); + + // Act + var metadata = metadataProvider.GetMetadataForType(type); + + // Assert + Assert.True(metadata.IsBindingAllowed); + Assert.False(metadata.IsBindingRequired); + } + + [Fact] + public void BindingBehavior_Override_RequiredOnProperty() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var type = typeof(BindingBehaviorModel); + var propertyName = nameof(BindingBehaviorModel.BindRequiredOverride); + + // Act + var metadata = metadataProvider.GetMetadataForProperty(type, propertyName); + + // Assert + Assert.True(metadata.IsBindingAllowed); + Assert.True(metadata.IsBindingRequired); + } + + [Fact] + public void BindingBehavior_Override_OptionalOnProperty() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var type = typeof(BindingBehaviorModel); + var propertyName = nameof(BindingBehaviorModel.BindingBehaviorOptionalOverride); + + // Act + var metadata = metadataProvider.GetMetadataForProperty(type, propertyName); + + // Assert + Assert.True(metadata.IsBindingAllowed); + Assert.False(metadata.IsBindingRequired); + } + + [Fact] + public void BindingBehavior_Never_DuplicatedPropertyAndClass() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var type = typeof(BindingBehaviorModel); + var propertyName = nameof(BindingBehaviorModel.BindingBehaviorNeverDuplicatedFromClass); + + // Act + var metadata = metadataProvider.GetMetadataForProperty(type, propertyName); + + // Assert + Assert.False(metadata.IsBindingAllowed); + Assert.False(metadata.IsBindingRequired); + } + + [Fact] + public void BindingBehavior_Never_SetOnClass() + { + // Arrange + var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var type = typeof(BindingBehaviorModel); + var propertyName = nameof(BindingBehaviorModel.BindingBehaviorNeverSetOnClass); + + // Act + var metadata = metadataProvider.GetMetadataForProperty(type, propertyName); + + // Assert + Assert.False(metadata.IsBindingAllowed); + Assert.False(metadata.IsBindingRequired); + } + private IModelMetadataProvider CreateProvider(params object[] attributes) { return new AttributeInjectModelMetadataProvider(attributes); @@ -869,6 +948,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata public string RequiredProperty { get; set; } } + [BindNever] + private class BindingBehaviorModel + { + [BindRequired] + public int BindRequiredOverride { get; set; } + + [BindingBehavior(BindingBehavior.Optional)] + public int BindingBehaviorOptionalOverride { get; set; } + + [BindingBehavior(BindingBehavior.Never)] + public int BindingBehaviorNeverDuplicatedFromClass { get; set; } + + public int BindingBehaviorNeverSetOnClass { get; set; } + } + private class AttributeInjectModelMetadataProvider : DefaultModelMetadataProvider { private readonly object[] _attributes; diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs index 26d6f281dd..134579bb25 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs @@ -309,6 +309,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + public override bool IsBindingAllowed + { + get + { + throw new NotImplementedException(); + } + } + public override bool IsBindingRequired { get