diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/JsonHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/JsonHelperExtensions.cs index 4c83a279c7..0cab536616 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/JsonHelperExtensions.cs +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/JsonHelperExtensions.cs @@ -23,6 +23,10 @@ namespace Microsoft.AspNetCore.Mvc.Rendering /// The to be used by the serializer. /// /// A new containing the serialized JSON. + /// + /// The value for from + /// is ignored by this method and is always used. + /// public static IHtmlContent Serialize( this IJsonHelper jsonHelper, object value, diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonHelper.cs index 5179cdf7bc..d72aceee7d 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonHelper.cs +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonHelper.cs @@ -6,8 +6,8 @@ using System.Buffers; using System.Globalization; using System.IO; using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.Options; using Newtonsoft.Json; namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson @@ -17,42 +17,39 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson /// internal class NewtonsoftJsonHelper : IJsonHelper { - private readonly NewtonsoftJsonOutputFormatter _jsonOutputFormatter; - private readonly ArrayPool _charPool; + // Perf: JsonSerializers are relatively expensive to create, and are thread safe. Cache the serializer + private readonly JsonSerializer _defaultSettingsJsonSerializer; + private readonly IArrayPool _charPool; /// - /// Initializes a new instance of that is backed by . + /// Initializes a new instance of . /// - /// The used to serialize JSON. + /// The . /// /// The for use with custom (see /// ). /// - public NewtonsoftJsonHelper(NewtonsoftJsonOutputFormatter jsonOutputFormatter, ArrayPool charPool) + public NewtonsoftJsonHelper(IOptions options, ArrayPool charPool) { - if (jsonOutputFormatter == null) + if (options == null) { - throw new ArgumentNullException(nameof(jsonOutputFormatter)); + throw new ArgumentNullException(nameof(options)); } + if (charPool == null) { throw new ArgumentNullException(nameof(charPool)); } - _jsonOutputFormatter = jsonOutputFormatter; - _charPool = charPool; + _defaultSettingsJsonSerializer = CreateHtmlSafeSerializer(options.Value.SerializerSettings); + _charPool = new JsonArrayPool(charPool); } - /// public IHtmlContent Serialize(object value) { - var settings = ShallowCopy(_jsonOutputFormatter.PublicSerializerSettings); - settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml; - - return Serialize(value, settings); + return Serialize(value, _defaultSettingsJsonSerializer); } - /// public IHtmlContent Serialize(object value, JsonSerializerSettings serializerSettings) { if (serializerSettings == null) @@ -60,54 +57,34 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson throw new ArgumentNullException(nameof(serializerSettings)); } - var jsonOutputFormatter = new NewtonsoftJsonOutputFormatter(serializerSettings, _charPool); - - return SerializeInternal(jsonOutputFormatter, value); + var jsonSerializer = CreateHtmlSafeSerializer(serializerSettings); + return Serialize(value, jsonSerializer); } - private IHtmlContent SerializeInternal(NewtonsoftJsonOutputFormatter jsonOutputFormatter, object value) + private IHtmlContent Serialize(object value, JsonSerializer jsonSerializer) { - var stringWriter = new StringWriter(CultureInfo.InvariantCulture); - jsonOutputFormatter.WriteObject(stringWriter, value); - - return new HtmlString(stringWriter.ToString()); - } - - private static JsonSerializerSettings ShallowCopy(JsonSerializerSettings settings) - { - var copiedSettings = new JsonSerializerSettings + using (var stringWriter = new StringWriter(CultureInfo.InvariantCulture)) { - 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, - }; + var jsonWriter = new JsonTextWriter(stringWriter) + { + ArrayPool = _charPool, + }; - return copiedSettings; + using (jsonWriter) + { + jsonSerializer.Serialize(jsonWriter, value); + } + + return new HtmlString(stringWriter.ToString()); + } + } + + private static JsonSerializer CreateHtmlSafeSerializer(JsonSerializerSettings serializerSettings) + { + var jsonSerializer = JsonSerializer.Create(serializerSettings); + // Ignore the user configured StringEscapeHandling and always escape it. + jsonSerializer.StringEscapeHandling = StringEscapeHandling.EscapeHtml; + return jsonSerializer; } } } diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonOutputFormatter.cs index 57825099ad..382ca577ff 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonOutputFormatter.cs +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson/NewtonsoftJsonOutputFormatter.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.ComponentModel; using System.IO; using System.Text; using System.Threading.Tasks; @@ -63,36 +62,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// protected JsonSerializerSettings SerializerSettings { get; } - /// - /// Gets the used to configure the . - /// - /// - /// Any modifications to the object after this - /// has been used will have no effect. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public JsonSerializerSettings PublicSerializerSettings => SerializerSettings; - - /// - /// Writes the given as JSON using the given - /// . - /// - /// The used to write the - /// The value to write as JSON. - public void WriteObject(TextWriter writer, object value) - { - if (writer == null) - { - throw new ArgumentNullException(nameof(writer)); - } - - using (var jsonWriter = CreateJsonWriter(writer)) - { - var jsonSerializer = CreateJsonSerializer(); - jsonSerializer.Serialize(jsonWriter, value); - } - } - /// /// Called during serialization to create the . /// @@ -145,7 +114,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var response = context.HttpContext.Response; using (var writer = context.WriterFactory(response.Body, selectedEncoding)) { - WriteObject(writer, context.Object); + using (var jsonWriter = CreateJsonWriter(writer)) + { + var jsonSerializer = CreateJsonSerializer(); + jsonSerializer.Serialize(jsonWriter, context.Object); + } // Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's // buffers. This is better than just letting dispose handle it (which would result in a synchronous diff --git a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs index d17c06b9d9..102dbcd811 100644 --- a/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs +++ b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs @@ -265,7 +265,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task JsonHelperWithSettings_RendersJson_WithNamesUnchanged() { // Arrange - var json = "{\"id\":9000,\"FullName\":\"John Smith\"}"; + var json = "{\"id\":9000,\"FullName\":\"John \\u003cb\\u003eSmith\\u003c/b\\u003e\"}"; var expectedBody = string.Format( @"