From 49391810759a864f119adc13ac771190ed93ea27 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 11 May 2015 19:02:44 -0700 Subject: [PATCH] Fix #2528 - Revert formatter behavior for [Required] This change removes the support in the DCS formatter to issue an error message when [Required] is used on a value type. --- .../RequiredValidationHelper.cs | 212 ----- ...XmlDataContractSerializerInputFormatter.cs | 8 - ...ataContractSerializerInputFormatterTest.cs | 35 +- ...ataContractSerializerInputFormatterTest.cs | 814 ------------------ 4 files changed, 7 insertions(+), 1062 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Xml/RequiredValidationHelper.cs diff --git a/src/Microsoft.AspNet.Mvc.Xml/RequiredValidationHelper.cs b/src/Microsoft.AspNet.Mvc.Xml/RequiredValidationHelper.cs deleted file mode 100644 index 6d1ce483a5..0000000000 --- a/src/Microsoft.AspNet.Mvc.Xml/RequiredValidationHelper.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Reflection; -using System.Runtime.Serialization; -using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.Framework.Internal; - -namespace Microsoft.AspNet.Mvc.Xml -{ - /// - /// Validates types having value type properties decorated with - /// but no . - /// - /// - /// supports where as the xml formatters - /// do not. Since a user's aplication can have both Json and Xml formatters, a request could be validated - /// when posted as Json but not Xml. So to prevent end users from having a false sense of security when posting - /// as Xml, we add errors to model-state to at least let the users know that there is a problem with their models. - /// - public class DataAnnotationRequiredAttributeValidation - { - // Since formatters are 'typically' registered as single instance, concurrent dictionary is used - // here to avoid duplicate errors being added for a type. - private ConcurrentDictionary>> _cachedValidationErrors - = new ConcurrentDictionary>>(); - - public void Validate([NotNull] Type modelType, [NotNull] ModelStateDictionary modelStateDictionary) - { - var visitedTypes = new HashSet(); - - // Every node maintains a dictionary of Type => Errors. - // It's a dictionary as we want to avoid adding duplicate error messages. - // Example: - // In the following case, from the perspective of type 'Store', we should not see duplicate - // errors related to type 'Address' - // public class Store - // { - // [Required] - // public int Id { get; set; } - // public Address Address { get; set; } - // } - // public class Employee - // { - // [Required] - // public int Id { get; set; } - // public Address Address { get; set; } - // } - // public class Address - // { - // [Required] - // public string Line1 { get; set; } - // [Required] - // public int Zipcode { get; set; } - // [Required] - // public string State { get; set; } - // } - var rootNodeValidationErrors = new Dictionary>(); - - Validate(modelType, visitedTypes, rootNodeValidationErrors); - - foreach (var validationError in rootNodeValidationErrors) - { - foreach (var validationErrorMessage in validationError.Value) - { - // Add error message to model state as exception to avoid - // disclosing details to end user as SerializableError sanitizes the - // model state errors having exceptions with a generic message when sending - // it to the client. - modelStateDictionary.TryAddModelError( - validationError.Key.FullName, - new InvalidOperationException(validationErrorMessage)); - } - } - } - - private void Validate( - Type modelType, - HashSet visitedTypes, - Dictionary> errors) - { - // We don't need to code special handling for KeyValuePair (for example, when the model type - // is Dictionary<,> which implements IEnumerable>) as the model - // type here would be KeyValuePair where Key and Value are public properties - // which would also be probed for Required attribute validation. - if (modelType.GetTypeInfo().IsGenericType) - { - var enumerableOfT = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); - if (enumerableOfT != null) - { - modelType = enumerableOfT.GenericTypeArguments[0]; - } - } - - if (ExcludeTypeFromValidation(modelType)) - { - return; - } - - // Avoid infinite loop in case of self-referencing properties - if (!visitedTypes.Add(modelType)) - { - return; - } - - Dictionary> cachedErrors; - if (_cachedValidationErrors.TryGetValue(modelType, out cachedErrors)) - { - foreach (var validationError in cachedErrors) - { - errors.Add(validationError.Key, validationError.Value); - } - - return; - } - - foreach (var propertyHelper in PropertyHelper.GetProperties(modelType)) - { - var propertyInfo = propertyHelper.Property; - var propertyType = propertyInfo.PropertyType; - - // Since DefaultObjectValidator can handle Required attribute validation for reference types, - // we only consider value types here. - if (propertyType.GetTypeInfo().IsValueType && !TypeHelper.IsNullableValueType(propertyType)) - { - var validationError = GetValidationError(propertyInfo); - if (validationError != null) - { - List errorMessages; - if (!errors.TryGetValue(validationError.ModelType, out errorMessages)) - { - errorMessages = new List(); - errors.Add(validationError.ModelType, errorMessages); - } - - errorMessages.Add(Resources.FormatRequiredProperty_MustHaveDataMemberRequired( - typeof(DataContractSerializer).FullName, - typeof(RequiredAttribute).FullName, - typeof(DataMemberAttribute).FullName, - nameof(DataMemberAttribute.IsRequired), - bool.TrueString, - validationError.PropertyName, - validationError.ModelType.FullName)); - } - - // if the type is not primitve, then it could be a struct in which case - // we need to probe its properties for validation - if (propertyType.GetTypeInfo().IsPrimitive) - { - continue; - } - } - - var childNodeErrors = new Dictionary>(); - Validate(propertyType, visitedTypes, childNodeErrors); - - // Avoid adding duplicate errors at current node. - foreach (var modelTypeKey in childNodeErrors.Keys) - { - if (!errors.ContainsKey(modelTypeKey)) - { - errors.Add(modelTypeKey, childNodeErrors[modelTypeKey]); - } - } - } - - _cachedValidationErrors.TryAdd(modelType, errors); - - visitedTypes.Remove(modelType); - } - - private ValidationError GetValidationError(PropertyInfo propertyInfo) - { - var required = propertyInfo.GetCustomAttribute(typeof(RequiredAttribute), inherit: true); - if (required == null) - { - return null; - } - - var dataMemberRequired = (DataMemberAttribute)propertyInfo.GetCustomAttribute( - typeof(DataMemberAttribute), - inherit: true); - - if (dataMemberRequired != null && dataMemberRequired.IsRequired) - { - return null; - } - - return new ValidationError() - { - ModelType = propertyInfo.DeclaringType, - PropertyName = propertyInfo.Name - }; - } - - private bool ExcludeTypeFromValidation(Type modelType) - { - return TypeHelper.IsSimpleType(modelType); - } - - private class ValidationError - { - public Type ModelType { get; set; } - - public string PropertyName { get; set; } - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs index 6e3aff7c51..2e23668db0 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs @@ -24,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.Xml private DataContractSerializerSettings _serializerSettings; private ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); - private readonly DataAnnotationRequiredAttributeValidation _dataAnnotationRequiredAttributeValidation; /// /// Initializes a new instance of DataContractSerializerInputFormatter @@ -41,8 +40,6 @@ namespace Microsoft.AspNet.Mvc.Xml WrapperProviderFactories = new List(); WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); - - _dataAnnotationRequiredAttributeValidation = new DataAnnotationRequiredAttributeValidation(); } /// @@ -103,11 +100,6 @@ namespace Microsoft.AspNet.Mvc.Xml using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding)) { var type = GetSerializableType(context.ModelType); - - _dataAnnotationRequiredAttributeValidation.Validate( - type, - context.ModelState); - var serializer = GetCachedSerializer(type); var deserializedObject = serializer.ReadObject(xmlReader); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs index d672dae7ed..13188033d2 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs @@ -54,10 +54,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests data); } - // Verifies that even though all the required data is posted to an action, the model - // state has errors related to value types's Required attribute validation. [Fact] - public async Task RequiredDataIsProvided_AndModelIsBound_AndHasRequiredAttributeValidationErrors() + public async Task RequiredDataIsProvided_AndModelIsBound_NoValidationErrors() { // Arrange var server = TestHelper.CreateServer(_app, SiteName, _configureServices); @@ -67,13 +65,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
WA" + "98052
10"; var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs"); - var propertiesCollection = new List>(); - propertiesCollection.Add(new KeyValuePair(nameof(Store.Id), typeof(Store).FullName)); - propertiesCollection.Add(new KeyValuePair(nameof(Address.Zipcode), typeof(Address).FullName)); - var expectedErrorMessages = propertiesCollection.Select(kvp => - { - return string.Format(errorMessageFormat, kvp.Key, kvp.Value); - }); // Act var response = await client.PostAsync("http://localhost/Validation/CreateStore", content); @@ -88,20 +79,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.NotNull(modelBindingInfo.Store.Address); Assert.Equal(98052, modelBindingInfo.Store.Address.Zipcode); Assert.Equal("WA", modelBindingInfo.Store.Address.State); - Assert.NotNull(modelBindingInfo.ModelStateErrorMessages); - Assert.Equal(expectedErrorMessages.Count(), modelBindingInfo.ModelStateErrorMessages.Count); - foreach (var expectedErrorMessage in expectedErrorMessages) - { - Assert.Contains( - modelBindingInfo.ModelStateErrorMessages, - (actualErrorMessage) => actualErrorMessage.Equals(expectedErrorMessage)); - } + Assert.Empty(modelBindingInfo.ModelStateErrorMessages); } - // Verifies that the model state has errors related to body model validation(for reference types) and also for - // Required attribute validation (for value types). + // Verifies that the model state has errors related to body model validation. [Fact] - public async Task DataMissingForReferneceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors() + public async Task DataMissingForRefereneceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors() { // Arrange var server = TestHelper.CreateServer(_app, SiteName, _configureServices); @@ -111,13 +94,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests " xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" + "
10"; var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs"); - var propertiesCollection = new List>(); - propertiesCollection.Add(new KeyValuePair(nameof(Store.Id), typeof(Store).FullName)); - propertiesCollection.Add(new KeyValuePair(nameof(Address.Zipcode), typeof(Address).FullName)); - var expectedErrorMessages = propertiesCollection.Select(kvp => - { - return string.Format(errorMessageFormat, kvp.Key, kvp.Value); - }).ToList(); + + var expectedErrorMessages = new List(); expectedErrorMessages.Add("Address:The Address field is required."); // Act @@ -131,6 +109,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.NotNull(modelBindingInfo.Store); Assert.Equal(10, modelBindingInfo.Store.Id); Assert.NotNull(modelBindingInfo.ModelStateErrorMessages); + Assert.Equal(expectedErrorMessages.Count(), modelBindingInfo.ModelStateErrorMessages.Count); foreach (var expectedErrorMessage in expectedErrorMessages) { diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs index 0e236923e4..d4584e1b08 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs @@ -53,15 +53,6 @@ namespace Microsoft.AspNet.Mvc.Xml public TestLevelOne TestOne { get; set; } } - private readonly string requiredErrorMessageFormat = string.Format( - "{0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property " + - "'{{0}}' on type '{{1}}'.", - typeof(DataContractSerializer).FullName, - typeof(RequiredAttribute).FullName, - typeof(DataMemberAttribute).FullName, - nameof(DataMemberAttribute.IsRequired), - bool.TrueString); - [Theory] [InlineData("application/xml", true)] [InlineData("application/*", true)] @@ -513,637 +504,6 @@ namespace Microsoft.AspNet.Mvc.Xml Assert.Equal(expectedInt, dummyModel.SampleInt); Assert.Equal(expectedString, dummyModel.SampleString); } - - [Fact] - public async Task PostingListOfModels_HasRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "
true98052" + - "
"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(List
)); - - // Act - var model = await formatter.ReadAsync(context) as List
; - - // Assert - Assert.NotNull(model); - Assert.Equal(1, model.Count); - Assert.Equal(98052, model[0].Zipcode); - Assert.Equal(true, model[0].IsResidential); - - Assert.Equal(1, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format( - requiredErrorMessageFormat, - nameof(Address.IsResidential), - typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModel_HasRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "
" + - "true98052
"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(Address)); - - // Act - var model = await formatter.ReadAsync(context) as Address; - - // Assert - Assert.NotNull(model); - Assert.Equal(98052, model.Zipcode); - Assert.Equal(true, model.IsResidential); - - Assert.Equal(1, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format( - requiredErrorMessageFormat, - nameof(Address.IsResidential), - typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModelWithProperty_HasRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "" + - "true98052" + - ""; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext( - contentBytes, - typeof(ModelWithPropertyHavingRequiredAttributeValidationErrors)); - - // Act - var model = await formatter.ReadAsync(context) as ModelWithPropertyHavingRequiredAttributeValidationErrors; - - // Assert - Assert.NotNull(model); - Assert.NotNull(model.AddressProperty); - Assert.Equal(98052, model.AddressProperty.Zipcode); - Assert.Equal(true, model.AddressProperty.IsResidential); - - Assert.Equal(1, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModel_WithCollectionProperty_HasRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "
" + - "true98052
" + - ""; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext( - contentBytes, - typeof(ModelWithCollectionPropertyHavingRequiredAttributeValidationErrors)); - - // Act - var model = await formatter.ReadAsync(context) - as ModelWithCollectionPropertyHavingRequiredAttributeValidationErrors; - - // Assert - Assert.NotNull(model); - Assert.NotNull(model.Addresses); - Assert.Equal(98052, model.Addresses[0].Zipcode); - Assert.Equal(true, model.Addresses[0].IsResidential); - - Assert.Equal(1, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModelInheritingType_HasRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "" + - "true98052" + - ""; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext( - contentBytes, - typeof(ModelInheritingTypeHavingRequiredAttributeValidationErrors)); - - // Act - var model = await formatter.ReadAsync(context) - as ModelInheritingTypeHavingRequiredAttributeValidationErrors; - - // Assert - Assert.NotNull(model); - Assert.Equal(98052, model.Zipcode); - Assert.Equal(true, model.IsResidential); - - Assert.Equal(1, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModelHavingNullableValueTypes_NoRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "" + - "200620072005"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(CarInfo)); - var expectedModel = new CarInfo() { Year = 2005, ServicedYears = new List() }; - expectedModel.ServicedYears.Add(2006); - expectedModel.ServicedYears.Add(2007); - - // Act - var model = await formatter.ReadAsync(context) as CarInfo; - - // Assert - Assert.NotNull(model); - Assert.Equal(expectedModel.Year, model.Year); - Assert.Equal(expectedModel.ServicedYears, model.ServicedYears); - Assert.Empty(context.ModelState); - } - - [Fact] - public async Task PostingModel_WithPropertyHavingNullableValueTypes_NoRequiredAttributeValidationErrors() - { - // Arrange - var input = "" + - "" + - "2006" + - "20072005" + - ""; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext( - contentBytes, - typeof(ModelWithPropertyHavingTypeWithNullableProperties)); - var expectedModel = new ModelWithPropertyHavingTypeWithNullableProperties() - { - CarInfoProperty = new CarInfo() { Year = 2005, ServicedYears = new List() } - }; - - expectedModel.CarInfoProperty.ServicedYears.Add(2006); - expectedModel.CarInfoProperty.ServicedYears.Add(2007); - - // Act - var model = await formatter.ReadAsync(context) as ModelWithPropertyHavingTypeWithNullableProperties; - - // Assert - Assert.NotNull(model); - Assert.NotNull(model.CarInfoProperty); - Assert.Equal(expectedModel.CarInfoProperty.Year, model.CarInfoProperty.Year); - Assert.Equal(expectedModel.CarInfoProperty.ServicedYears, model.CarInfoProperty.ServicedYears); - Assert.Empty(context.ModelState); - } - - [Fact] - public async Task PostingModel_WithPropertySelfReferencingItself() - { - // Arrange - var input = "1011MikeJohn"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(Employee)); - var expectedModel = new Employee() - { - Id = 10, - Name = "John", - Manager = new Employee() - { - Id = 11, - Name = "Mike" - } - }; - - // Act - var model = await formatter.ReadAsync(context) as Employee; - - // Assert - Assert.NotNull(model); - Assert.Equal(expectedModel.Id, model.Id); - Assert.Equal(expectedModel.Name, model.Name); - Assert.NotNull(model.Manager); - Assert.Equal(expectedModel.Manager.Id, model.Manager.Id); - Assert.Equal(expectedModel.Manager.Name, model.Manager.Name); - Assert.Null(model.Manager.Manager); - - Assert.Equal(1, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Employee).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Employee.Id), typeof(Employee).FullName) - }); - } - - [Fact] - public async Task PostingModel_WithBothRequiredAndDataMemberRequired_NoValidationErrors() - { - // Arrange - var input = "" + - "" + - "10true"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(Laptop)); - - // Act - var model = await formatter.ReadAsync(context) as Laptop; - - // Assert - Assert.NotNull(model); - Assert.Equal(10, model.Id); - Assert.Equal(true, model.SupportsVirtualization); - Assert.Empty(context.ModelState); - } - - [Fact] - public async Task PostingListofModels_WithBothRequiredAndDataMemberRequired_NoValidationErrors() - { - // Arrange - var input = "" + - "" + - "10true"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(List)); - - // Act - var model = await formatter.ReadAsync(context) as List; - - // Assert - Assert.NotNull(model); - Assert.Equal(1, model.Count); - Assert.Equal(10, model[0].Id); - Assert.Equal(true, model[0].SupportsVirtualization); - Assert.Empty(context.ModelState); - } - - [Fact] - public async Task PostingModel_WithRequiredAndDataMemberNoRequired_HasValidationErrors() - { - // Arrange - var input = "" + - "" + - "10Phone"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(Product)); - - // Act - var model = await formatter.ReadAsync(context) as Product; - - // Assert - Assert.NotNull(model); - Assert.Equal(10, model.Id); - - Assert.Equal(2, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Product).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Product.Id), typeof(Product).FullName) - }); - - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingListOfModels_WithRequiredAndDataMemberNoRequired_HasValidationErrors() - { - // Arrange - var input = "" + - "" + - "10Phone"; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(List)); - - // Act - var model = await formatter.ReadAsync(context) as List; - - // Assert - Assert.NotNull(model); - Assert.Equal(1, model.Count); - Assert.Equal(10, model[0].Id); - Assert.Equal("Phone", model[0].Name); - - Assert.Equal(2, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Product).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Product.Id), typeof(Product).FullName) - }); - - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModel_WithDeeperHierarchy_HasValidationErrors() - { - // Arrange - var input = "" + - ""; - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(Store)); - - // Act - var model = await formatter.ReadAsync(context) as Store; - - // Assert - Assert.Null(model); - - Assert.Equal(3, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName) - }); - - AssertModelStateErrorMessages( - typeof(Employee).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Employee.Id), typeof(Employee).FullName) - }); - - AssertModelStateErrorMessages( - typeof(Product).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Product.Id), typeof(Product).FullName) - }); - } - - [Fact] - public async Task PostingModelOfStructs_WithDeeperHierarchy_HasValidationErrors() - { - // Arrange - var input = ""; - - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(School)); - - // Act - var model = await formatter.ReadAsync(context); - - // Assert - Assert.Null(model); - - Assert.Equal(3, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(School).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(School.Id), typeof(School).FullName) - }); - AssertModelStateErrorMessages( - typeof(Website).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Website.Id), typeof(Website).FullName) - }); - - AssertModelStateErrorMessages( - typeof(Student).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Student.Id), typeof(Student).FullName) - }); - } - - [Fact] - public async Task PostingModel_WithDictionaryProperty_HasValidationErrorsOnKeyAndValue() - { - // Arrange - var input = ""; - - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(FavoriteLocations)); - - // Act - var model = await formatter.ReadAsync(context); - - // Assert - Assert.Null(model); - - Assert.Equal(2, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Point).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Point.X), typeof(Point).FullName), - string.Format(requiredErrorMessageFormat, nameof(Point.Y), typeof(Point).FullName) - }); - AssertModelStateErrorMessages( - typeof(Address).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Address.IsResidential), typeof(Address).FullName), - string.Format(requiredErrorMessageFormat, nameof(Address.Zipcode), typeof(Address).FullName) - }); - } - - [Fact] - public async Task PostingModel_WithDifferentValueTypeProperties_HasValidationErrors() - { - // Arrange - var input = ""; - - var formatter = new XmlDataContractSerializerInputFormatter(); - var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(ValueTypePropertiesModel)); - - // Act - var model = await formatter.ReadAsync(context); - - // Assert - Assert.Null(model); - - Assert.Equal(3, context.ModelState.Keys.Count); - AssertModelStateErrorMessages( - typeof(Point).FullName, - context, - expectedErrorMessages: new[] - { - string.Format(requiredErrorMessageFormat, nameof(Point.X), typeof(Point).FullName), - string.Format(requiredErrorMessageFormat, nameof(Point.X), typeof(Point).FullName) - }); - AssertModelStateErrorMessages( - typeof(GpsCoordinate).FullName, - context, - expectedErrorMessages: new[] - { - string.Format( - requiredErrorMessageFormat, - nameof(GpsCoordinate.Latitude), - typeof(GpsCoordinate).FullName), - string.Format( - requiredErrorMessageFormat, - nameof(GpsCoordinate.Longitude), - typeof(GpsCoordinate).FullName) - }); - AssertModelStateErrorMessages( - typeof(ValueTypePropertiesModel).FullName, - context, - expectedErrorMessages: new[] - { - string.Format( - requiredErrorMessageFormat, - nameof(ValueTypePropertiesModel.IntProperty), - typeof(ValueTypePropertiesModel).FullName), - string.Format( - requiredErrorMessageFormat, - nameof(ValueTypePropertiesModel.DateTimeProperty), - typeof(ValueTypePropertiesModel).FullName), - string.Format( - requiredErrorMessageFormat, - nameof(ValueTypePropertiesModel.PointProperty), - typeof(ValueTypePropertiesModel).FullName), - string.Format( - requiredErrorMessageFormat, - nameof(ValueTypePropertiesModel.GpsCoordinateProperty), - typeof(ValueTypePropertiesModel).FullName) - }); - } - - private void AssertModelStateErrorMessages( - string modelStateKey, - InputFormatterContext context, - IEnumerable expectedErrorMessages) - { - ModelState modelState; - context.ModelState.TryGetValue(modelStateKey, out modelState); - - Assert.NotNull(modelState); - Assert.NotEmpty(modelState.Errors); - - var actualErrorMessages = modelState.Errors.Select(error => - { - if (string.IsNullOrEmpty(error.ErrorMessage)) - { - if (error.Exception != null) - { - return error.Exception.Message; - } - } - - return error.ErrorMessage; - }); - - Assert.Equal(expectedErrorMessages.Count(), actualErrorMessages.Count()); - - if (expectedErrorMessages != null) - { - foreach (var expectedErrorMessage in expectedErrorMessages) - { - Assert.Contains(expectedErrorMessage, actualErrorMessages); - } - } - } - private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType) { var httpContext = GetHttpContext(contentBytes); @@ -1177,178 +537,4 @@ namespace Microsoft.AspNet.Mvc.Xml } } } - - public class Address - { - [Required] - public int Zipcode { get; set; } - - [Required] - public bool IsResidential { get; set; } - } - - public class CarInfo - { - [Required] - public int? Year { get; set; } - - [Required] - public List ServicedYears { get; set; } - } - - public class Employee - { - [Required] - public int Id { get; set; } - - [Required] - public string Name { get; set; } - - [Required] - public Employee Manager { get; set; } - } - - [DataContract] - public class Laptop - { - [DataMember(IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(IsRequired = true)] - [Required] - public bool SupportsVirtualization { get; set; } - } - - [DataContract] - public class Product - { - // Here the property has DataMember but does not set the value 'IsRequired = true' - [DataMember(Name = "Id")] - [Required] - public int Id { get; set; } - - [DataMember(Name = "Name")] - [Required] - public string Name { get; set; } - - [DataMember(Name = "Manufacturer")] - [Required] - public Manufacturer Manufacturer { get; set; } - } - - public class ModelWithPropertyHavingRequiredAttributeValidationErrors - { - public Address AddressProperty { get; set; } - } - - public class ModelWithCollectionPropertyHavingRequiredAttributeValidationErrors - { - public List
Addresses { get; set; } - } - - public class ModelInheritingTypeHavingRequiredAttributeValidationErrors : Address - { - } - - public class ModelWithPropertyHavingTypeWithNullableProperties - { - public CarInfo CarInfoProperty { get; set; } - } - - public class Store - { - public StoreDetails StoreDetails { get; set; } - - public List Products { get; set; } - } - - public class StoreDetails - { - public List Employees { get; set; } - - public Address Address { get; set; } - } - - public class Manufacturer - { - public Address Address { get; set; } - } - - public struct School - { - [Required] - public int Id { get; set; } - - public List Students { get; set; } - - public Website Address { get; set; } - } - - public struct Student - { - [Required] - public int Id { get; set; } - - public Website Address { get; set; } - } - - public struct Website - { - [Required] - public int Id { get; set; } - - [Required] - public string Name { get; set; } - } - - public struct ValueTypePropertiesModel - { - [Required] - public int IntProperty { get; set; } - - [Required] - public int? NullableIntProperty { get; set; } - - [Required] - public DateTime DateTimeProperty { get; set; } - - [Required] - public DateTime? NullableDateTimeProperty { get; set; } - - [Required] - public Point PointProperty { get; set; } - - [Required] - public Point? NullablePointProperty { get; set; } - - [Required] - public GpsCoordinate GpsCoordinateProperty { get; set; } - - [Required] - public GpsCoordinate? NullableGpsCoordinateProperty { get; set; } - } - - public struct GpsCoordinate - { - [Required] - public Point Latitude { get; set; } - - [Required] - public Point Longitude { get; set; } - } - - public struct Point - { - [Required] - public int X { get; set; } - - [Required] - public int Y { get; set; } - } - - public class FavoriteLocations - { - public Dictionary Addresses { get; set; } - } } \ No newline at end of file