diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/SimpleTypesExcludeFilter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/SimpleTypesExcludeFilter.cs index 0d9a76c786..11f9c766c7 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/SimpleTypesExcludeFilter.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/SimpleTypesExcludeFilter.cs @@ -19,22 +19,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { Type[] actualTypes; - var enumerable = type.ExtractGenericInterface(typeof(IEnumerable<>)); - if (enumerable == null) + if (type.IsGenericType() && + type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { - actualTypes = new Type[] { type }; + actualTypes = type.GenericTypeArguments; } else { - actualTypes = enumerable.GenericTypeArguments; - // The following special case is for IEnumerable>, - // supertype of IDictionary, and IReadOnlyDictionary. - if (actualTypes.Length == 1 - && actualTypes[0].IsGenericType() - && actualTypes[0].GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - actualTypes = actualTypes[0].GenericTypeArguments; - } + actualTypes = new Type[] { type }; } foreach (var actualType in actualTypes) diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/SimpleTypeExcludeFilterTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/SimpleTypeExcludeFilterTest.cs index e697585708..5f3f344c89 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/SimpleTypeExcludeFilterTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/SimpleTypeExcludeFilterTest.cs @@ -7,11 +7,11 @@ using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { - public class SimpleTypeExcluceFilterTest + public class SimpleTypeExcludeFilterTest { [Theory] [MemberData(nameof(ExcludedTypes))] - public void SimpleTypeExcluceFilter_ExcludedTypes(Type type) + public void SimpleTypeExcludeFilter_ExcludedTypes(Type type) { // Arrange var filter = new SimpleTypesExcludeFilter(); @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation [Theory] [MemberData(nameof(IncludedTypes))] - public void SimpleTypeExcluceFilter_IncludedTypes(Type type) + public void SimpleTypeExcludeFilter_IncludedTypes(Type type) { // Arrange var filter = new SimpleTypesExcludeFilter(); @@ -43,24 +43,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation return new TheoryData() { // Simple types - typeof(int[]), typeof(int), - typeof(List), - typeof(SortedSet), + typeof(DateTime), // Nullable types - typeof(ICollection), - typeof(int?[]), - typeof(SortedSet), - typeof(HashSet), - typeof(HashSet), - - // Value types - typeof(IList), + typeof(int?), // KeyValue types - typeof(Dictionary), - typeof(IReadOnlyDictionary) + typeof(KeyValuePair) }; } } @@ -71,12 +61,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation { return new TheoryData() { + // Enumerable types + typeof(int[]), + typeof(List), + typeof(SortedSet), + typeof(ICollection), + typeof(int?[]), + typeof(SortedSet), + typeof(HashSet), + typeof(HashSet), + typeof(IList), + typeof(Dictionary), + typeof(IReadOnlyDictionary), + + // Complex types typeof(TestType), typeof(TestType[]), typeof(SortedSet), typeof(Dictionary), typeof(Dictionary), - typeof(Dictionary) + typeof(Dictionary), + typeof(KeyValuePair) }; } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs index aea1abcdf3..17a6c19b6c 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultObjectValidatorTests.cs @@ -472,19 +472,129 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation validator.Validate(validationContext); // Assert + Assert.True(validationContext.ModelState.IsValid); var modelState = validationContext.ModelState["serviceProvider.TestService"]; Assert.Empty(modelState.Errors); Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped); } + [Theory] + [InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(string[]))] + [InlineData(new[] { 1, 2, 3 }, typeof(int[]))] + [InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(IList))] + [InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(HashSet))] + [InlineData(new[] { "1/1/14", "2/2/14", "3/3/14" }, typeof(ICollection))] + [InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(HashSet))] + public void EnumerableType_ValidationSuccessful(object model, Type type) + { + // Arrange + var modelStateDictionary = new ModelStateDictionary(); + modelStateDictionary.Add("items[0]", new ModelState()); + modelStateDictionary.Add("items[1]", new ModelState()); + modelStateDictionary.Add("items[2]", new ModelState()); + + var testValidationContext = GetModelValidationContext( + model, + type, + "items", + excludedTypes: null, + modelStateDictionary: modelStateDictionary); + + var excludeTypeFilters = new List(); + excludeTypeFilters.Add(new SimpleTypesExcludeFilter()); + + var mockValidationExcludeFiltersProvider = new Mock(); + mockValidationExcludeFiltersProvider + .SetupGet(o => o.ExcludeFilters) + .Returns(excludeTypeFilters); + testValidationContext.ExcludeFiltersProvider = mockValidationExcludeFiltersProvider.Object; + + var validationContext = testValidationContext.ModelValidationContext; + + var validator = new DefaultObjectValidator( + testValidationContext.ExcludeFiltersProvider, + testValidationContext.ModelMetadataProvider); + + // Act + validator.Validate(validationContext); + + // Assert + Assert.True(validationContext.ModelState.IsValid); + var modelState = validationContext.ModelState["items"]; + Assert.Equal(modelState.ValidationState, ModelValidationState.Valid); + } + + [Fact] + public void DictionaryType_ValidationSuccessful() + { + // Arrange + var modelStateDictionary = new ModelStateDictionary(); + modelStateDictionary.Add("items[0].Key", new ModelState()); + modelStateDictionary.Add("items[0].Value", new ModelState()); + modelStateDictionary.Add("items[1].Key", new ModelState()); + modelStateDictionary.Add("items[1].Value", new ModelState()); + + var model = new Dictionary() + { + { "FooKey", "FooValue" }, + { "BarKey", "BarValue" } + }; + + var testValidationContext = GetModelValidationContext( + model, + typeof(Dictionary), + "items", + excludedTypes: null, + modelStateDictionary: modelStateDictionary); + + var excludeTypeFilters = new List(); + excludeTypeFilters.Add(new SimpleTypesExcludeFilter()); + + var mockValidationExcludeFiltersProvider = new Mock(); + mockValidationExcludeFiltersProvider + .SetupGet(o => o.ExcludeFilters) + .Returns(excludeTypeFilters); + testValidationContext.ExcludeFiltersProvider = mockValidationExcludeFiltersProvider.Object; + + var validationContext = testValidationContext.ModelValidationContext; + + var validator = new DefaultObjectValidator( + testValidationContext.ExcludeFiltersProvider, + testValidationContext.ModelMetadataProvider); + + // Act + validator.Validate(validationContext); + + // Assert + Assert.True(validationContext.ModelState.IsValid); + var modelState = validationContext.ModelState["items"]; + Assert.Equal(modelState.ValidationState, ModelValidationState.Valid); + modelState = validationContext.ModelState["items[0].Key"]; + Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped); + modelState = validationContext.ModelState["items[0].Value"]; + Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped); + modelState = validationContext.ModelState["items[1].Key"]; + Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped); + modelState = validationContext.ModelState["items[1].Value"]; + Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped); + } + private TestModelValidationContext GetModelValidationContext( object model, Type type, string key = "", List excludedTypes = null) { - var modelStateDictionary = new ModelStateDictionary(); + return GetModelValidationContext(model, type, key, excludedTypes, new ModelStateDictionary()); + } + private TestModelValidationContext GetModelValidationContext( + object model, + Type type, + string key, + List excludedTypes, + ModelStateDictionary modelStateDictionary) + { var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var excludedValidationTypesPredicate = new List();