@@ -23,6 +23,10 @@ public static class HttpRequestJsonExtensions
2323 "Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved." ;
2424 private const string RequiresDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed and need runtime code generation. " +
2525 "Use the overload that takes a JsonTypeInfo or JsonSerializerContext for native AOT applications." ;
26+ // Fallback to the stream-based overloads for JsonSerializer.DeserializeAsync
27+ // This is to give users with custom JsonConverter implementations the chance to update their
28+ // converters to support ReadOnlySequence<T> if needed while still keeping their apps working.
29+ private static readonly bool _useStreamJsonOverload = AppContext . TryGetSwitch ( "Microsoft.AspNetCore.UseStreamBasedJsonParsing" , out var isEnabled ) && isEnabled ;
2630
2731 /// <summary>
2832 /// Read JSON from the request and deserialize to the specified type.
@@ -68,15 +72,33 @@ public static class HttpRequestJsonExtensions
6872 options ??= ResolveSerializerOptions ( request . HttpContext ) ;
6973
7074 var encoding = GetEncodingFromCharset ( charset ) ;
71- var ( inputStream , usesTranscodingStream ) = GetInputStream ( request . HttpContext , encoding ) ;
75+ Stream ? inputStream = null ;
76+ ValueTask < TValue ? > deserializeTask ;
7277
7378 try
7479 {
75- return await JsonSerializer . DeserializeAsync < TValue > ( inputStream , options , cancellationToken ) ;
80+ if ( encoding == null || encoding . CodePage == Encoding . UTF8 . CodePage )
81+ {
82+ if ( _useStreamJsonOverload )
83+ {
84+ deserializeTask = JsonSerializer . DeserializeAsync < TValue > ( request . Body , options , cancellationToken ) ;
85+ }
86+ else
87+ {
88+ deserializeTask = JsonSerializer . DeserializeAsync < TValue > ( request . BodyReader , options , cancellationToken ) ;
89+ }
90+ }
91+ else
92+ {
93+ inputStream = Encoding . CreateTranscodingStream ( request . Body , encoding , Encoding . UTF8 , leaveOpen : true ) ;
94+ deserializeTask = JsonSerializer . DeserializeAsync < TValue > ( inputStream , options , cancellationToken ) ;
95+ }
96+
97+ return await deserializeTask ;
7698 }
7799 finally
78100 {
79- if ( usesTranscodingStream )
101+ if ( inputStream is not null )
80102 {
81103 await inputStream . DisposeAsync ( ) ;
82104 }
@@ -106,15 +128,33 @@ public static class HttpRequestJsonExtensions
106128 }
107129
108130 var encoding = GetEncodingFromCharset ( charset ) ;
109- var ( inputStream , usesTranscodingStream ) = GetInputStream ( request . HttpContext , encoding ) ;
131+ Stream ? inputStream = null ;
132+ ValueTask < TValue ? > deserializeTask ;
110133
111134 try
112135 {
113- return await JsonSerializer . DeserializeAsync ( inputStream , jsonTypeInfo , cancellationToken ) ;
136+ if ( encoding == null || encoding . CodePage == Encoding . UTF8 . CodePage )
137+ {
138+ if ( _useStreamJsonOverload )
139+ {
140+ deserializeTask = JsonSerializer . DeserializeAsync ( request . Body , jsonTypeInfo , cancellationToken ) ;
141+ }
142+ else
143+ {
144+ deserializeTask = JsonSerializer . DeserializeAsync ( request . BodyReader , jsonTypeInfo , cancellationToken ) ;
145+ }
146+ }
147+ else
148+ {
149+ inputStream = Encoding . CreateTranscodingStream ( request . Body , encoding , Encoding . UTF8 , leaveOpen : true ) ;
150+ deserializeTask = JsonSerializer . DeserializeAsync ( inputStream , jsonTypeInfo , cancellationToken ) ;
151+ }
152+
153+ return await deserializeTask ;
114154 }
115155 finally
116156 {
117- if ( usesTranscodingStream )
157+ if ( inputStream is not null )
118158 {
119159 await inputStream . DisposeAsync ( ) ;
120160 }
@@ -144,15 +184,33 @@ public static class HttpRequestJsonExtensions
144184 }
145185
146186 var encoding = GetEncodingFromCharset ( charset ) ;
147- var ( inputStream , usesTranscodingStream ) = GetInputStream ( request . HttpContext , encoding ) ;
187+ Stream ? inputStream = null ;
188+ ValueTask < object ? > deserializeTask ;
148189
149190 try
150191 {
151- return await JsonSerializer . DeserializeAsync ( inputStream , jsonTypeInfo , cancellationToken ) ;
192+ if ( encoding == null || encoding . CodePage == Encoding . UTF8 . CodePage )
193+ {
194+ if ( _useStreamJsonOverload )
195+ {
196+ deserializeTask = JsonSerializer . DeserializeAsync ( request . Body , jsonTypeInfo , cancellationToken ) ;
197+ }
198+ else
199+ {
200+ deserializeTask = JsonSerializer . DeserializeAsync ( request . BodyReader , jsonTypeInfo , cancellationToken ) ;
201+ }
202+ }
203+ else
204+ {
205+ inputStream = Encoding . CreateTranscodingStream ( request . Body , encoding , Encoding . UTF8 , leaveOpen : true ) ;
206+ deserializeTask = JsonSerializer . DeserializeAsync ( inputStream , jsonTypeInfo , cancellationToken ) ;
207+ }
208+
209+ return await deserializeTask ;
152210 }
153211 finally
154212 {
155- if ( usesTranscodingStream )
213+ if ( inputStream is not null )
156214 {
157215 await inputStream . DisposeAsync ( ) ;
158216 }
@@ -206,15 +264,33 @@ public static class HttpRequestJsonExtensions
206264 options ??= ResolveSerializerOptions ( request . HttpContext ) ;
207265
208266 var encoding = GetEncodingFromCharset ( charset ) ;
209- var ( inputStream , usesTranscodingStream ) = GetInputStream ( request . HttpContext , encoding ) ;
267+ Stream ? inputStream = null ;
268+ ValueTask < object ? > deserializeTask ;
210269
211270 try
212271 {
213- return await JsonSerializer . DeserializeAsync ( inputStream , type , options , cancellationToken ) ;
272+ if ( encoding == null || encoding . CodePage == Encoding . UTF8 . CodePage )
273+ {
274+ if ( _useStreamJsonOverload )
275+ {
276+ deserializeTask = JsonSerializer . DeserializeAsync ( request . Body , type , options , cancellationToken ) ;
277+ }
278+ else
279+ {
280+ deserializeTask = JsonSerializer . DeserializeAsync ( request . BodyReader , type , options , cancellationToken ) ;
281+ }
282+ }
283+ else
284+ {
285+ inputStream = Encoding . CreateTranscodingStream ( request . Body , encoding , Encoding . UTF8 , leaveOpen : true ) ;
286+ deserializeTask = JsonSerializer . DeserializeAsync ( inputStream , type , options , cancellationToken ) ;
287+ }
288+
289+ return await deserializeTask ;
214290 }
215291 finally
216292 {
217- if ( usesTranscodingStream )
293+ if ( inputStream is not null )
218294 {
219295 await inputStream . DisposeAsync ( ) ;
220296 }
@@ -248,15 +324,33 @@ public static class HttpRequestJsonExtensions
248324 }
249325
250326 var encoding = GetEncodingFromCharset ( charset ) ;
251- var ( inputStream , usesTranscodingStream ) = GetInputStream ( request . HttpContext , encoding ) ;
327+ Stream ? inputStream = null ;
328+ ValueTask < object ? > deserializeTask ;
252329
253330 try
254331 {
255- return await JsonSerializer . DeserializeAsync ( inputStream , type , context , cancellationToken ) ;
332+ if ( encoding == null || encoding . CodePage == Encoding . UTF8 . CodePage )
333+ {
334+ if ( _useStreamJsonOverload )
335+ {
336+ deserializeTask = JsonSerializer . DeserializeAsync ( request . Body , type , context , cancellationToken ) ;
337+ }
338+ else
339+ {
340+ deserializeTask = JsonSerializer . DeserializeAsync ( request . BodyReader , type , context , cancellationToken ) ;
341+ }
342+ }
343+ else
344+ {
345+ inputStream = Encoding . CreateTranscodingStream ( request . Body , encoding , Encoding . UTF8 , leaveOpen : true ) ;
346+ deserializeTask = JsonSerializer . DeserializeAsync ( inputStream , type , context , cancellationToken ) ;
347+ }
348+
349+ return await deserializeTask ;
256350 }
257351 finally
258352 {
259- if ( usesTranscodingStream )
353+ if ( inputStream is not null )
260354 {
261355 await inputStream . DisposeAsync ( ) ;
262356 }
@@ -312,17 +406,6 @@ private static void ThrowContentTypeError(HttpRequest request)
312406 throw new InvalidOperationException ( $ "Unable to read the request as JSON because the request content type '{ request . ContentType } ' is not a known JSON content type.") ;
313407 }
314408
315- private static ( Stream inputStream , bool usesTranscodingStream ) GetInputStream ( HttpContext httpContext , Encoding ? encoding )
316- {
317- if ( encoding == null || encoding . CodePage == Encoding . UTF8 . CodePage )
318- {
319- return ( httpContext . Request . Body , false ) ;
320- }
321-
322- var inputStream = Encoding . CreateTranscodingStream ( httpContext . Request . Body , encoding , Encoding . UTF8 , leaveOpen : true ) ;
323- return ( inputStream , true ) ;
324- }
325-
326409 private static Encoding ? GetEncodingFromCharset ( StringSegment charset )
327410 {
328411 if ( charset . Equals ( "utf-8" , StringComparison . OrdinalIgnoreCase ) )
0 commit comments