diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs
index 8d0d049d53..1b0a030efd 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs
@@ -64,98 +64,10 @@ namespace Microsoft.AspNet.Mvc
response.ContentType = contentTypeHeader.ToString();
- var wrappedStream = new StreamWrapper(response.Body);
-
- using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true))
+ using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
{
- try
- {
- var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
- await view.RenderAsync(viewContext);
- }
- catch
- {
- // Need to prevent writes/flushes on dispose because the StreamWriter will flush even if
- // nothing got written. This leads to a response going out on the wire prematurely in case an
- // exception is being thrown inside the try catch block.
- wrappedStream.BlockWrites = true;
- throw;
- }
- }
- }
-
- private class StreamWrapper : Stream
- {
- private readonly Stream _wrappedStream;
-
- public StreamWrapper(Stream stream)
- {
- _wrappedStream = stream;
- }
-
- public bool BlockWrites { get; set; }
-
- public override bool CanRead
- {
- get { return _wrappedStream.CanRead; }
- }
-
- public override bool CanSeek
- {
- get { return _wrappedStream.CanSeek; }
- }
-
- public override bool CanWrite
- {
- get { return _wrappedStream.CanWrite; }
- }
-
- public override void Flush()
- {
- if (!BlockWrites)
- {
- _wrappedStream.Flush();
- }
- }
-
- public override long Length
- {
- get { return _wrappedStream.Length; }
- }
-
- public override long Position
- {
- get
- {
- return _wrappedStream.Position;
- }
- set
- {
- _wrappedStream.Position = value;
- }
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- return _wrappedStream.Read(buffer, offset, count);
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- return Seek(offset, origin);
- }
-
- public override void SetLength(long value)
- {
- SetLength(value);
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- if (!BlockWrites)
- {
- _wrappedStream.Write(buffer, offset, count);
- }
+ var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
+ await view.RenderAsync(viewContext);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs
index 783dfa997f..1b4ab69cf9 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs
@@ -70,8 +70,7 @@ namespace Microsoft.AspNet.Mvc
var response = context.ActionContext.HttpContext.Response;
var selectedEncoding = context.SelectedEncoding;
- using (var nonDisposableStream = new NonDisposableStream(response.Body))
- using (var writer = new StreamWriter(nonDisposableStream, selectedEncoding, 1024, leaveOpen: true))
+ using (var writer = new HttpResponseStreamWriter(response.Body, selectedEncoding))
{
WriteObject(writer, context.Object);
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs b/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs
new file mode 100644
index 0000000000..e4d848c21e
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs
@@ -0,0 +1,235 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Framework.Internal;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// Writes to the using the supplied .
+ /// It does not write the BOM and also does not close the stream.
+ ///
+ public class HttpResponseStreamWriter : TextWriter
+ {
+ private const int DefaultBufferSize = 1024;
+ private readonly Stream _stream;
+ private Encoder _encoder;
+ private byte[] _byteBuffer;
+ private char[] _charBuffer;
+ private int _charBufferSize;
+ private int _charBufferCount;
+
+ public HttpResponseStreamWriter(Stream stream, Encoding encoding)
+ : this(stream, encoding, DefaultBufferSize)
+ {
+ }
+
+ public HttpResponseStreamWriter([NotNull] Stream stream, [NotNull] Encoding encoding, int bufferSize)
+ {
+ _stream = stream;
+ Encoding = encoding;
+ _encoder = encoding.GetEncoder();
+ _charBufferSize = bufferSize;
+ _charBuffer = new char[bufferSize];
+ _byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)];
+ }
+
+ public override Encoding Encoding { get; }
+
+ public override void Write(char value)
+ {
+ if (_charBufferCount == _charBufferSize)
+ {
+ FlushInternal();
+ }
+
+ _charBuffer[_charBufferCount] = value;
+ _charBufferCount++;
+ }
+
+ public override void Write(char[] values, int index, int count)
+ {
+ if (values == null)
+ {
+ return;
+ }
+
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
+ {
+ FlushInternal();
+ }
+
+ CopyToCharBuffer(values, ref index, ref count);
+ }
+ }
+
+ public override void Write(string value)
+ {
+ if (value == null)
+ {
+ return;
+ }
+
+ var count = value.Length;
+ var index = 0;
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
+ {
+ FlushInternal();
+ }
+
+ CopyToCharBuffer(value, ref index, ref count);
+ }
+ }
+
+ public override async Task WriteAsync(char value)
+ {
+ if (_charBufferCount == _charBufferSize)
+ {
+ await FlushInternalAsync();
+ }
+
+ _charBuffer[_charBufferCount] = value;
+ _charBufferCount++;
+ }
+
+ public override async Task WriteAsync(char[] values, int index, int count)
+ {
+ if (values == null)
+ {
+ return;
+ }
+
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
+ {
+ await FlushInternalAsync();
+ }
+
+ CopyToCharBuffer(values, ref index, ref count);
+ }
+ }
+
+ public override async Task WriteAsync(string value)
+ {
+ if (value == null)
+ {
+ return;
+ }
+
+ var count = value.Length;
+ var index = 0;
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
+ {
+ await FlushInternalAsync();
+ }
+
+ CopyToCharBuffer(value, ref index, ref count);
+ }
+ }
+
+ // We want to flush the stream when Flush/FlushAsync is explicitly
+ // called by the user (example: from a Razor view).
+
+ public override void Flush()
+ {
+ FlushInternal(true, true);
+ }
+
+ public override async Task FlushAsync()
+ {
+ await FlushInternalAsync(true, true);
+ }
+
+ // Do not flush the stream on Dispose, as this will cause response to be
+ // sent in chunked encoding in case of Helios.
+ protected override void Dispose(bool disposing)
+ {
+ FlushInternal(flushStream: false, flushEncoder: true);
+ }
+
+ private void FlushInternal(bool flushStream = false, bool flushEncoder = false)
+ {
+ if (_charBufferCount == 0)
+ {
+ return;
+ }
+
+ var count = _encoder.GetBytes(_charBuffer, 0, _charBufferCount, _byteBuffer, 0, flushEncoder);
+ if (count > 0)
+ {
+ _stream.Write(_byteBuffer, 0, count);
+ }
+
+ _charBufferCount = 0;
+
+ if (flushStream)
+ {
+ _stream.Flush();
+ }
+ }
+
+ private async Task FlushInternalAsync(bool flushStream = false, bool flushEncoder = false)
+ {
+ if (_charBufferCount == 0)
+ {
+ return;
+ }
+
+ var count = _encoder.GetBytes(_charBuffer, 0, _charBufferCount, _byteBuffer, 0, flushEncoder);
+ if (count > 0)
+ {
+ await _stream.WriteAsync(_byteBuffer, 0, count);
+ }
+
+ _charBufferCount = 0;
+
+ if (flushStream)
+ {
+ await _stream.FlushAsync();
+ }
+ }
+
+ private void CopyToCharBuffer(string value, ref int index, ref int count)
+ {
+ var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
+
+ value.CopyTo(
+ sourceIndex: index,
+ destination: _charBuffer,
+ destinationIndex: _charBufferCount,
+ count: remaining);
+
+ _charBufferCount += remaining;
+ index += remaining;
+ count -= remaining;
+ }
+
+ private void CopyToCharBuffer(char[] values, ref int index, ref int count)
+ {
+ var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
+
+ Buffer.BlockCopy(
+ src: values,
+ srcOffset: index * sizeof(char),
+ dst: _charBuffer,
+ dstOffset: _charBufferCount * sizeof(char),
+ count: remaining * sizeof(char));
+
+ _charBufferCount += remaining;
+ index += remaining;
+ count -= remaining;
+ }
+ }
+}
+
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs
index 746ec2e992..f03d72a5f0 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ViewExecutorTest.cs
@@ -18,9 +18,6 @@ namespace Microsoft.AspNet.Mvc
{
public class ViewExecutorTest
{
- // The buffer size of the StreamWriter used in ViewResult.
- private const int ViewResultStreamWriterBufferSize = 1024;
-
public static TheoryData ViewExecutorSetsContentTypeAndEncodingData
{
get
@@ -85,39 +82,16 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(expectedContentData, memoryStream.ToArray());
}
- public static IEnumerable