|
2 | 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
3 | 3 |
|
4 | 4 | using System;
|
5 |
| -using System.IO; |
| 5 | +using System.Runtime.ExceptionServices; |
6 | 6 | using System.Text;
|
7 | 7 | using System.Text.Encodings.Web;
|
8 | 8 | using System.Text.Json;
|
9 |
| -using System.Threading; |
10 | 9 | using System.Threading.Tasks;
|
11 |
| -using Microsoft.AspNetCore.Http; |
12 |
| -using Microsoft.AspNetCore.Mvc.Formatters.Json; |
13 | 10 |
|
14 | 11 | namespace Microsoft.AspNetCore.Mvc.Formatters
|
15 | 12 | {
|
@@ -73,44 +70,50 @@ public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteCon
|
73 | 70 |
|
74 | 71 | var httpContext = context.HttpContext;
|
75 | 72 |
|
76 |
| - var writeStream = GetWriteStream(httpContext, selectedEncoding); |
77 |
| - try |
| 73 | + // context.ObjectType reflects the declared model type when specified. |
| 74 | + // For polymorphic scenarios where the user declares a return type, but returns a derived type, |
| 75 | + // we want to serialize all the properties on the derived type. This keeps parity with |
| 76 | + // the behavior you get when the user does not declare the return type and with Json.Net at least at the top level. |
| 77 | + var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object); |
| 78 | + |
| 79 | + var responseStream = httpContext.Response.Body; |
| 80 | + if (selectedEncoding.CodePage == Encoding.UTF8.CodePage) |
| 81 | + { |
| 82 | + await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions); |
| 83 | + await responseStream.FlushAsync(); |
| 84 | + } |
| 85 | + else |
78 | 86 | {
|
79 |
| - // context.ObjectType reflects the declared model type when specified. |
80 |
| - // For polymorphic scenarios where the user declares a return type, but returns a derived type, |
81 |
| - // we want to serialize all the properties on the derived type. This keeps parity with |
82 |
| - // the behavior you get when the user does not declare the return type and with Json.Net at least at the top level. |
83 |
| - var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object); |
84 |
| - await JsonSerializer.SerializeAsync(writeStream, context.Object, objectType, SerializerOptions); |
| 87 | + // JsonSerializer only emits UTF8 encoded output, but we need to write the response in the encoding specified by |
| 88 | + // selectedEncoding |
| 89 | + var transcodingStream = Encoding.CreateTranscodingStream(httpContext.Response.Body, selectedEncoding, Encoding.UTF8, leaveOpen: true); |
85 | 90 |
|
86 |
| - // The transcoding streams use Encoders and Decoders that have internal buffers. We need to flush these |
87 |
| - // when there is no more data to be written. Stream.FlushAsync isn't suitable since it's |
88 |
| - // acceptable to Flush a Stream (multiple times) prior to completion. |
89 |
| - if (writeStream is TranscodingWriteStream transcodingStream) |
| 91 | + ExceptionDispatchInfo exceptionDispatchInfo = null; |
| 92 | + try |
90 | 93 | {
|
91 |
| - await transcodingStream.FinalWriteAsync(CancellationToken.None); |
| 94 | + await JsonSerializer.SerializeAsync(transcodingStream, context.Object, objectType, SerializerOptions); |
| 95 | + await transcodingStream.FlushAsync(); |
92 | 96 | }
|
93 |
| - await writeStream.FlushAsync(); |
94 |
| - } |
95 |
| - finally |
96 |
| - { |
97 |
| - if (writeStream is TranscodingWriteStream transcodingStream) |
| 97 | + catch (Exception ex) |
98 | 98 | {
|
99 |
| - await transcodingStream.DisposeAsync(); |
| 99 | + // TranscodingStream may write to the inner stream as part of it's disposal. |
| 100 | + // We do not want this exception "ex" to be eclipsed by any exception encountered during the write. We will stash it and |
| 101 | + // explicitly rethrow it during the finally block. |
| 102 | + exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); |
100 | 103 | }
|
101 |
| - } |
102 |
| - } |
| 104 | + finally |
| 105 | + { |
| 106 | + try |
| 107 | + { |
| 108 | + await transcodingStream.DisposeAsync(); |
| 109 | + } |
| 110 | + catch when (exceptionDispatchInfo != null) |
| 111 | + { |
| 112 | + } |
103 | 113 |
|
104 |
| - private Stream GetWriteStream(HttpContext httpContext, Encoding selectedEncoding) |
105 |
| - { |
106 |
| - if (selectedEncoding.CodePage == Encoding.UTF8.CodePage) |
107 |
| - { |
108 |
| - // JsonSerializer does not write a BOM. Therefore we do not have to handle it |
109 |
| - // in any special way. |
110 |
| - return httpContext.Response.Body; |
| 114 | + exceptionDispatchInfo?.Throw(); |
| 115 | + } |
111 | 116 | }
|
112 |
| - |
113 |
| - return new TranscodingWriteStream(httpContext.Response.Body, selectedEncoding); |
114 | 117 | }
|
115 | 118 | }
|
116 | 119 | }
|
0 commit comments