Added support for validation of Required attributes in case of XmlDataContractSerializerInputFormatter
This commit is contained in:
parent
eef8884d0f
commit
c276ddaa39
|
|
@ -15,6 +15,7 @@
|
|||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" }
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" },
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Copyright (c) Microsoft Open Technologies, Inc. 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
|
@ -60,5 +62,35 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public static bool IsSimpleType(Type type)
|
||||
{
|
||||
return type.GetTypeInfo().IsPrimitive ||
|
||||
type.Equals(typeof(decimal)) ||
|
||||
type.Equals(typeof(string)) ||
|
||||
type.Equals(typeof(DateTime)) ||
|
||||
type.Equals(typeof(Guid)) ||
|
||||
type.Equals(typeof(DateTimeOffset)) ||
|
||||
type.Equals(typeof(TimeSpan)) ||
|
||||
type.Equals(typeof(Uri));
|
||||
}
|
||||
|
||||
public static bool HasStringConverter(Type type)
|
||||
{
|
||||
return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
|
||||
}
|
||||
|
||||
public static bool IsCollectionType(Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
// Even though string implements IEnumerable, we don't really think of it
|
||||
// as a collection for the purposes of model binding.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We only need to look for IEnumerable, because IEnumerable<T> extends it.
|
||||
return typeof(IEnumerable).IsAssignableFrom(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
"aspnet50": { },
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.ComponentModel.TypeConverter": "4.0.0-beta-*",
|
||||
"System.Reflection.Extensions": "4.0.0-beta-*",
|
||||
"System.Text.Encoding.Extensions": "4.0.10-beta-*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
internal class TypeHelper
|
||||
{
|
||||
internal static bool IsSimpleType(Type type)
|
||||
{
|
||||
return type.GetTypeInfo().IsPrimitive ||
|
||||
type.Equals(typeof(decimal)) ||
|
||||
type.Equals(typeof(string)) ||
|
||||
type.Equals(typeof(DateTime)) ||
|
||||
type.Equals(typeof(Guid)) ||
|
||||
type.Equals(typeof(DateTimeOffset)) ||
|
||||
type.Equals(typeof(TimeSpan));
|
||||
}
|
||||
|
||||
internal static bool HasStringConverter(Type type)
|
||||
{
|
||||
return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
|
||||
}
|
||||
|
||||
internal static bool IsCollectionType(Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
// Even though string implements IEnumerable, we don't really think of it
|
||||
// as a collection for the purposes of model binding.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We only need to look for IEnumerable, because IEnumerable<T> extends it.
|
||||
return typeof(IEnumerable).IsAssignableFrom(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,16 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
protected virtual bool IsSimpleType(Type type)
|
||||
{
|
||||
var result = type.GetTypeInfo().IsPrimitive ||
|
||||
type.Equals(typeof(decimal)) ||
|
||||
type.Equals(typeof(string)) ||
|
||||
type.Equals(typeof(DateTime)) ||
|
||||
type.Equals(typeof(Guid)) ||
|
||||
type.Equals(typeof(DateTimeOffset)) ||
|
||||
type.Equals(typeof(TimeSpan)) ||
|
||||
type.Equals(typeof(Uri));
|
||||
|
||||
return result;
|
||||
return TypeHelper.IsSimpleType(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Razor.Runtime": "4.0.0-*",
|
||||
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
|
||||
"Microsoft.Framework.FileSystemGlobbing": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Interfaces": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.ModelBinding": "6.0.0-*",
|
||||
"Microsoft.AspNet.WebApi.Client": "5.2.2",
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,22 @@ namespace Microsoft.AspNet.Mvc.Xml
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.
|
||||
/// </summary>
|
||||
internal static string RequiredProperty_MustHaveDataMemberRequired
|
||||
{
|
||||
get { return GetString("RequiredProperty_MustHaveDataMemberRequired"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.
|
||||
/// </summary>
|
||||
internal static string FormatRequiredProperty_MustHaveDataMemberRequired(object p0, object p1, object p2, object p3, object p4, object p5, object p6)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RequiredProperty_MustHaveDataMemberRequired"), p0, p1, p2, p3, p4, p5, p6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object to be wrapped must be of type '{0}' but was of type '{1}'.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.IsGenericType())
|
||||
{
|
||||
var enumerableOfT = modelType.ExtractGenericInterface(typeof(IEnumerable<>));
|
||||
if (enumerableOfT != null)
|
||||
{
|
||||
modelType = enumerableOfT.GetGenericArguments()[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.IsValueType() && !propertyType.IsNullableValueType())
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +120,9 @@
|
|||
<data name="EnumerableWrapperProvider_InvalidSourceEnumerableOfT" xml:space="preserve">
|
||||
<value>The type must be an interface and must be or derive from '{0}'.</value>
|
||||
</data>
|
||||
<data name="RequiredProperty_MustHaveDataMemberRequired" xml:space="preserve">
|
||||
<value>{0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'.</value>
|
||||
</data>
|
||||
<data name="WrapperProvider_MismatchType" xml:space="preserve">
|
||||
<value>The object to be wrapped must be of type '{0}' but was of type '{1}'.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ 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
|
||||
/// </summary>
|
||||
|
|
@ -39,6 +40,8 @@ namespace Microsoft.AspNet.Mvc.Xml
|
|||
|
||||
WrapperProviderFactories = new List<IWrapperProviderFactory>();
|
||||
WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
|
||||
|
||||
_dataAnnotationRequiredAttributeValidation = new DataAnnotationRequiredAttributeValidation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -96,6 +99,10 @@ namespace Microsoft.AspNet.Mvc.Xml
|
|||
{
|
||||
var type = GetSerializableType(context.ModelType);
|
||||
|
||||
_dataAnnotationRequiredAttributeValidation.Validate(
|
||||
type,
|
||||
context.ActionContext.ModelState);
|
||||
|
||||
var serializer = GetCachedSerializer(type);
|
||||
|
||||
var deserializedObject = serializer.ReadObject(xmlReader);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Mvc.Razor": "6.0.0-*",
|
||||
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.CopyOnWriteDictionary.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "version": "1.0.0-*", "type": "build" },
|
||||
"xunit.runner.kre": "1.0.0-*",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using XmlFormattersWebSite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class XmlDataContractSerializerInputFormatterTest
|
||||
{
|
||||
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(XmlFormattersWebSite));
|
||||
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
|
||||
private readonly string errorMessageFormat = string.Format(
|
||||
"{{1}}:{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);
|
||||
|
||||
// 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()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml-dcs"));
|
||||
var input = "<Store xmlns=\"http://schemas.datacontract.org/2004/07/XmlFormattersWebSite\" " +
|
||||
"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);
|
||||
|
||||
//Assert
|
||||
var dcsSerializer = new DataContractSerializer(typeof(ModelBindingInfo));
|
||||
var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
var modelBindingInfo = dcsSerializer.ReadObject(responseStream) as ModelBindingInfo;
|
||||
Assert.NotNull(modelBindingInfo);
|
||||
Assert.NotNull(modelBindingInfo.Store);
|
||||
Assert.Equal(10, modelBindingInfo.Store.Id);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the model state has errors related to body model validation(for reference types) and also for
|
||||
// Required attribute validation (for value types).
|
||||
[Fact]
|
||||
public async Task DataMissingForReferneceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml-dcs"));
|
||||
var input = "<Store xmlns=\"http://schemas.datacontract.org/2004/07/XmlFormattersWebSite\"" +
|
||||
" 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();
|
||||
expectedErrorMessages.Add("store.Address:The Address field is required.");
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync("http://localhost/Validation/CreateStore", content);
|
||||
|
||||
//Assert
|
||||
var dcsSerializer = new DataContractSerializer(typeof(ModelBindingInfo));
|
||||
var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
var modelBindingInfo = dcsSerializer.ReadObject(responseStream) as ModelBindingInfo;
|
||||
Assert.NotNull(modelBindingInfo);
|
||||
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)
|
||||
{
|
||||
Assert.Contains(
|
||||
modelBindingInfo.ModelStateErrorMessages,
|
||||
(actualErrorMessage) => actualErrorMessage.Equals(expectedErrorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
|
@ -51,6 +53,15 @@ 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)]
|
||||
|
|
@ -474,6 +485,636 @@ namespace Microsoft.AspNet.Mvc.Xml
|
|||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
modelStateKey: typeof(Address).FullName,
|
||||
actionContext: context.ActionContext,
|
||||
expectedErrorMessages: 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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.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.ActionContext.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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Employee).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.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.ActionContext.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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Product).FullName,
|
||||
context.ActionContext,
|
||||
expectedErrorMessages: new[]
|
||||
{
|
||||
string.Format(requiredErrorMessageFormat, nameof(Product.Id), typeof(Product).FullName)
|
||||
});
|
||||
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Product).FullName,
|
||||
context.ActionContext,
|
||||
expectedErrorMessages: new[]
|
||||
{
|
||||
string.Format(requiredErrorMessageFormat, nameof(Product.Id), typeof(Product).FullName)
|
||||
});
|
||||
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Address).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext,
|
||||
expectedErrorMessages: new[]
|
||||
{
|
||||
string.Format(requiredErrorMessageFormat, nameof(Employee.Id), typeof(Employee).FullName)
|
||||
});
|
||||
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Product).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(School).FullName,
|
||||
context.ActionContext,
|
||||
expectedErrorMessages: new[]
|
||||
{
|
||||
string.Format(requiredErrorMessageFormat, nameof(School.Id), typeof(School).FullName)
|
||||
});
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Website).FullName,
|
||||
context.ActionContext,
|
||||
expectedErrorMessages: new[]
|
||||
{
|
||||
string.Format(requiredErrorMessageFormat, nameof(Website.Id), typeof(Website).FullName)
|
||||
});
|
||||
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Student).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Point).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext,
|
||||
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.ActionContext.ModelState.Keys.Count);
|
||||
AssertModelStateErrorMessages(
|
||||
typeof(Point).FullName,
|
||||
context.ActionContext,
|
||||
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.ActionContext,
|
||||
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.ActionContext,
|
||||
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,
|
||||
ActionContext actionContext,
|
||||
IEnumerable<string> expectedErrorMessages)
|
||||
{
|
||||
ModelState modelState;
|
||||
actionContext.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 actionContext = GetActionContext(contentBytes);
|
||||
|
|
@ -514,4 +1155,178 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,23 +2,30 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace FormatterWebSite
|
||||
{
|
||||
[DataContract]
|
||||
public class User
|
||||
{
|
||||
[DataMember(IsRequired = true)]
|
||||
[Required, Range(1, 2000)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember(IsRequired = true)]
|
||||
[Required, MinLength(5)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember]
|
||||
[StringLength(15, MinimumLength = 3)]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[DataMember]
|
||||
[RegularExpression("[0-9a-zA-Z]*")]
|
||||
public string Designation { get; set; }
|
||||
|
||||
[DataMember]
|
||||
public string description { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,9 @@
|
|||
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" },
|
||||
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Generic;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace XmlFormattersWebSite
|
||||
{
|
||||
public class ValidationController : Controller
|
||||
{
|
||||
public IActionResult CreateStore([FromBody] Store store)
|
||||
{
|
||||
// We want to verify that 'store' is model bound and also that the
|
||||
// model state has the errors we are expecting.
|
||||
return new ObjectResult(new ModelBindingInfo()
|
||||
{
|
||||
Store = store,
|
||||
ModelStateErrorMessages = GetModelStateErrorMessages(ModelState)
|
||||
});
|
||||
}
|
||||
|
||||
// Cannot use 'SerializableError' here as 'RequiredAttribute' validation errors are added as exceptions
|
||||
// into the model state dictionary and 'SerializableError' sanitizes exceptions with generic error message.
|
||||
// Since the tests need to verify the messages, we are doing the following.
|
||||
private List<string> GetModelStateErrorMessages(ModelStateDictionary modelStateDictionary)
|
||||
{
|
||||
var allErrorMessages = new List<string>();
|
||||
foreach (var keyModelStatePair in modelStateDictionary)
|
||||
{
|
||||
var key = keyModelStatePair.Key;
|
||||
var errors = keyModelStatePair.Value.Errors;
|
||||
if (errors != null && errors.Count > 0)
|
||||
{
|
||||
string errorMessage = null;
|
||||
foreach (var modelError in errors)
|
||||
{
|
||||
if (string.IsNullOrEmpty(modelError.ErrorMessage))
|
||||
{
|
||||
if (modelError.Exception != null)
|
||||
{
|
||||
errorMessage = modelError.Exception.Message;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = modelError.ErrorMessage;
|
||||
}
|
||||
|
||||
if (errorMessage != null)
|
||||
{
|
||||
allErrorMessages.Add(string.Format("{0}:{1}", key, errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrorMessages;
|
||||
}
|
||||
}
|
||||
|
||||
public class ModelBindingInfo
|
||||
{
|
||||
public Store Store { get; set; }
|
||||
|
||||
public List<string> ModelStateErrorMessages { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace XmlFormattersWebSite
|
||||
{
|
||||
public class Address
|
||||
{
|
||||
[Required]
|
||||
public string State { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Zipcode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace XmlFormattersWebSite
|
||||
{
|
||||
public class Store
|
||||
{
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue