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 ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
|
||||
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
|
||||
private readonly DataAnnotationRequiredAttributeValidation _dataAnnotationRequiredAttributeValidation;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of DataContractSerializerInputFormatter
|
||||
|
|
@ -41,8 +40,6 @@ namespace Microsoft.AspNet.Mvc.Xml
|
|||
|
||||
WrapperProviderFactories = new List<IWrapperProviderFactory>();
|
||||
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
|
||||
|
||||
_dataAnnotationRequiredAttributeValidation = new DataAnnotationRequiredAttributeValidation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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\"><Address><State>WA</State><Zipcode>" +
|
||||
"98052</Zipcode></Address><Id>10</Id></Store>";
|
||||
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
|
||||
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\">" +
|
||||
"<Address i:nil=\"true\"/><Id>10</Id></Store>";
|
||||
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);
|
||||
}).ToList();
|
||||
|
||||
var expectedErrorMessages = new List<string>();
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 = "<?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)
|
||||
{
|
||||
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