| File: Formatters\SystemTextJsonOutputFormatter.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.ExceptionServices; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Mvc.Formatters; /// <summary> /// A <see cref="TextOutputFormatter"/> for JSON content that uses <see cref="JsonSerializer"/>. /// </summary> public class SystemTextJsonOutputFormatter : TextOutputFormatter { /// <summary> /// Initializes a new <see cref="SystemTextJsonOutputFormatter"/> instance. /// </summary> /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/>.</param> public SystemTextJsonOutputFormatter(JsonSerializerOptions jsonSerializerOptions) { SerializerOptions = jsonSerializerOptions; jsonSerializerOptions.MakeReadOnly(); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); } internal static SystemTextJsonOutputFormatter CreateFormatter(JsonOptions jsonOptions) { var jsonSerializerOptions = jsonOptions.JsonSerializerOptions; if (jsonSerializerOptions.Encoder is null) { // If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters. jsonSerializerOptions = new JsonSerializerOptions(jsonSerializerOptions) { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; } return new SystemTextJsonOutputFormatter(jsonSerializerOptions); } /// <summary> /// Gets the <see cref="JsonSerializerOptions"/> used to configure the <see cref="JsonSerializer"/>. /// </summary> /// <remarks> /// A single instance of <see cref="SystemTextJsonOutputFormatter"/> is used for all JSON formatting. Any /// changes to the options will affect all output formatting. /// </remarks> public JsonSerializerOptions SerializerOptions { get; } /// <inheritdoc /> public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(selectedEncoding); var httpContext = context.HttpContext; // context.ObjectType reflects the declared model type when specified. // For polymorphic scenarios where the user declares a return type, but returns a derived type, // we want to serialize all the properties on the derived type. This keeps parity with // the behavior you get when the user does not declare the return type. // To enable this our best option is to check if the JsonTypeInfo for the declared type is valid, // if it is use it. If it isn't, serialize the value as 'object' and let JsonSerializer serialize it as necessary. JsonTypeInfo? jsonTypeInfo = null; if (context.ObjectType is not null) { var declaredTypeJsonInfo = SerializerOptions.GetTypeInfo(context.ObjectType); var runtimeType = context.Object?.GetType(); if (declaredTypeJsonInfo.ShouldUseWith(runtimeType)) { jsonTypeInfo = declaredTypeJsonInfo; } } if (selectedEncoding.CodePage == Encoding.UTF8.CodePage) { try { var responseWriter = httpContext.Response.BodyWriter; if (jsonTypeInfo is not null) { await JsonSerializer.SerializeAsync(responseWriter, context.Object, jsonTypeInfo, httpContext.RequestAborted); } else { await JsonSerializer.SerializeAsync(responseWriter, context.Object, SerializerOptions, httpContext.RequestAborted); } } catch (OperationCanceledException) when (context.HttpContext.RequestAborted.IsCancellationRequested) { } } else { // JsonSerializer only emits UTF8 encoded output, but we need to write the response in the encoding specified by // selectedEncoding var transcodingStream = Encoding.CreateTranscodingStream(httpContext.Response.Body, selectedEncoding, Encoding.UTF8, leaveOpen: true); ExceptionDispatchInfo? exceptionDispatchInfo = null; try { if (jsonTypeInfo is not null) { await JsonSerializer.SerializeAsync(transcodingStream, context.Object, jsonTypeInfo); } else { await JsonSerializer.SerializeAsync(transcodingStream, context.Object, SerializerOptions); } await transcodingStream.FlushAsync(); } catch (Exception ex) { // TranscodingStream may write to the inner stream as part of it's disposal. // We do not want this exception "ex" to be eclipsed by any exception encountered during the write. We will stash it and // explicitly rethrow it during the finally block. exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); } finally { try { await transcodingStream.DisposeAsync(); } catch when (exceptionDispatchInfo != null) { } exceptionDispatchInfo?.Throw(); } } } }