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.
This commit is contained in:
parent
13a3c0b931
commit
4939181075
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Validates types having value type properties decorated with <see cref="RequiredAttribute"/>
|
|
||||||
/// but no <see cref="DataMemberAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <see cref="JsonInputFormatter"/> supports <see cref="RequiredAttribute"/> 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.
|
|
||||||
/// </remarks>
|
|
||||||
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<Type, Dictionary<Type, List<string>>> _cachedValidationErrors
|
|
||||||
= new ConcurrentDictionary<Type, Dictionary<Type, List<string>>>();
|
|
||||||
|
|
||||||
public void Validate([NotNull] Type modelType, [NotNull] ModelStateDictionary modelStateDictionary)
|
|
||||||
{
|
|
||||||
var visitedTypes = new HashSet<Type>();
|
|
||||||
|
|
||||||
// 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<Type, List<string>>();
|
|
||||||
|
|
||||||
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<Type> visitedTypes,
|
|
||||||
Dictionary<Type, List<string>> errors)
|
|
||||||
{
|
|
||||||
// We don't need to code special handling for KeyValuePair (for example, when the model type
|
|
||||||
// is Dictionary<,> which implements IEnumerable<KeyValuePair<TKey, TValue>>) as the model
|
|
||||||
// type here would be KeyValuePair<TKey, TValue> 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<Type, List<string>> 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<string> errorMessages;
|
|
||||||
if (!errors.TryGetValue(validationError.ModelType, out errorMessages))
|
|
||||||
{
|
|
||||||
errorMessages = new List<string>();
|
|
||||||
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<Type, List<string>>();
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -24,7 +24,6 @@ namespace Microsoft.AspNet.Mvc.Xml
|
||||||
private DataContractSerializerSettings _serializerSettings;
|
private DataContractSerializerSettings _serializerSettings;
|
||||||
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
||||||
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
|
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
|
||||||
private readonly DataAnnotationRequiredAttributeValidation _dataAnnotationRequiredAttributeValidation;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of DataContractSerializerInputFormatter
|
/// Initializes a new instance of DataContractSerializerInputFormatter
|
||||||
|
|
@ -41,8 +40,6 @@ namespace Microsoft.AspNet.Mvc.Xml
|
||||||
|
|
||||||
WrapperProviderFactories = new List<IWrapperProviderFactory>();
|
WrapperProviderFactories = new List<IWrapperProviderFactory>();
|
||||||
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
|
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
|
||||||
|
|
||||||
_dataAnnotationRequiredAttributeValidation = new DataAnnotationRequiredAttributeValidation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -103,11 +100,6 @@ namespace Microsoft.AspNet.Mvc.Xml
|
||||||
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
|
using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding))
|
||||||
{
|
{
|
||||||
var type = GetSerializableType(context.ModelType);
|
var type = GetSerializableType(context.ModelType);
|
||||||
|
|
||||||
_dataAnnotationRequiredAttributeValidation.Validate(
|
|
||||||
type,
|
|
||||||
context.ModelState);
|
|
||||||
|
|
||||||
var serializer = GetCachedSerializer(type);
|
var serializer = GetCachedSerializer(type);
|
||||||
|
|
||||||
var deserializedObject = serializer.ReadObject(xmlReader);
|
var deserializedObject = serializer.ReadObject(xmlReader);
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
data);
|
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]
|
[Fact]
|
||||||
public async Task RequiredDataIsProvided_AndModelIsBound_AndHasRequiredAttributeValidationErrors()
|
public async Task RequiredDataIsProvided_AndModelIsBound_NoValidationErrors()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
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\"><Address><State>WA</State><Zipcode>" +
|
"xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Address><State>WA</State><Zipcode>" +
|
||||||
"98052</Zipcode></Address><Id>10</Id></Store>";
|
"98052</Zipcode></Address><Id>10</Id></Store>";
|
||||||
var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs");
|
var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs");
|
||||||
var propertiesCollection = new List<KeyValuePair<string, string>>();
|
|
||||||
propertiesCollection.Add(new KeyValuePair<string, string>(nameof(Store.Id), typeof(Store).FullName));
|
|
||||||
propertiesCollection.Add(new KeyValuePair<string, string>(nameof(Address.Zipcode), typeof(Address).FullName));
|
|
||||||
var expectedErrorMessages = propertiesCollection.Select(kvp =>
|
|
||||||
{
|
|
||||||
return string.Format(errorMessageFormat, kvp.Key, kvp.Value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await client.PostAsync("http://localhost/Validation/CreateStore", content);
|
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.NotNull(modelBindingInfo.Store.Address);
|
||||||
Assert.Equal(98052, modelBindingInfo.Store.Address.Zipcode);
|
Assert.Equal(98052, modelBindingInfo.Store.Address.Zipcode);
|
||||||
Assert.Equal("WA", modelBindingInfo.Store.Address.State);
|
Assert.Equal("WA", modelBindingInfo.Store.Address.State);
|
||||||
Assert.NotNull(modelBindingInfo.ModelStateErrorMessages);
|
Assert.Empty(modelBindingInfo.ModelStateErrorMessages);
|
||||||
Assert.Equal(expectedErrorMessages.Count(), modelBindingInfo.ModelStateErrorMessages.Count);
|
|
||||||
foreach (var expectedErrorMessage in expectedErrorMessages)
|
|
||||||
{
|
|
||||||
Assert.Contains(
|
|
||||||
modelBindingInfo.ModelStateErrorMessages,
|
|
||||||
(actualErrorMessage) => actualErrorMessage.Equals(expectedErrorMessage));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies that the model state has errors related to body model validation(for reference types) and also for
|
// Verifies that the model state has errors related to body model validation.
|
||||||
// Required attribute validation (for value types).
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DataMissingForReferneceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors()
|
public async Task DataMissingForRefereneceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
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\">" +
|
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
|
||||||
"<Address i:nil=\"true\"/><Id>10</Id></Store>";
|
"<Address i:nil=\"true\"/><Id>10</Id></Store>";
|
||||||
var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs");
|
var content = new StringContent(input, Encoding.UTF8, "application/xml-dcs");
|
||||||
var propertiesCollection = new List<KeyValuePair<string, string>>();
|
|
||||||
propertiesCollection.Add(new KeyValuePair<string, string>(nameof(Store.Id), typeof(Store).FullName));
|
var expectedErrorMessages = new List<string>();
|
||||||
propertiesCollection.Add(new KeyValuePair<string, string>(nameof(Address.Zipcode), typeof(Address).FullName));
|
|
||||||
var expectedErrorMessages = propertiesCollection.Select(kvp =>
|
|
||||||
{
|
|
||||||
return string.Format(errorMessageFormat, kvp.Key, kvp.Value);
|
|
||||||
}).ToList();
|
|
||||||
expectedErrorMessages.Add("Address:The Address field is required.");
|
expectedErrorMessages.Add("Address:The Address field is required.");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -131,6 +109,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.NotNull(modelBindingInfo.Store);
|
Assert.NotNull(modelBindingInfo.Store);
|
||||||
Assert.Equal(10, modelBindingInfo.Store.Id);
|
Assert.Equal(10, modelBindingInfo.Store.Id);
|
||||||
Assert.NotNull(modelBindingInfo.ModelStateErrorMessages);
|
Assert.NotNull(modelBindingInfo.ModelStateErrorMessages);
|
||||||
|
|
||||||
Assert.Equal(expectedErrorMessages.Count(), modelBindingInfo.ModelStateErrorMessages.Count);
|
Assert.Equal(expectedErrorMessages.Count(), modelBindingInfo.ModelStateErrorMessages.Count);
|
||||||
foreach (var expectedErrorMessage in expectedErrorMessages)
|
foreach (var expectedErrorMessage in expectedErrorMessages)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -53,15 +53,6 @@ namespace Microsoft.AspNet.Mvc.Xml
|
||||||
public TestLevelOne TestOne { get; set; }
|
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]
|
[Theory]
|
||||||
[InlineData("application/xml", true)]
|
[InlineData("application/xml", true)]
|
||||||
[InlineData("application/*", true)]
|
[InlineData("application/*", true)]
|
||||||
|
|
@ -513,637 +504,6 @@ namespace Microsoft.AspNet.Mvc.Xml
|
||||||
Assert.Equal(expectedInt, dummyModel.SampleInt);
|
Assert.Equal(expectedInt, dummyModel.SampleInt);
|
||||||
Assert.Equal(expectedString, dummyModel.SampleString);
|
Assert.Equal(expectedString, dummyModel.SampleString);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PostingListOfModels_HasRequiredAttributeValidationErrors()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var input = "<?xml version=\"1.0\" encoding=\"utf-8\"?><ArrayOfAddress " +
|
|
||||||
"xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\" " +
|
|
||||||
"xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
|
|
||||||
"<Address><IsResidential>true</IsResidential><Zipcode>98052" +
|
|
||||||
"</Zipcode></Address></ArrayOfAddress>";
|
|
||||||
var formatter = new XmlDataContractSerializerInputFormatter();
|
|
||||||
var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input);
|
|
||||||
var context = GetInputFormatterContext(contentBytes, typeof(List<Address>));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var model = await formatter.ReadAsync(context) as List<Address>;
|
|
||||||
|
|
||||||
// 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<Address xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><IsResidential>" +
|
|
||||||
"true</IsResidential><Zipcode>98052</Zipcode></Address>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<ModelWithPropertyHavingRequiredAttributeValidationErrors " +
|
|
||||||
"xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AddressProperty>" +
|
|
||||||
"<IsResidential>true</IsResidential><Zipcode>98052</Zipcode></AddressProperty>" +
|
|
||||||
"</ModelWithPropertyHavingRequiredAttributeValidationErrors>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<ModelWithCollectionPropertyHavingRequiredAttributeValidationErrors" +
|
|
||||||
" xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Addresses><Address>" +
|
|
||||||
"<IsResidential>true</IsResidential><Zipcode>98052</Zipcode></Address></Addresses>" +
|
|
||||||
"</ModelWithCollectionPropertyHavingRequiredAttributeValidationErrors>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<ModelInheritingTypeHavingRequiredAttributeValidationErrors" +
|
|
||||||
" xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
|
|
||||||
"<IsResidential>true</IsResidential><Zipcode>98052</Zipcode>" +
|
|
||||||
"</ModelInheritingTypeHavingRequiredAttributeValidationErrors>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?><CarInfo " +
|
|
||||||
"xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
|
|
||||||
"<ServicedYears xmlns:a=\"http://schemas.datacontract.org/2004/07/System\">" +
|
|
||||||
"<a:int>2006</a:int><a:int>2007</a:int></ServicedYears><Year>2005</Year></CarInfo>";
|
|
||||||
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<int?>() };
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<ModelWithPropertyHavingTypeWithNullableProperties " +
|
|
||||||
"xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\" " +
|
|
||||||
"xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><CarInfoProperty>" +
|
|
||||||
"<ServicedYears xmlns:a=\"http://schemas.datacontract.org/2004/07/System\"><a:int>2006</a:int>" +
|
|
||||||
"<a:int>2007</a:int></ServicedYears><Year>2005</Year></CarInfoProperty>" +
|
|
||||||
"</ModelWithPropertyHavingTypeWithNullableProperties>";
|
|
||||||
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<int?>() }
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = "<Employee xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Id>10</Id><Manager><Id>11</Id><Manager" +
|
|
||||||
" i:nil=\"true\"/><Name>Mike</Name></Manager><Name>John</Name></Employee>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<Laptop xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Id>" +
|
|
||||||
"10</Id><SupportsVirtualization>true</SupportsVirtualization></Laptop>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<ArrayOfLaptop xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Laptop><Id>" +
|
|
||||||
"10</Id><SupportsVirtualization>true</SupportsVirtualization></Laptop></ArrayOfLaptop>";
|
|
||||||
var formatter = new XmlDataContractSerializerInputFormatter();
|
|
||||||
var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input);
|
|
||||||
var context = GetInputFormatterContext(contentBytes, typeof(List<Laptop>));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var model = await formatter.ReadAsync(context) as List<Laptop>;
|
|
||||||
|
|
||||||
// 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<Product xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Id>" +
|
|
||||||
"10</Id><Name>Phone</Name></Product>";
|
|
||||||
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<ArrayOfProduct xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Product><Id>" +
|
|
||||||
"10</Id><Name>Phone</Name></Product></ArrayOfProduct>";
|
|
||||||
var formatter = new XmlDataContractSerializerInputFormatter();
|
|
||||||
var contentBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes(input);
|
|
||||||
var context = GetInputFormatterContext(contentBytes, typeof(List<Product>));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var model = await formatter.ReadAsync(context) as List<Product>;
|
|
||||||
|
|
||||||
// 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
|
||||||
"<Store xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" i:nil=\"true\" />";
|
|
||||||
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 = "<School i:nil=\"true\" " +
|
|
||||||
"xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\" " +
|
|
||||||
"xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"/>";
|
|
||||||
|
|
||||||
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 = "<FavoriteLocations " +
|
|
||||||
"i:nil=\"true\" xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\"" +
|
|
||||||
" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"/>";
|
|
||||||
|
|
||||||
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 = "<ValueTypePropertiesModel i:nil=\"true\" " +
|
|
||||||
"xmlns=\"http://schemas.datacontract.org/2004/07/Microsoft.AspNet.Mvc.Xml\" " +
|
|
||||||
"xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"/>";
|
|
||||||
|
|
||||||
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<string> 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)
|
private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType)
|
||||||
{
|
{
|
||||||
var httpContext = GetHttpContext(contentBytes);
|
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<int?> 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<Address> 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<Product> Products { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StoreDetails
|
|
||||||
{
|
|
||||||
public List<Employee> 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<Student> 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<Point, Address> Addresses { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue