aspnetcore/src/Microsoft.AspNet.Mvc.Format.../JsonOutputFormatter.cs

157 lines
5.3 KiB
C#

// 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
{
/// <summary>
/// An output formatter that specializes in writing JSON content.
/// </summary>
public class JsonOutputFormatter : OutputFormatter
{
private readonly IArrayPool<char> _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<char>.Shared)
{
}
public JsonOutputFormatter(JsonSerializerSettings serializerSettings)
: this(serializerSettings, ArrayPool<char>.Shared)
{
}
public JsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<char> charPool)
{
if (serializerSettings == null)
{
throw new ArgumentNullException(nameof(serializerSettings));
}
if (charPool == null)
{
throw new ArgumentNullException(nameof(charPool));
}
_serializerSettings = serializerSettings;
_charPool = new JsonArrayPool<char>(charPool);
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson);
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson);
}
/// <summary>
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
/// </summary>
/// <remarks>
/// Any modifications to the <see cref="JsonSerializerSettings"/> object after this
/// <see cref="JsonOutputFormatter"/> has been used will have no effect.
/// </remarks>
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);
}
}
/// <summary>
/// Called during serialization to create the <see cref="JsonWriter"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> used to write.</param>
/// <returns>The <see cref="JsonWriter"/> used during serialization.</returns>
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;
}
/// <summary>
/// Called during serialization to create the <see cref="JsonSerializer"/>.
/// </summary>
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
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();
}
}
}
}