From 23c02ef9a982258b0687aefb18df799d0b87ddde Mon Sep 17 00:00:00 2001 From: Pranav Krishnamoorthy Date: Tue, 14 Apr 2020 22:38:24 +0000 Subject: [PATCH] Merged PR 7269: Avoid caching JsonSerializer Avoid caching JsonSerializer --- .../src/NewtonsoftJsonOutputFormatter.cs | 50 ++++++++++++++++--- .../test/NewtonsoftJsonOutputFormatterTest.cs | 34 +++++++++++++ 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs index b6889c1c2d..e98d4477bd 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonOutputFormatter.cs @@ -20,10 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { private readonly IArrayPool _charPool; private readonly MvcOptions _mvcOptions; - - // 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; + private JsonSerializerSettings _serializerSettings; /// /// Initializes a new instance. @@ -99,12 +96,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// The used during serialization and deserialization. protected virtual JsonSerializer CreateJsonSerializer() { - if (_serializer == null) + if (_serializerSettings == null) { - _serializer = JsonSerializer.Create(SerializerSettings); + // Lock the serializer settings once the first serialization has been initiated. + _serializerSettings = ShallowCopy(SerializerSettings); } - return _serializer; + return JsonSerializer.Create(_serializerSettings); } /// @@ -166,5 +164,43 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } } } + + private static JsonSerializerSettings ShallowCopy(JsonSerializerSettings settings) + { + var copiedSettings = new JsonSerializerSettings + { + FloatParseHandling = settings.FloatParseHandling, + FloatFormatHandling = settings.FloatFormatHandling, + DateParseHandling = settings.DateParseHandling, + DateTimeZoneHandling = settings.DateTimeZoneHandling, + DateFormatHandling = settings.DateFormatHandling, + Formatting = settings.Formatting, + MaxDepth = settings.MaxDepth, + DateFormatString = settings.DateFormatString, + Context = settings.Context, + Error = settings.Error, + SerializationBinder = settings.SerializationBinder, + TraceWriter = settings.TraceWriter, + Culture = settings.Culture, + ReferenceResolverProvider = settings.ReferenceResolverProvider, + EqualityComparer = settings.EqualityComparer, + ContractResolver = settings.ContractResolver, + ConstructorHandling = settings.ConstructorHandling, + TypeNameAssemblyFormatHandling = settings.TypeNameAssemblyFormatHandling, + MetadataPropertyHandling = settings.MetadataPropertyHandling, + TypeNameHandling = settings.TypeNameHandling, + PreserveReferencesHandling = settings.PreserveReferencesHandling, + Converters = settings.Converters, + DefaultValueHandling = settings.DefaultValueHandling, + NullValueHandling = settings.NullValueHandling, + ObjectCreationHandling = settings.ObjectCreationHandling, + MissingMemberHandling = settings.MissingMemberHandling, + ReferenceLoopHandling = settings.ReferenceLoopHandling, + CheckAdditionalContent = settings.CheckAdditionalContent, + StringEscapeHandling = settings.StringEscapeHandling, + }; + + return copiedSettings; + } } } diff --git a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonOutputFormatterTest.cs b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonOutputFormatterTest.cs index a04c79c638..67fdde1b22 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonOutputFormatterTest.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/test/NewtonsoftJsonOutputFormatterTest.cs @@ -324,6 +324,40 @@ namespace Microsoft.AspNetCore.Mvc.Formatters stream.Verify(v => v.Flush(), Times.Never()); } + [Fact] + public async Task SerializingWithPreserveReferenceHandling() + { + // Arrange + var expected = "{\"$id\":\"1\",\"fullName\":\"John\",\"age\":35}"; + var user = new User { FullName = "John", age = 35 }; + + var settings = new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy(), + }, + PreserveReferencesHandling = PreserveReferencesHandling.All, + }; + var formatter = new TestableJsonOutputFormatter(settings); + + for (var i = 0; i < 3; i++) + { + // Act + var context = GetOutputFormatterContext(user, typeof(User)); + await formatter.WriteResponseBodyAsync(context, Encoding.UTF8); + + // Assert + var body = context.HttpContext.Response.Body; + + Assert.NotNull(body); + body.Position = 0; + + var content = new StreamReader(body, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expected, content); + } + } + private class TestableJsonOutputFormatter : NewtonsoftJsonOutputFormatter { public TestableJsonOutputFormatter(JsonSerializerSettings serializerSettings)