Skip to content

Commit 833b5db

Browse files
committed
Prevent synchronous writes when using Razor
* Do not perform synchronous writes to the Response TextWriter after a Razor FlushAsync * Use ViewBuffer to perform async writes to the response when using ViewComponentResult
1 parent eaa7895 commit 833b5db

File tree

12 files changed

+221
-267
lines changed

12 files changed

+221
-267
lines changed

src/Mvc/Mvc.Razor/src/RazorPageBase.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,7 @@ public virtual void Write(object value)
370370
var encoder = HtmlEncoder;
371371
if (value is IHtmlContent htmlContent)
372372
{
373-
var bufferedWriter = writer as ViewBufferTextWriter;
374-
if (bufferedWriter == null || !bufferedWriter.IsBuffering)
375-
{
376-
htmlContent.WriteTo(writer, encoder);
377-
}
378-
else
373+
if (writer is ViewBufferTextWriter bufferedWriter)
379374
{
380375
if (value is IHtmlContentContainer htmlContentContainer)
381376
{
@@ -389,6 +384,10 @@ public virtual void Write(object value)
389384
bufferedWriter.Buffer.AppendHtml(htmlContent);
390385
}
391386
}
387+
else
388+
{
389+
htmlContent.WriteTo(writer, encoder);
390+
}
392391

393392
return;
394393
}

src/Mvc/Mvc.Razor/src/RazorView.cs

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ private async Task RenderLayoutAsync(
233233
// (including the layout page we just rendered).
234234
while (!string.IsNullOrEmpty(previousPage.Layout))
235235
{
236-
if (!bodyWriter.IsBuffering)
236+
if (bodyWriter.Flushed)
237237
{
238238
// Once a call to RazorPage.FlushAsync is made, we can no longer render Layout pages - content has
239239
// already been written to the client and the layout content would be appended rather than surround
@@ -274,25 +274,22 @@ private async Task RenderLayoutAsync(
274274
layoutPage.EnsureRenderedBodyOrSections();
275275
}
276276

277-
if (bodyWriter.IsBuffering)
277+
// We've got a bunch of content in the view buffer. How to best deal with it
278+
// really depends on whether or not we're writing directly to the output or if we're writing to
279+
// another buffer.
280+
if (context.Writer is ViewBufferTextWriter viewBufferTextWriter)
278281
{
279-
// If IsBuffering - then we've got a bunch of content in the view buffer. How to best deal with it
280-
// really depends on whether or not we're writing directly to the output or if we're writing to
281-
// another buffer.
282-
var viewBufferTextWriter = context.Writer as ViewBufferTextWriter;
283-
if (viewBufferTextWriter == null || !viewBufferTextWriter.IsBuffering)
284-
{
285-
// This means we're writing to a 'real' writer, probably to the actual output stream.
286-
// We're using PagedBufferedTextWriter here to 'smooth' synchronous writes of IHtmlContent values.
287-
using (var writer = _bufferScope.CreateWriter(context.Writer))
288-
{
289-
await bodyWriter.Buffer.WriteToAsync(writer, _htmlEncoder);
290-
}
291-
}
292-
else
282+
// This means we're writing to another buffer. Use MoveTo to combine them.
283+
bodyWriter.Buffer.MoveTo(viewBufferTextWriter.Buffer);
284+
}
285+
else
286+
{
287+
// This means we're writing to a 'real' writer, probably to the actual output stream.
288+
// We're using PagedBufferedTextWriter here to 'smooth' synchronous writes of IHtmlContent values.
289+
using (var writer = _bufferScope.CreateWriter(context.Writer))
293290
{
294-
// This means we're writing to another buffer. Use MoveTo to combine them.
295-
bodyWriter.Buffer.MoveTo(viewBufferTextWriter.Buffer);
291+
await bodyWriter.Buffer.WriteToAsync(writer, _htmlEncoder);
292+
await writer.FlushAsync();
296293
}
297294
}
298295
}

src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBufferTextWriter.cs

Lines changed: 39 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,20 @@ public ViewBufferTextWriter(ViewBuffer buffer, Encoding encoding, HtmlEncoder ht
8686
/// <inheritdoc />
8787
public override Encoding Encoding { get; }
8888

89-
/// <inheritdoc />
90-
public bool IsBuffering { get; private set; } = true;
91-
9289
/// <summary>
9390
/// Gets the <see cref="ViewBuffer"/>.
9491
/// </summary>
9592
public ViewBuffer Buffer { get; }
9693

94+
/// <summary>
95+
/// Gets a value that indiciates if <see cref="Flush"/> or <see cref="FlushAsync" /> was invoked.
96+
/// </summary>
97+
public bool Flushed { get; private set; }
98+
9799
/// <inheritdoc />
98100
public override void Write(char value)
99101
{
100-
if (IsBuffering)
101-
{
102-
Buffer.AppendHtml(value.ToString());
103-
}
104-
else
105-
{
106-
_inner.Write(value);
107-
}
102+
Buffer.AppendHtml(value.ToString());
108103
}
109104

110105
/// <inheritdoc />
@@ -125,14 +120,7 @@ public override void Write(char[] buffer, int index, int count)
125120
throw new ArgumentOutOfRangeException(nameof(count));
126121
}
127122

128-
if (IsBuffering)
129-
{
130-
Buffer.AppendHtml(new string(buffer, index, count));
131-
}
132-
else
133-
{
134-
_inner.Write(buffer, index, count);
135-
}
123+
Buffer.AppendHtml(new string(buffer, index, count));
136124
}
137125

138126
/// <inheritdoc />
@@ -143,14 +131,7 @@ public override void Write(string value)
143131
return;
144132
}
145133

146-
if (IsBuffering)
147-
{
148-
Buffer.AppendHtml(value);
149-
}
150-
else
151-
{
152-
_inner.Write(value);
153-
}
134+
Buffer.AppendHtml(value);
154135
}
155136

156137
/// <inheritdoc />
@@ -186,14 +167,7 @@ public void Write(IHtmlContent value)
186167
return;
187168
}
188169

189-
if (IsBuffering)
190-
{
191-
Buffer.AppendHtml(value);
192-
}
193-
else
194-
{
195-
value.WriteTo(_inner, _htmlEncoder);
196-
}
170+
Buffer.AppendHtml(value);
197171
}
198172

199173
/// <summary>
@@ -207,14 +181,7 @@ public void Write(IHtmlContentContainer value)
207181
return;
208182
}
209183

210-
if (IsBuffering)
211-
{
212-
value.MoveTo(Buffer);
213-
}
214-
else
215-
{
216-
value.WriteTo(_inner, _htmlEncoder);
217-
}
184+
value.MoveTo(Buffer);
218185
}
219186

220187
/// <inheritdoc />
@@ -245,15 +212,8 @@ public override void WriteLine(object value)
245212
/// <inheritdoc />
246213
public override Task WriteAsync(char value)
247214
{
248-
if (IsBuffering)
249-
{
250-
Buffer.AppendHtml(value.ToString());
251-
return Task.CompletedTask;
252-
}
253-
else
254-
{
255-
return _inner.WriteAsync(value);
256-
}
215+
Buffer.AppendHtml(value.ToString());
216+
return Task.CompletedTask;
257217
}
258218

259219
/// <inheritdoc />
@@ -273,121 +233,64 @@ public override Task WriteAsync(char[] buffer, int index, int count)
273233
throw new ArgumentOutOfRangeException(nameof(count));
274234
}
275235

276-
if (IsBuffering)
277-
{
278-
Buffer.AppendHtml(new string(buffer, index, count));
279-
return Task.CompletedTask;
280-
}
281-
else
282-
{
283-
return _inner.WriteAsync(buffer, index, count);
284-
}
236+
Buffer.AppendHtml(new string(buffer, index, count));
237+
return Task.CompletedTask;
285238
}
286239

287240
/// <inheritdoc />
288241
public override Task WriteAsync(string value)
289242
{
290-
if (IsBuffering)
291-
{
292-
Buffer.AppendHtml(value);
293-
return Task.CompletedTask;
294-
}
295-
else
296-
{
297-
return _inner.WriteAsync(value);
298-
}
243+
Buffer.AppendHtml(value);
244+
return Task.CompletedTask;
299245
}
300246

301247
/// <inheritdoc />
302248
public override void WriteLine()
303249
{
304-
if (IsBuffering)
305-
{
306-
Buffer.AppendHtml(NewLine);
307-
}
308-
else
309-
{
310-
_inner.WriteLine();
311-
}
250+
Buffer.AppendHtml(NewLine);
312251
}
313252

314253
/// <inheritdoc />
315254
public override void WriteLine(string value)
316255
{
317-
if (IsBuffering)
318-
{
319-
Buffer.AppendHtml(value);
320-
Buffer.AppendHtml(NewLine);
321-
}
322-
else
323-
{
324-
_inner.WriteLine(value);
325-
}
256+
Buffer.AppendHtml(value);
257+
Buffer.AppendHtml(NewLine);
326258
}
327259

328260
/// <inheritdoc />
329261
public override Task WriteLineAsync(char value)
330262
{
331-
if (IsBuffering)
332-
{
333-
Buffer.AppendHtml(value.ToString());
334-
Buffer.AppendHtml(NewLine);
335-
return Task.CompletedTask;
336-
}
337-
else
338-
{
339-
return _inner.WriteLineAsync(value);
340-
}
263+
Buffer.AppendHtml(value.ToString());
264+
Buffer.AppendHtml(NewLine);
265+
return Task.CompletedTask;
341266
}
342267

343268
/// <inheritdoc />
344269
public override Task WriteLineAsync(char[] value, int start, int offset)
345270
{
346-
if (IsBuffering)
347-
{
348-
Buffer.AppendHtml(new string(value, start, offset));
349-
Buffer.AppendHtml(NewLine);
350-
return Task.CompletedTask;
351-
}
352-
else
353-
{
354-
return _inner.WriteLineAsync(value, start, offset);
355-
}
271+
Buffer.AppendHtml(new string(value, start, offset));
272+
Buffer.AppendHtml(NewLine);
273+
return Task.CompletedTask;
274+
356275
}
357276

358277
/// <inheritdoc />
359278
public override Task WriteLineAsync(string value)
360279
{
361-
if (IsBuffering)
362-
{
363-
Buffer.AppendHtml(value);
364-
Buffer.AppendHtml(NewLine);
365-
return Task.CompletedTask;
366-
}
367-
else
368-
{
369-
return _inner.WriteLineAsync(value);
370-
}
280+
Buffer.AppendHtml(value);
281+
Buffer.AppendHtml(NewLine);
282+
return Task.CompletedTask;
371283
}
372284

373285
/// <inheritdoc />
374286
public override Task WriteLineAsync()
375287
{
376-
if (IsBuffering)
377-
{
378-
Buffer.AppendHtml(NewLine);
379-
return Task.CompletedTask;
380-
}
381-
else
382-
{
383-
return _inner.WriteLineAsync();
384-
}
288+
Buffer.AppendHtml(NewLine);
289+
return Task.CompletedTask;
385290
}
386291

387292
/// <summary>
388293
/// Copies the buffered content to the unbuffered writer and invokes flush on it.
389-
/// Additionally causes this instance to no longer buffer and direct all write operations
390-
/// to the unbuffered writer.
391294
/// </summary>
392295
public override void Flush()
393296
{
@@ -396,20 +299,16 @@ public override void Flush()
396299
return;
397300
}
398301

399-
if (IsBuffering)
400-
{
401-
IsBuffering = false;
402-
Buffer.WriteTo(_inner, _htmlEncoder);
403-
Buffer.Clear();
404-
}
302+
Flushed = true;
303+
304+
Buffer.WriteTo(_inner, _htmlEncoder);
305+
Buffer.Clear();
405306

406307
_inner.Flush();
407308
}
408309

409310
/// <summary>
410311
/// Copies the buffered content to the unbuffered writer and invokes flush on it.
411-
/// Additionally causes this instance to no longer buffer and direct all write operations
412-
/// to the unbuffered writer.
413312
/// </summary>
414313
/// <returns>A <see cref="Task"/> that represents the asynchronous copy and flush operations.</returns>
415314
public override async Task FlushAsync()
@@ -419,12 +318,10 @@ public override async Task FlushAsync()
419318
return;
420319
}
421320

422-
if (IsBuffering)
423-
{
424-
IsBuffering = false;
425-
await Buffer.WriteToAsync(_inner, _htmlEncoder);
426-
Buffer.Clear();
427-
}
321+
Flushed = true;
322+
323+
await Buffer.WriteToAsync(_inner, _htmlEncoder);
324+
Buffer.Clear();
428325

429326
await _inner.FlushAsync();
430327
}

0 commit comments

Comments
 (0)