Skip to content

Commit 51f18b5

Browse files
authored
Use Encoding.CreateTranscodingStream (#21509)
* Use Encoding.CreateTranscodingStream Fixes #21243
1 parent 5fd4f87 commit 51f18b5

12 files changed

+193
-825
lines changed

src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Text.Json;
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Http;
10-
using Microsoft.AspNetCore.Mvc.Formatters.Json;
1110
using Microsoft.Extensions.Logging;
1211

1312
namespace Microsoft.AspNetCore.Mvc.Formatters
@@ -67,7 +66,7 @@ public sealed override async Task<InputFormatterResult> ReadRequestBodyAsync(
6766
}
6867

6968
var httpContext = context.HttpContext;
70-
var inputStream = GetInputStream(httpContext, encoding);
69+
var (inputStream, usesTranscodingStream) = GetInputStream(httpContext, encoding);
7170

7271
object model;
7372
try
@@ -98,9 +97,9 @@ public sealed override async Task<InputFormatterResult> ReadRequestBodyAsync(
9897
}
9998
finally
10099
{
101-
if (inputStream is TranscodingReadStream transcoding)
100+
if (usesTranscodingStream)
102101
{
103-
await transcoding.DisposeAsync();
102+
await inputStream.DisposeAsync();
104103
}
105104
}
106105

@@ -119,14 +118,15 @@ public sealed override async Task<InputFormatterResult> ReadRequestBodyAsync(
119118
}
120119
}
121120

122-
private Stream GetInputStream(HttpContext httpContext, Encoding encoding)
121+
private (Stream inputStream, bool usesTranscodingStream) GetInputStream(HttpContext httpContext, Encoding encoding)
123122
{
124123
if (encoding.CodePage == Encoding.UTF8.CodePage)
125124
{
126-
return httpContext.Request.Body;
125+
return (httpContext.Request.Body, false);
127126
}
128127

129-
return new TranscodingReadStream(httpContext.Request.Body, encoding);
128+
var inputStream = Encoding.CreateTranscodingStream(httpContext.Request.Body, encoding, Encoding.UTF8, leaveOpen: true);
129+
return (inputStream, true);
130130
}
131131

132132
private static class Log

src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.IO;
5+
using System.Runtime.ExceptionServices;
66
using System.Text;
77
using System.Text.Encodings.Web;
88
using System.Text.Json;
9-
using System.Threading;
109
using System.Threading.Tasks;
11-
using Microsoft.AspNetCore.Http;
12-
using Microsoft.AspNetCore.Mvc.Formatters.Json;
1310

1411
namespace Microsoft.AspNetCore.Mvc.Formatters
1512
{
@@ -73,44 +70,50 @@ public sealed override async Task WriteResponseBodyAsync(OutputFormatterWriteCon
7370

7471
var httpContext = context.HttpContext;
7572

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
7886
{
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);
8590

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
9093
{
91-
await transcodingStream.FinalWriteAsync(CancellationToken.None);
94+
await JsonSerializer.SerializeAsync(transcodingStream, context.Object, objectType, SerializerOptions);
95+
await transcodingStream.FlushAsync();
9296
}
93-
await writeStream.FlushAsync();
94-
}
95-
finally
96-
{
97-
if (writeStream is TranscodingWriteStream transcodingStream)
97+
catch (Exception ex)
9898
{
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);
100103
}
101-
}
102-
}
104+
finally
105+
{
106+
try
107+
{
108+
await transcodingStream.DisposeAsync();
109+
}
110+
catch when (exceptionDispatchInfo != null)
111+
{
112+
}
103113

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+
}
111116
}
112-
113-
return new TranscodingWriteStream(httpContext.Response.Body, selectedEncoding);
114117
}
115118
}
116119
}

src/Mvc/Mvc.Core/src/Formatters/TranscodingReadStream.cs

Lines changed: 0 additions & 223 deletions
This file was deleted.

0 commit comments

Comments
 (0)