From de630754bfcd4ba4017a64f74f30f144e1977e96 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 9 May 2015 06:25:14 +0000 Subject: [PATCH] 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 --- .../ActionResults/JsonResult.cs | 2 +- .../Formatters/JsonContractResolver.cs | 43 ----- .../Formatters/JsonInputFormatter.cs | 39 ++--- .../Formatters/JsonOutputFormatter.cs | 21 ++- .../Formatters/JsonPatchInputFormatter.cs | 10 +- .../Internal/SerializerSettingsProvider.cs | 35 ++++ src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs | 8 + .../Rendering/JsonHelper.cs | 5 +- .../ApiController.cs | 10 +- src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 6 +- .../MvcServiceCollectionExtensions.cs | 7 +- .../Formatters/JsonInputFormatterTest.cs | 154 ++---------------- .../Formatters/JsonOutputFormatterTests.cs | 11 ++ .../JsonPatchTest.cs | 1 + .../ModelBindingTest.cs | 69 -------- .../MvcOptionsSetupTest.cs | 25 +++ .../Controllers/ValidationController.cs | 45 ----- .../ModelBindingWebSite/Models/Drawing.cs | 18 -- .../ModelBindingWebSite/Models/Rectangle.cs | 12 -- 19 files changed, 142 insertions(+), 379 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Core/Formatters/JsonContractResolver.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Internal/SerializerSettingsProvider.cs delete mode 100644 test/WebSites/ModelBindingWebSite/Models/Drawing.cs delete mode 100644 test/WebSites/ModelBindingWebSite/Models/Rectangle.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs index 3318b2c5d6..faa96e6cc9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNet.Mvc /// The to be used by /// the formatter. public JsonResult(object value, [NotNull] JsonSerializerSettings serializerSettings) - : this(value, formatter: new JsonOutputFormatter { SerializerSettings = serializerSettings }) + : this(value, formatter: new JsonOutputFormatter(serializerSettings)) { } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonContractResolver.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonContractResolver.cs deleted file mode 100644 index d46c85f265..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonContractResolver.cs +++ /dev/null @@ -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 -{ - /// - /// The default for . - /// It determines if a value type member has and sets the appropriate - /// JsonProperty settings. - /// - 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; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs index 0d56574795..3498331fef 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs @@ -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(); } /// @@ -45,15 +37,14 @@ namespace Microsoft.AspNet.Mvc /// public JsonSerializerSettings SerializerSettings { - get { return _jsonSerializerSettings; } + get + { + return _serializerSettings; + } + [param: NotNull] set { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _jsonSerializerSettings = value; + _serializerSettings = value; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs index 1b4ab69cf9..e6f9a47bd9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs @@ -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(); } /// @@ -30,14 +36,13 @@ namespace Microsoft.AspNet.Mvc /// public JsonSerializerSettings SerializerSettings { - get { return _serializerSettings; } + get + { + return _serializerSettings; + } + [param: NotNull] set { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - _serializerSettings = value; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonPatchInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonPatchInputFormatter.cs index 2c5ccdf7ce..bcdd9f5a1a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonPatchInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonPatchInputFormatter.cs @@ -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 ReadRequestBodyAsync([NotNull] InputFormatterContext context) { var jsonPatchDocument = (IJsonPatchDocument)(await base.ReadRequestBodyAsync(context)); - if (jsonPatchDocument != null) + if (jsonPatchDocument != null && SerializerSettings.ContractResolver != null) { jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/SerializerSettingsProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/SerializerSettingsProvider.cs new file mode 100644 index 0000000000..86c416061b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Internal/SerializerSettingsProvider.cs @@ -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 +{ + /// + /// Helper class which provides . + /// + internal static class SerializerSettingsProvider + { + private const int DefaultMaxDepth = 32; + + /// + /// Creates default . + /// + /// Default . + 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 + }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index 58cfdae65f..4f831f9d85 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -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(); ClientModelValidatorProviders = new List(); CacheProfiles = new Dictionary(StringComparer.OrdinalIgnoreCase); + SerializerSettings = SerializerSettingsProvider.CreateSerializerSettings(); } /// @@ -81,6 +84,11 @@ namespace Microsoft.AspNet.Mvc /// public IList InputFormatters { get; } + /// + /// Gets the that are used by this application. + /// + public JsonSerializerSettings SerializerSettings { get; } + /// /// Gets a list of s that are used by this application. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/JsonHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/JsonHelper.cs index 789b4783c8..ba84f76e5d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/JsonHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/JsonHelper.cs @@ -34,10 +34,7 @@ namespace Microsoft.AspNet.Mvc.Rendering /// public HtmlString Serialize(object value, [NotNull] JsonSerializerSettings serializerSettings) { - var jsonOutputFormatter = new JsonOutputFormatter - { - SerializerSettings = serializerSettings - }; + var jsonOutputFormatter = new JsonOutputFormatter(serializerSettings); return SerializeInternal(jsonOutputFormatter, value); } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index b3d9cf2f8e..4e00bdb9f2 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs @@ -257,10 +257,7 @@ namespace System.Web.Http [NonAction] public virtual JsonResult Json([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); diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index 1bfcbd0852..f75df92f12 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -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()); diff --git a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs index 0091994a11..37bb13f21b 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs @@ -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(serviceProvider => + { + var options = serviceProvider.GetRequiredService>().Options; + return new JsonOutputFormatter(options.SerializerSettings); + })); // Razor, Views and runtime compilation diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs index ee8d39caba..61ddfe47c5 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs @@ -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 NearByLocations { get; set; } - } } } #endif diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs index 8ff99b3b3d..6e587a48bc 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonOutputFormatterTests.cs @@ -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() diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs index 99d7e3aa17..c9e3abd431 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/JsonPatchTest.cs @@ -133,6 +133,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert var body = await response.Content.ReadAsStringAsync(); + var customer = JsonConvert.DeserializeObject(body); Assert.Equal("Order0", customer.Orders[1].OrderName); Assert.Null(customer.Orders[0].OrderName); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs index b40d752e1a..e781c6d91a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs @@ -1906,75 +1906,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("March", user.RegisterationMonth); } - public static TheoryData ModelStateHasErrorsForValueAndReferenceTypesData - { - get - { - return new TheoryData() - { - { - "{}", - 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 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>(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() { diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs index 9c886ba18a..20645dc032 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs @@ -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(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(); + foreach (var jsonInputFormatter in jsonInputFormatters) + { + Assert.Same(mvcOptions.SerializerSettings, jsonInputFormatter.SerializerSettings); + } + + var jsonOuputFormatters = mvcOptions.OutputFormatters.OfType(); + foreach (var jsonOuputFormatter in jsonOuputFormatters) + { + Assert.Same(mvcOptions.SerializerSettings, jsonOuputFormatter.SerializerSettings); + } + } } } \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Controllers/ValidationController.cs b/test/WebSites/ModelBindingWebSite/Controllers/ValidationController.cs index 77ce5d7b49..db32d0bbd1 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/ValidationController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/ValidationController.cs @@ -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 GetModelStateErrorMessages(ModelStateDictionary modelStateDictionary) - { - var allErrorMessages = new List(); - 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 diff --git a/test/WebSites/ModelBindingWebSite/Models/Drawing.cs b/test/WebSites/ModelBindingWebSite/Models/Drawing.cs deleted file mode 100644 index 1401a399da..0000000000 --- a/test/WebSites/ModelBindingWebSite/Models/Drawing.cs +++ /dev/null @@ -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 Lines { get; set; } - } -} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Models/Rectangle.cs b/test/WebSites/ModelBindingWebSite/Models/Rectangle.cs deleted file mode 100644 index 130673bdbc..0000000000 --- a/test/WebSites/ModelBindingWebSite/Models/Rectangle.cs +++ /dev/null @@ -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 - { - - } -} \ No newline at end of file