Merged PR 7269: Avoid caching JsonSerializer

Avoid caching JsonSerializer
This commit is contained in:
Pranav Krishnamoorthy 2020-04-14 22:38:24 +00:00
parent 45a57eac45
commit 23c02ef9a9
2 changed files with 77 additions and 7 deletions

View File

@ -20,10 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
private readonly IArrayPool<char> _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;
/// <summary>
/// Initializes a new <see cref="NewtonsoftJsonOutputFormatter"/> instance.
@ -99,12 +96,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
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);
}
/// <summary>
@ -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;
}
}
}

View File

@ -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)