SerializerSettings refactor
Add SerializerSettings to MvcOptions and pass those options to the JsonInputFormatter and JsonOutputFormatter. Remove custom contract resolver. PR feedback Pass JsonSerializerSettings to JsonPatchInputFormatter PR feedback Make DI JsonOutputFormatter formatter use MvcOptions SerializerSettings Fix JsonPatchInputFormatter using null ContractResolver Fix tests
This commit is contained in:
parent
3fbead0ce8
commit
de630754bf
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// <param name="serializerSettings">The <see cref="JsonSerializerSettings"/> to be used by
|
||||
/// the formatter.</param>
|
||||
public JsonResult(object value, [NotNull] JsonSerializerSettings serializerSettings)
|
||||
: this(value, formatter: new JsonOutputFormatter { SerializerSettings = serializerSettings })
|
||||
: this(value, formatter: new JsonOutputFormatter(serializerSettings))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +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.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// The default <see cref="IContractResolver"/> for <see cref="JsonInputFormatter"/>.
|
||||
/// It determines if a value type member has <see cref="RequiredAttribute"/> and sets the appropriate
|
||||
/// JsonProperty settings.
|
||||
/// </summary>
|
||||
public class JsonContractResolver : DefaultContractResolver
|
||||
{
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
var property = base.CreateProperty(member, memberSerialization);
|
||||
|
||||
var required = member.GetCustomAttribute(typeof(RequiredAttribute), inherit: true);
|
||||
if (required != null)
|
||||
{
|
||||
var propertyType = ((PropertyInfo)member).PropertyType;
|
||||
|
||||
// DefaultObjectValidator does required attribute validation on properties based on the property
|
||||
// value being null. Since this is not possible in case of value types, we depend on the formatters
|
||||
// to handle value type validation.
|
||||
// With the following settings here, if a value is not present on the wire for value types
|
||||
// like primitive, struct etc., Json.net's serializer would throw exception which we catch
|
||||
// and add it to model state.
|
||||
if (propertyType.GetTypeInfo().IsValueType && !TypeHelper.IsNullableValueType(propertyType))
|
||||
{
|
||||
property.Required = Required.AllowNull;
|
||||
}
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core.Internal;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -13,31 +14,22 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
public class JsonInputFormatter : InputFormatter
|
||||
{
|
||||
private const int DefaultMaxDepth = 32;
|
||||
private JsonSerializerSettings _jsonSerializerSettings;
|
||||
private JsonSerializerSettings _serializerSettings;
|
||||
|
||||
public JsonInputFormatter()
|
||||
: this(SerializerSettingsProvider.CreateSerializerSettings())
|
||||
{
|
||||
}
|
||||
|
||||
public JsonInputFormatter([NotNull] JsonSerializerSettings serializerSettings)
|
||||
{
|
||||
_serializerSettings = serializerSettings;
|
||||
|
||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
||||
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json"));
|
||||
|
||||
_jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
|
||||
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
|
||||
// from deserialization errors that might occur from deeply nested objects.
|
||||
MaxDepth = DefaultMaxDepth,
|
||||
|
||||
// Do not change this setting
|
||||
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
|
||||
_jsonSerializerSettings.ContractResolver = new JsonContractResolver();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -45,15 +37,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// </summary>
|
||||
public JsonSerializerSettings SerializerSettings
|
||||
{
|
||||
get { return _jsonSerializerSettings; }
|
||||
get
|
||||
{
|
||||
return _serializerSettings;
|
||||
}
|
||||
[param: NotNull]
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_jsonSerializerSettings = value;
|
||||
_serializerSettings = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core.Internal;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
|
@ -16,13 +17,18 @@ namespace Microsoft.AspNet.Mvc
|
|||
private JsonSerializerSettings _serializerSettings;
|
||||
|
||||
public JsonOutputFormatter()
|
||||
: this(SerializerSettingsProvider.CreateSerializerSettings())
|
||||
{
|
||||
}
|
||||
|
||||
public JsonOutputFormatter([NotNull] JsonSerializerSettings serializerSettings)
|
||||
{
|
||||
_serializerSettings = serializerSettings;
|
||||
|
||||
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
|
||||
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
|
||||
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json"));
|
||||
|
||||
_serializerSettings = new JsonSerializerSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,14 +36,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// </summary>
|
||||
public JsonSerializerSettings SerializerSettings
|
||||
{
|
||||
get { return _serializerSettings; }
|
||||
get
|
||||
{
|
||||
return _serializerSettings;
|
||||
}
|
||||
[param: NotNull]
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_serializerSettings = value;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,20 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNet.JsonPatch;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.AspNet.Mvc.Core.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class JsonPatchInputFormatter : JsonInputFormatter
|
||||
{
|
||||
public JsonPatchInputFormatter()
|
||||
: this(SerializerSettingsProvider.CreateSerializerSettings())
|
||||
{
|
||||
}
|
||||
|
||||
public JsonPatchInputFormatter([NotNull] JsonSerializerSettings serializerSettings)
|
||||
: base(serializerSettings)
|
||||
{
|
||||
// Clear all values and only include json-patch+json value.
|
||||
SupportedMediaTypes.Clear();
|
||||
|
|
@ -23,7 +31,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
public async override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
|
||||
{
|
||||
var jsonPatchDocument = (IJsonPatchDocument)(await base.ReadRequestBodyAsync(context));
|
||||
if (jsonPatchDocument != null)
|
||||
if (jsonPatchDocument != null && SerializerSettings.ContractResolver != null)
|
||||
{
|
||||
jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
// 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 Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Core.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class which provides <see cref="JsonSerializerSettings"/>.
|
||||
/// </summary>
|
||||
internal static class SerializerSettingsProvider
|
||||
{
|
||||
private const int DefaultMaxDepth = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Creates default <see cref="JsonSerializerSettings"/>.
|
||||
/// </summary>
|
||||
/// <returns>Default <see cref="JsonSerializerSettings"/>.</returns>
|
||||
public static JsonSerializerSettings CreateSerializerSettings()
|
||||
{
|
||||
return new JsonSerializerSettings
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
|
||||
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
|
||||
// from deserialization errors that might occur from deeply nested objects.
|
||||
MaxDepth = DefaultMaxDepth,
|
||||
|
||||
// Do not change this setting
|
||||
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types
|
||||
TypeNameHandling = TypeNameHandling.None
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,11 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Core.Internal;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
|
|
@ -34,6 +36,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
ModelValidatorProviders = new List<IModelValidatorProvider>();
|
||||
ClientModelValidatorProviders = new List<IClientModelValidatorProvider>();
|
||||
CacheProfiles = new Dictionary<string, CacheProfile>(StringComparer.OrdinalIgnoreCase);
|
||||
SerializerSettings = SerializerSettingsProvider.CreateSerializerSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -81,6 +84,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// </summary>
|
||||
public IList<IInputFormatter> InputFormatters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="JsonSerializerSettings"/> that are used by this application.
|
||||
/// </summary>
|
||||
public JsonSerializerSettings SerializerSettings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="IExcludeTypeValidationFilter"/>s that are used by this application.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -34,10 +34,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <inheritdoc />
|
||||
public HtmlString Serialize(object value, [NotNull] JsonSerializerSettings serializerSettings)
|
||||
{
|
||||
var jsonOutputFormatter = new JsonOutputFormatter
|
||||
{
|
||||
SerializerSettings = serializerSettings
|
||||
};
|
||||
var jsonOutputFormatter = new JsonOutputFormatter(serializerSettings);
|
||||
|
||||
return SerializeInternal(jsonOutputFormatter, value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,10 +257,7 @@ namespace System.Web.Http
|
|||
[NonAction]
|
||||
public virtual JsonResult Json<T>([NotNull] T content, [NotNull] JsonSerializerSettings serializerSettings)
|
||||
{
|
||||
var formatter = new JsonOutputFormatter()
|
||||
{
|
||||
SerializerSettings = serializerSettings,
|
||||
};
|
||||
var formatter = new JsonOutputFormatter(serializerSettings);
|
||||
|
||||
return new JsonResult(content, formatter);
|
||||
}
|
||||
|
|
@ -279,10 +276,7 @@ namespace System.Web.Http
|
|||
[NotNull] JsonSerializerSettings serializerSettings,
|
||||
[NotNull] Encoding encoding)
|
||||
{
|
||||
var formatter = new JsonOutputFormatter()
|
||||
{
|
||||
SerializerSettings = serializerSettings,
|
||||
};
|
||||
var formatter = new JsonOutputFormatter(serializerSettings);
|
||||
|
||||
formatter.SupportedEncodings.Clear();
|
||||
formatter.SupportedEncodings.Add(encoding);
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ namespace Microsoft.AspNet.Mvc
|
|||
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
||||
options.OutputFormatters.Add(new StringOutputFormatter());
|
||||
options.OutputFormatters.Add(new StreamOutputFormatter());
|
||||
options.OutputFormatters.Add(new JsonOutputFormatter());
|
||||
options.OutputFormatters.Add(new JsonOutputFormatter(options.SerializerSettings));
|
||||
|
||||
// Set up default mapping for json extensions to content type
|
||||
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));
|
||||
|
||||
// Set up default input formatters.
|
||||
options.InputFormatters.Add(new JsonInputFormatter());
|
||||
options.InputFormatters.Add(new JsonPatchInputFormatter());
|
||||
options.InputFormatters.Add(new JsonInputFormatter(options.SerializerSettings));
|
||||
options.InputFormatters.Add(new JsonPatchInputFormatter(options.SerializerSettings));
|
||||
|
||||
// Set up ValueProviders
|
||||
options.ValueProviderFactories.Add(new RouteValueValueProviderFactory());
|
||||
|
|
|
|||
|
|
@ -102,7 +102,12 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
|
||||
}));
|
||||
|
||||
services.TryAdd(ServiceDescriptor.Instance(typeof(JsonOutputFormatter), new JsonOutputFormatter()));
|
||||
// JsonOutputFormatter should use the SerializerSettings on MvcOptions
|
||||
services.TryAdd(ServiceDescriptor.Singleton<JsonOutputFormatter>(serviceProvider =>
|
||||
{
|
||||
var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Options;
|
||||
return new JsonOutputFormatter(options.SerializerSettings);
|
||||
}));
|
||||
|
||||
// Razor, Views and runtime compilation
|
||||
|
||||
|
|
|
|||
|
|
@ -166,6 +166,18 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.NotNull(jsonFormatter.SerializerSettings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_UsesSerializerSettings()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var serializerSettings = new JsonSerializerSettings();
|
||||
var jsonFormatter = new JsonInputFormatter(serializerSettings);
|
||||
|
||||
// Assert
|
||||
Assert.Same(serializerSettings, jsonFormatter.SerializerSettings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChangesTo_DefaultSerializerSettings_TakesEffect()
|
||||
{
|
||||
|
|
@ -219,83 +231,6 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Contains("Required property 'Password' not found in JSON", modelErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsException_OnSupplyingNull_ForRequiredValueType()
|
||||
{
|
||||
// Arrange
|
||||
var contentBytes = Encoding.UTF8.GetBytes("{\"Id\":\"null\",\"Name\":\"Programming C#\"}");
|
||||
var jsonFormatter = new JsonInputFormatter();
|
||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Book));
|
||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||
|
||||
// Act
|
||||
var obj = await jsonFormatter.ReadAsync(inputFormatterContext);
|
||||
|
||||
// Assert
|
||||
var book = obj as Book;
|
||||
Assert.NotNull(book);
|
||||
Assert.Equal(0, book.Id);
|
||||
Assert.Equal("Programming C#", book.Name);
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
|
||||
Assert.Equal(1, actionContext.ModelState.Values.First().Errors.Count);
|
||||
var modelErrorMessage = actionContext.ModelState.Values.First().Errors[0].Exception.Message;
|
||||
Assert.Contains("Could not convert string to integer: null. Path 'Id'", modelErrorMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(Book))]
|
||||
[InlineData(typeof(EBook))]
|
||||
public async Task Validates_RequiredAttribute_OnRegularAndInheritedProperties(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var contentBytes = Encoding.UTF8.GetBytes("{ \"Name\" : \"Programming C#\"}");
|
||||
var jsonFormatter = new JsonInputFormatter();
|
||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(type);
|
||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||
|
||||
// Act
|
||||
var obj = await jsonFormatter.ReadAsync(inputFormatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
Assert.Equal(1, actionContext.ModelState.Count);
|
||||
|
||||
var modelErrorMessage = actionContext.ModelState.Values.First().Errors[0].Exception.Message;
|
||||
Assert.Contains("Required property 'Id' not found in JSON", modelErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Validates_RequiredAttributeOnStructTypes()
|
||||
{
|
||||
// Arrange
|
||||
var contentBytes = Encoding.UTF8.GetBytes("{\"Longitude\":{}}");
|
||||
var jsonFormatter = new JsonInputFormatter();
|
||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(GpsCoordinate));
|
||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||
|
||||
// Act
|
||||
var obj = await jsonFormatter.ReadAsync(inputFormatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(actionContext.ModelState.IsValid);
|
||||
Assert.Equal(2, actionContext.ModelState.Count);
|
||||
var errorMessages = GetModelStateErrorMessages(actionContext.ModelState);
|
||||
Assert.Equal(3, errorMessages.Count());
|
||||
Assert.Contains(
|
||||
errorMessages,
|
||||
(errorMessage) => errorMessage.Contains("Required property 'Latitude' not found in JSON"));
|
||||
Assert.Contains(
|
||||
errorMessages,
|
||||
(errorMessage) => errorMessage.Contains("Required property 'X' not found in JSON"));
|
||||
Assert.Contains(
|
||||
errorMessages,
|
||||
(errorMessage) => errorMessage.Contains("Required property 'Y' not found in JSON"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Validation_DoesNotHappen_ForNonRequired_ValueTypeProperties()
|
||||
{
|
||||
|
|
@ -317,28 +252,6 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal("Seattle", location.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Validation_DoesNotHappen_OnNullableValueTypeProperties()
|
||||
{
|
||||
// Arrange
|
||||
var contentBytes = Encoding.UTF8.GetBytes("{}");
|
||||
var jsonFormatter = new JsonInputFormatter();
|
||||
var actionContext = GetActionContext(contentBytes, "application/json;charset=utf-8");
|
||||
var metadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(Venue));
|
||||
var inputFormatterContext = new InputFormatterContext(actionContext, metadata.ModelType);
|
||||
|
||||
// Act
|
||||
var obj = await jsonFormatter.ReadAsync(inputFormatterContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(actionContext.ModelState.IsValid);
|
||||
var venue = obj as Venue;
|
||||
Assert.NotNull(venue);
|
||||
Assert.Null(venue.Location);
|
||||
Assert.Null(venue.NearByLocations);
|
||||
Assert.Null(venue.Name);
|
||||
}
|
||||
|
||||
private static ActionContext GetActionContext(byte[] contentBytes,
|
||||
string contentType = "application/xml")
|
||||
{
|
||||
|
|
@ -407,55 +320,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
private class Book
|
||||
{
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
private class EBook : Book
|
||||
{
|
||||
}
|
||||
|
||||
private struct Point
|
||||
{
|
||||
[Required]
|
||||
public int X { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Y { get; set; }
|
||||
}
|
||||
|
||||
private class GpsCoordinate
|
||||
{
|
||||
[Required]
|
||||
public Point Latitude { get; set; }
|
||||
|
||||
[Required]
|
||||
public Point Longitude { get; set; }
|
||||
}
|
||||
|
||||
private class Location
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
private class Venue
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required]
|
||||
public Point? Location { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<Point> NearByLocations { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ namespace Microsoft.AspNet.Mvc.Core.Test.Formatters
|
|||
Assert.NotNull(jsonFormatter.SerializerSettings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_UsesSerializerSettings()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var serializerSettings = new JsonSerializerSettings();
|
||||
var jsonFormatter = new JsonInputFormatter(serializerSettings);
|
||||
|
||||
// Assert
|
||||
Assert.Same(serializerSettings, jsonFormatter.SerializerSettings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChangesTo_DefaultSerializerSettings_TakesEffect()
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
|
||||
// Assert
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var customer = JsonConvert.DeserializeObject<Customer>(body);
|
||||
Assert.Equal("Order0", customer.Orders[1].OrderName);
|
||||
Assert.Null(customer.Orders[0].OrderName);
|
||||
|
|
|
|||
|
|
@ -1906,75 +1906,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal("March", user.RegisterationMonth);
|
||||
}
|
||||
|
||||
public static TheoryData<string, string[]> ModelStateHasErrorsForValueAndReferenceTypesData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string, string[]>()
|
||||
{
|
||||
{
|
||||
"{}",
|
||||
new[]
|
||||
{
|
||||
":Required property 'Id' not found in JSON",
|
||||
"Lines:The Lines field is required."
|
||||
}
|
||||
},
|
||||
{
|
||||
"{\"Id\":10}",
|
||||
new[]
|
||||
{
|
||||
"Lines:The Lines field is required."
|
||||
}
|
||||
},
|
||||
{
|
||||
"{\"Id\":10,\"Lines\":[{}]}",
|
||||
new []
|
||||
{
|
||||
"Lines[0]:Required property 'Start' not found in JSON",
|
||||
"Lines[0]:Required property 'End' not found in JSON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"{\"Id\":10,\"Lines\":[{\"Start\":{\"X\":10,\"Y\":10},\"End\":{\"X\":10}}]}",
|
||||
new []
|
||||
{
|
||||
"Lines[0].End:Required property 'Y' not found in JSON"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ModelStateHasErrorsForValueAndReferenceTypesData))]
|
||||
public async Task ModelState_HasErrors_ForValueAndReferenceTypes(
|
||||
string input,
|
||||
IEnumerable<string> expectedModelStateErrorMessages)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||
var client = server.CreateClient();
|
||||
var content = new StringContent(input, Encoding.UTF8, "text/json");
|
||||
|
||||
// Act
|
||||
var response = await client.PostAsync(
|
||||
"http://localhost/Validation/CreateRectangle",
|
||||
content);
|
||||
|
||||
// Assert
|
||||
var data = await response.Content.ReadAsStringAsync();
|
||||
var actualModelStateErrorMessages = JsonConvert.DeserializeObject<IEnumerable<string>>(data);
|
||||
Assert.NotNull(actualModelStateErrorMessages);
|
||||
Assert.Equal(expectedModelStateErrorMessages.Count(), actualModelStateErrorMessages.Count());
|
||||
foreach (var expectedErrorMessage in expectedModelStateErrorMessages)
|
||||
{
|
||||
Assert.Contains(
|
||||
actualModelStateErrorMessages,
|
||||
(actualErrorMessage) => actualErrorMessage.StartsWith(expectedErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModelAsync_WithCollection()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
||||
|
|
@ -207,5 +208,29 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.IsType<DefaultTypeNameBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[i++]);
|
||||
Assert.Equal(xmlNodeFilter.ExcludedTypeName, "System.Xml.XmlNode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Setup_JsonFormattersUseSerializerSettings()
|
||||
{
|
||||
// Arrange
|
||||
var mvcOptions = new MvcOptions();
|
||||
var setup = new MvcOptionsSetup();
|
||||
|
||||
// Act
|
||||
setup.Configure(mvcOptions);
|
||||
|
||||
// Assert
|
||||
var jsonInputFormatters = mvcOptions.InputFormatters.OfType<JsonInputFormatter>();
|
||||
foreach (var jsonInputFormatter in jsonInputFormatters)
|
||||
{
|
||||
Assert.Same(mvcOptions.SerializerSettings, jsonInputFormatter.SerializerSettings);
|
||||
}
|
||||
|
||||
var jsonOuputFormatters = mvcOptions.OutputFormatters.OfType<JsonOutputFormatter>();
|
||||
foreach (var jsonOuputFormatter in jsonOuputFormatters)
|
||||
{
|
||||
Assert.Same(mvcOptions.SerializerSettings, jsonOuputFormatter.SerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,51 +28,6 @@ namespace ModelBindingWebSite.Controllers
|
|||
{
|
||||
return ModelState.IsValid;
|
||||
}
|
||||
|
||||
public IActionResult CreateRectangle([FromBody] Rectangle rectangle)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return new ObjectResult(GetModelStateErrorMessages(ModelState)) { StatusCode = 400 };
|
||||
}
|
||||
|
||||
return new ObjectResult(rectangle);
|
||||
}
|
||||
|
||||
private IEnumerable<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 SelfishPerson
|
||||
|
|
|
|||
|
|
@ -1,18 +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.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class Drawing
|
||||
{
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<Line> Lines { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +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;
|
||||
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class Rectangle : Drawing
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue