22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Diagnostics . CodeAnalysis ;
5+ using System . IO . Pipelines ;
56using System . Text . Json ;
67using System . Text . Json . Serialization ;
78using System . Text . Json . Serialization . Metadata ;
@@ -89,13 +90,23 @@ public static Task WriteAsJsonAsync<TValue>(
8990
9091 response . ContentType = contentType ?? JsonConstants . JsonContentTypeWithCharset ;
9192
93+ var startTask = Task . CompletedTask ;
94+ if ( ! response . HasStarted )
95+ {
96+ // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
97+ startTask = response . StartAsync ( cancellationToken ) ;
98+ }
99+
92100 // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
93- if ( ! cancellationToken . CanBeCanceled )
101+ if ( ! startTask . IsCompleted || ! cancellationToken . CanBeCanceled )
94102 {
95- return WriteAsJsonAsyncSlow ( response . Body , value , options , response . HttpContext . RequestAborted ) ;
103+ return WriteAsJsonAsyncSlow ( startTask , response . BodyWriter , value , options ,
104+ ignoreOCE : ! cancellationToken . CanBeCanceled ,
105+ cancellationToken . CanBeCanceled ? cancellationToken : response . HttpContext . RequestAborted ) ;
96106 }
97107
98- return JsonSerializer . SerializeAsync ( response . Body , value , options , cancellationToken ) ;
108+ startTask . GetAwaiter ( ) . GetResult ( ) ;
109+ return JsonSerializer . SerializeAsync ( response . BodyWriter , value , options , cancellationToken ) ;
99110 }
100111
101112 /// <summary>
@@ -120,21 +131,33 @@ public static Task WriteAsJsonAsync<TValue>(
120131
121132 response . ContentType = contentType ?? JsonConstants . JsonContentTypeWithCharset ;
122133
134+ var startTask = Task . CompletedTask ;
135+ if ( ! response . HasStarted )
136+ {
137+ // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
138+ startTask = response . StartAsync ( cancellationToken ) ;
139+ }
140+
123141 // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
124- if ( ! cancellationToken . CanBeCanceled )
142+ if ( ! startTask . IsCompleted || ! cancellationToken . CanBeCanceled )
125143 {
126- return WriteAsJsonAsyncSlow ( response , value , jsonTypeInfo ) ;
144+ return WriteAsJsonAsyncSlow ( startTask , response , value , jsonTypeInfo ,
145+ ignoreOCE : ! cancellationToken . CanBeCanceled ,
146+ cancellationToken . CanBeCanceled ? cancellationToken : response . HttpContext . RequestAborted ) ;
127147 }
128148
129- return JsonSerializer . SerializeAsync ( response . Body , value , jsonTypeInfo , cancellationToken ) ;
149+ startTask . GetAwaiter ( ) . GetResult ( ) ;
150+ return JsonSerializer . SerializeAsync ( response . BodyWriter , value , jsonTypeInfo , cancellationToken ) ;
130151
131- static async Task WriteAsJsonAsyncSlow ( HttpResponse response , TValue value , JsonTypeInfo < TValue > jsonTypeInfo )
152+ static async Task WriteAsJsonAsyncSlow ( Task startTask , HttpResponse response , TValue value , JsonTypeInfo < TValue > jsonTypeInfo ,
153+ bool ignoreOCE , CancellationToken cancellationToken )
132154 {
133155 try
134156 {
135- await JsonSerializer . SerializeAsync ( response . Body , value , jsonTypeInfo , response . HttpContext . RequestAborted ) ;
157+ await startTask ;
158+ await JsonSerializer . SerializeAsync ( response . BodyWriter , value , jsonTypeInfo , cancellationToken ) ;
136159 }
137- catch ( OperationCanceledException ) { }
160+ catch ( OperationCanceledException ) when ( ignoreOCE ) { }
138161 }
139162 }
140163
@@ -161,37 +184,52 @@ public static Task WriteAsJsonAsync(
161184
162185 response . ContentType = contentType ?? JsonConstants . JsonContentTypeWithCharset ;
163186
187+ var startTask = Task . CompletedTask ;
188+ if ( ! response . HasStarted )
189+ {
190+ // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
191+ startTask = response . StartAsync ( cancellationToken ) ;
192+ }
193+
164194 // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
165- if ( ! cancellationToken . CanBeCanceled )
195+ if ( ! startTask . IsCompleted || ! cancellationToken . CanBeCanceled )
166196 {
167- return WriteAsJsonAsyncSlow ( response , value , jsonTypeInfo ) ;
197+ return WriteAsJsonAsyncSlow ( startTask , response , value , jsonTypeInfo ,
198+ ignoreOCE : ! cancellationToken . CanBeCanceled ,
199+ cancellationToken . CanBeCanceled ? cancellationToken : response . HttpContext . RequestAborted ) ;
168200 }
169201
170- return JsonSerializer . SerializeAsync ( response . Body , value , jsonTypeInfo , cancellationToken ) ;
202+ startTask . GetAwaiter ( ) . GetResult ( ) ;
203+ return JsonSerializer . SerializeAsync ( response . BodyWriter , value , jsonTypeInfo , cancellationToken ) ;
171204
172- static async Task WriteAsJsonAsyncSlow ( HttpResponse response , object ? value , JsonTypeInfo jsonTypeInfo )
205+ static async Task WriteAsJsonAsyncSlow ( Task startTask , HttpResponse response , object ? value , JsonTypeInfo jsonTypeInfo ,
206+ bool ignoreOCE , CancellationToken cancellationToken )
173207 {
174208 try
175209 {
176- await JsonSerializer . SerializeAsync ( response . Body , value , jsonTypeInfo , response . HttpContext . RequestAborted ) ;
210+ await startTask ;
211+ await JsonSerializer . SerializeAsync ( response . BodyWriter , value , jsonTypeInfo , cancellationToken ) ;
177212 }
178- catch ( OperationCanceledException ) { }
213+ catch ( OperationCanceledException ) when ( ignoreOCE ) { }
179214 }
180215 }
181216
182217 [ RequiresUnreferencedCode ( RequiresUnreferencedCodeMessage ) ]
183218 [ RequiresDynamicCode ( RequiresDynamicCodeMessage ) ]
184219 private static async Task WriteAsJsonAsyncSlow < TValue > (
185- Stream body ,
220+ Task startTask ,
221+ PipeWriter body ,
186222 TValue value ,
187223 JsonSerializerOptions ? options ,
224+ bool ignoreOCE ,
188225 CancellationToken cancellationToken )
189226 {
190227 try
191228 {
229+ await startTask ;
192230 await JsonSerializer . SerializeAsync ( body , value , options , cancellationToken ) ;
193231 }
194- catch ( OperationCanceledException ) { }
232+ catch ( OperationCanceledException ) when ( ignoreOCE ) { }
195233 }
196234
197235 /// <summary>
@@ -266,29 +304,42 @@ public static Task WriteAsJsonAsync(
266304
267305 response . ContentType = contentType ?? JsonConstants . JsonContentTypeWithCharset ;
268306
307+ var startTask = Task . CompletedTask ;
308+ if ( ! response . HasStarted )
309+ {
310+ // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
311+ startTask = response . StartAsync ( cancellationToken ) ;
312+ }
313+
269314 // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
270- if ( ! cancellationToken . CanBeCanceled )
315+ if ( ! startTask . IsCompleted || ! cancellationToken . CanBeCanceled )
271316 {
272- return WriteAsJsonAsyncSlow ( response . Body , value , type , options , response . HttpContext . RequestAborted ) ;
317+ return WriteAsJsonAsyncSlow ( startTask , response . BodyWriter , value , type , options ,
318+ ignoreOCE : ! cancellationToken . CanBeCanceled ,
319+ cancellationToken . CanBeCanceled ? cancellationToken : response . HttpContext . RequestAborted ) ;
273320 }
274321
275- return JsonSerializer . SerializeAsync ( response . Body , value , type , options , cancellationToken ) ;
322+ startTask . GetAwaiter ( ) . GetResult ( ) ;
323+ return JsonSerializer . SerializeAsync ( response . BodyWriter , value , type , options , cancellationToken ) ;
276324 }
277325
278326 [ RequiresUnreferencedCode ( RequiresUnreferencedCodeMessage ) ]
279327 [ RequiresDynamicCode ( RequiresDynamicCodeMessage ) ]
280328 private static async Task WriteAsJsonAsyncSlow (
281- Stream body ,
329+ Task startTask ,
330+ PipeWriter body ,
282331 object ? value ,
283332 Type type ,
284333 JsonSerializerOptions ? options ,
334+ bool ignoreOCE ,
285335 CancellationToken cancellationToken )
286336 {
287337 try
288338 {
339+ await startTask ;
289340 await JsonSerializer . SerializeAsync ( body , value , type , options , cancellationToken ) ;
290341 }
291- catch ( OperationCanceledException ) { }
342+ catch ( OperationCanceledException ) when ( ignoreOCE ) { }
292343 }
293344
294345 /// <summary>
@@ -316,21 +367,33 @@ public static Task WriteAsJsonAsync(
316367
317368 response . ContentType = contentType ?? JsonConstants . JsonContentTypeWithCharset ;
318369
370+ var startTask = Task . CompletedTask ;
371+ if ( ! response . HasStarted )
372+ {
373+ // Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush.
374+ startTask = response . StartAsync ( cancellationToken ) ;
375+ }
376+
319377 // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
320- if ( ! cancellationToken . CanBeCanceled )
378+ if ( ! startTask . IsCompleted || ! cancellationToken . CanBeCanceled )
321379 {
322- return WriteAsJsonAsyncSlow ( ) ;
380+ return WriteAsJsonAsyncSlow ( startTask , response . BodyWriter , value , type , context ,
381+ ignoreOCE : ! cancellationToken . CanBeCanceled ,
382+ cancellationToken . CanBeCanceled ? cancellationToken : response . HttpContext . RequestAborted ) ;
323383 }
324384
325- return JsonSerializer . SerializeAsync ( response . Body , value , type , context , cancellationToken ) ;
385+ startTask . GetAwaiter ( ) . GetResult ( ) ;
386+ return JsonSerializer . SerializeAsync ( response . BodyWriter , value , type , context , cancellationToken ) ;
326387
327- async Task WriteAsJsonAsyncSlow ( )
388+ static async Task WriteAsJsonAsyncSlow ( Task startTask , PipeWriter body , object ? value , Type type , JsonSerializerContext context ,
389+ bool ignoreOCE , CancellationToken cancellationToken )
328390 {
329391 try
330392 {
331- await JsonSerializer . SerializeAsync ( response . Body , value , type , context , cancellationToken ) ;
393+ await startTask ;
394+ await JsonSerializer . SerializeAsync ( body , value , type , context , cancellationToken ) ;
332395 }
333- catch ( OperationCanceledException ) { }
396+ catch ( OperationCanceledException ) when ( ignoreOCE ) { }
334397 }
335398 }
336399
0 commit comments