From 8d09403118ca5d3d972b599b50f942be359a9a49 Mon Sep 17 00:00:00 2001 From: Pranav Krishnamoorthy Date: Tue, 14 Apr 2020 17:49:57 +0000 Subject: [PATCH] Merged PR 7268: Avoid caching JsonSerializer Avoid caching JsonSerializer --- eng/PatchConfig.props | 1 + .../src/JsonOutputFormatter.cs | 49 ++++++++++++++++--- .../test/JsonOutputFormatterTests.cs | 36 +++++++++++++- ...AspNetCore.Mvc.Formatters.Json.Test.csproj | 1 + 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/eng/PatchConfig.props b/eng/PatchConfig.props index 46ef4cd3c4d4..4e0ba9c9556b 100644 --- a/eng/PatchConfig.props +++ b/eng/PatchConfig.props @@ -62,6 +62,7 @@ Later on, this will be checked using this condition: + Microsoft.AspNetCore.Mvc.Formatters.Json; diff --git a/src/Mvc/Mvc.Formatters.Json/src/JsonOutputFormatter.cs b/src/Mvc/Mvc.Formatters.Json/src/JsonOutputFormatter.cs index 744d55868090..bab47261d7ea 100644 --- a/src/Mvc/Mvc.Formatters.Json/src/JsonOutputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Json/src/JsonOutputFormatter.cs @@ -18,10 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters public class JsonOutputFormatter : TextOutputFormatter { private readonly IArrayPool _charPool; - - // 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. @@ -121,12 +118,12 @@ protected virtual JsonWriter CreateJsonWriter(TextWriter writer) /// The used during serialization and deserialization. protected virtual JsonSerializer CreateJsonSerializer() { - if (_serializer == null) + if (_serializerSettings == null) { - _serializer = JsonSerializer.Create(SerializerSettings); + _serializerSettings = ShallowCopy(SerializerSettings); } - return _serializer; + return JsonSerializer.Create(_serializerSettings); } /// @@ -153,5 +150,43 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co await writer.FlushAsync(); } } + + 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.Formatters.Json/test/JsonOutputFormatterTests.cs b/src/Mvc/Mvc.Formatters.Json/test/JsonOutputFormatterTests.cs index 15e735a3286a..595ebc426dc6 100644 --- a/src/Mvc/Mvc.Formatters.Json/test/JsonOutputFormatterTests.cs +++ b/src/Mvc/Mvc.Formatters.Json/test/JsonOutputFormatterTests.cs @@ -403,7 +403,7 @@ public async Task ErrorDuringSerialization_DoesNotCloseTheBrackets() { // Arrange var formatter = new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool.Shared); - + var body = new MemoryStream(); var actionContext = GetActionContext(MediaTypeHeaderValue.Parse(mediaType), body); var outputFormatterContext = new OutputFormatterWriteContext( @@ -425,6 +425,40 @@ public async Task ErrorDuringSerialization_DoesNotCloseTheBrackets() Assert.Equal(new StringSegment(expectedContentType), outputFormatterContext.ContentType); } + [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 static Encoding CreateOrGetSupportedEncoding( JsonOutputFormatter formatter, string encodingAsString, diff --git a/src/Mvc/Mvc.Formatters.Json/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj b/src/Mvc/Mvc.Formatters.Json/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj index 6bea21ef4ee4..dc89f857d6ea 100644 --- a/src/Mvc/Mvc.Formatters.Json/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj +++ b/src/Mvc/Mvc.Formatters.Json/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj @@ -5,6 +5,7 @@ +