// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Formatters.Json.Internal; using Microsoft.AspNet.Mvc.Internal; using Newtonsoft.Json; namespace Microsoft.AspNet.Mvc.Formatters { /// /// An output formatter that specializes in writing JSON content. /// public class JsonOutputFormatter : OutputFormatter { private readonly IArrayPool _charPool; 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(), ArrayPool.Shared) { } public JsonOutputFormatter(JsonSerializerSettings serializerSettings) : this(serializerSettings, ArrayPool.Shared) { } public JsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool charPool) { if (serializerSettings == null) { throw new ArgumentNullException(nameof(serializerSettings)); } if (charPool == null) { throw new ArgumentNullException(nameof(charPool)); } _serializerSettings = serializerSettings; _charPool = new JsonArrayPool(charPool); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); } /// /// 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 { return _serializerSettings; } set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _serializerSettings = value; // If the settings change, then invalidate the cached serializer. _serializer = null; } } 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 . /// /// The used to write. /// The used during serialization. protected virtual JsonWriter CreateJsonWriter(TextWriter writer) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } var jsonWriter = new JsonTextWriter(writer) { ArrayPool = _charPool, CloseOutput = false, }; return jsonWriter; } /// /// Called during serialization to create the . /// /// The used during serialization and deserialization. protected virtual JsonSerializer CreateJsonSerializer() { if (_serializer == null) { _serializer = JsonSerializer.Create(SerializerSettings); } return _serializer; } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var response = context.HttpContext.Response; var selectedEncoding = context.ContentType?.Encoding ?? Encoding.UTF8; using (var writer = context.WriterFactory(response.Body, selectedEncoding)) { WriteObject(writer, 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 // write). await writer.FlushAsync(); } } } }