diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs index 70ef8c2512..975ebf23b8 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs @@ -17,6 +17,10 @@ namespace Microsoft.AspNet.Mvc.Formatters { private JsonSerializerSettings _serializerSettings; + // Perf: JsonSerializers are relatively expensive to create, and are thread safe. We cache + // the serializer and invalidate it when the settings change. + private JsonSerializer _serializer; + public JsonOutputFormatter() : this(SerializerSettingsProvider.CreateSerializerSettings()) { @@ -40,6 +44,10 @@ namespace Microsoft.AspNet.Mvc.Formatters /// /// Gets or sets the used to configure the . /// + /// + /// Any modifications to the object after this + /// has been used will have no effect. + /// public JsonSerializerSettings SerializerSettings { get @@ -54,6 +62,9 @@ namespace Microsoft.AspNet.Mvc.Formatters } _serializerSettings = value; + + // If the settings change, then invalidate the cached serializer. + _serializer = null; } } @@ -95,7 +106,12 @@ namespace Microsoft.AspNet.Mvc.Formatters /// The used during serialization and deserialization. protected virtual JsonSerializer CreateJsonSerializer() { - return JsonSerializer.Create(SerializerSettings); + if (_serializer == null) + { + _serializer = JsonSerializer.Create(SerializerSettings); + } + + return _serializer; } public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs index b748f0f9bf..d5fc0b8c76 100644 --- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; +using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Microsoft.Net.Http.Headers; @@ -74,6 +75,79 @@ namespace Microsoft.AspNet.Mvc.Formatters Assert.Equal(expectedOutput, content); } + [Fact] + public async Task ChangesTo_DefaultSerializerSettings_AfterSerialization_NoEffect() + { + // Arrange + var person = new User() { Name = "John", Age = 35 }; + var expectedOutput = JsonConvert.SerializeObject( + person, + SerializerSettingsProvider.CreateSerializerSettings()); + + var jsonFormatter = new JsonOutputFormatter(); + + // This will create a serializer - which gets cached. + var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User)); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1); + + // These changes should have no effect. + jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonFormatter.SerializerSettings.Formatting = Formatting.Indented; + + var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User)); + + // Act + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2); + + // Assert + var body = outputFormatterContext2.HttpContext.Response.Body; + + Assert.NotNull(body); + body.Position = 0; + + var content = new StreamReader(body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, content); + } + + [Fact] + public async Task ReplaceSerializerSettings_AfterSerialization_TakesEffect() + { + // Arrange + var person = new User() { Name = "John", Age = 35 }; + var expectedOutput = JsonConvert.SerializeObject(person, new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Formatting.Indented + }); + + var jsonFormatter = new JsonOutputFormatter(); + + // This will create a serializer - which gets cached. + var outputFormatterContext1 = GetOutputFormatterContext(person, typeof(User)); + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext1); + + // This results in a new serializer being created. + jsonFormatter.SerializerSettings = new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Formatting.Indented, + }; + + var outputFormatterContext2 = GetOutputFormatterContext(person, typeof(User)); + + // Act + await jsonFormatter.WriteResponseBodyAsync(outputFormatterContext2); + + // Assert + var body = outputFormatterContext2.HttpContext.Response.Body; + + Assert.NotNull(body); + body.Position = 0; + + var content = new StreamReader(body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, content); + } + [Fact] public async Task CustomSerializerSettingsObject_TakesEffect() {