Custom stream writer which avoids writing the BOM and does not flush or close the stream.
This commit is contained in:
parent
d6012d4297
commit
40794fcc33
|
|
@ -64,98 +64,10 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
response.ContentType = contentTypeHeader.ToString();
|
response.ContentType = contentTypeHeader.ToString();
|
||||||
|
|
||||||
var wrappedStream = new StreamWrapper(response.Body);
|
using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
|
||||||
|
|
||||||
using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true))
|
|
||||||
{
|
{
|
||||||
try
|
var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
|
||||||
{
|
await view.RenderAsync(viewContext);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var response = context.ActionContext.HttpContext.Response;
|
var response = context.ActionContext.HttpContext.Response;
|
||||||
var selectedEncoding = context.SelectedEncoding;
|
var selectedEncoding = context.SelectedEncoding;
|
||||||
|
|
||||||
using (var nonDisposableStream = new NonDisposableStream(response.Body))
|
using (var writer = new HttpResponseStreamWriter(response.Body, selectedEncoding))
|
||||||
using (var writer = new StreamWriter(nonDisposableStream, selectedEncoding, 1024, leaveOpen: true))
|
|
||||||
{
|
{
|
||||||
WriteObject(writer, context.Object);
|
WriteObject(writer, context.Object);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes to the <see cref="Stream"/> using the supplied <see cref="Encoding"/>.
|
||||||
|
/// It does not write the BOM and also does not close the stream.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -18,9 +18,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public class ViewExecutorTest
|
public class ViewExecutorTest
|
||||||
{
|
{
|
||||||
// The buffer size of the StreamWriter used in ViewResult.
|
|
||||||
private const int ViewResultStreamWriterBufferSize = 1024;
|
|
||||||
|
|
||||||
public static TheoryData<MediaTypeHeaderValue, string, byte[]> ViewExecutorSetsContentTypeAndEncodingData
|
public static TheoryData<MediaTypeHeaderValue, string, byte[]> ViewExecutorSetsContentTypeAndEncodingData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -85,39 +82,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal(expectedContentData, memoryStream.ToArray());
|
Assert.Equal(expectedContentData, memoryStream.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object[]> ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData
|
[Fact]
|
||||||
{
|
public async Task ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrown()
|
||||||
get
|
|
||||||
{
|
|
||||||
yield return new object[] { 30, 0 };
|
|
||||||
|
|
||||||
if (TestPlatformHelper.IsMono)
|
|
||||||
{
|
|
||||||
// The StreamWriter in Mono buffers 2x the buffer size before flushing.
|
|
||||||
yield return new object[] { ViewResultStreamWriterBufferSize * 2 + 30, ViewResultStreamWriterBufferSize };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return new object[] { ViewResultStreamWriterBufferSize + 30, ViewResultStreamWriterBufferSize };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The StreamWriter used by ViewResult an internal buffer and consequently anything written to this buffer
|
|
||||||
// prior to it filling up will not be written to the underlying stream once an exception is thrown.
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData))]
|
|
||||||
public async Task ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrown(int writtenLength, int expectedLength)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var longString = new string('a', writtenLength);
|
var expectedLength = 0;
|
||||||
|
|
||||||
var view = new Mock<IView>();
|
var view = new Mock<IView>();
|
||||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||||
.Callback((ViewContext v) =>
|
.Callback((ViewContext v) =>
|
||||||
{
|
{
|
||||||
view.ToString();
|
|
||||||
v.Writer.Write(longString);
|
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,11 +142,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.Formatters
|
||||||
var formattedContent = "\"" + content + "\"";
|
var formattedContent = "\"" + content + "\"";
|
||||||
var mediaType = string.Format("application/json; charset={0}", encodingAsString);
|
var mediaType = string.Format("application/json; charset={0}", encodingAsString);
|
||||||
var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
|
var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
|
||||||
var preamble = encoding.GetPreamble();
|
var expectedData = encoding.GetBytes(formattedContent);
|
||||||
var data = encoding.GetBytes(formattedContent);
|
|
||||||
var expectedData = new byte[preamble.Length + data.Length];
|
|
||||||
Buffer.BlockCopy(preamble, 0, expectedData, 0, preamble.Length);
|
|
||||||
Buffer.BlockCopy(data, 0, expectedData, preamble.Length, data.Length);
|
|
||||||
|
|
||||||
var memStream = new MemoryStream();
|
var memStream = new MemoryStream();
|
||||||
var outputFormatterContext = new OutputFormatterContext
|
var outputFormatterContext = new OutputFormatterContext
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,398 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
public class HttpResponseStreamWriterTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotWriteBOM()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
var encodingWithBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
|
||||||
|
var writer = new HttpResponseStreamWriter(memoryStream, encodingWithBOM);
|
||||||
|
var expectedData = new byte[] { 97, 98, 99, 100 }; // without BOM
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
await writer.WriteAsync("abcd");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedData, memoryStream.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotFlush_UnderlyingStream_OnClosingWriter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await writer.WriteAsync("Hello");
|
||||||
|
writer.Close();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.FlushCallCount);
|
||||||
|
Assert.Equal(0, stream.FlushAsyncCallCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotFlush_UnderlyingStream_OnDisposingWriter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await writer.WriteAsync("Hello");
|
||||||
|
writer.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.FlushCallCount);
|
||||||
|
Assert.Equal(0, stream.FlushAsyncCallCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotClose_UnderlyingStream_OnDisposingWriter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await writer.WriteAsync("Hello");
|
||||||
|
writer.Close();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.CloseCallCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DoesNotDispose_UnderlyingStream_OnDisposingWriter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await writer.WriteAsync("Hello world");
|
||||||
|
writer.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.DisposeCallCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public async Task FlushesBuffer_OnClose(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
await writer.WriteAsync(new string('a', byteLength));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
writer.Close();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.FlushCallCount);
|
||||||
|
Assert.Equal(0, stream.FlushAsyncCallCount);
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public async Task FlushesBuffer_OnDispose(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
await writer.WriteAsync(new string('a', byteLength));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
writer.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.FlushCallCount);
|
||||||
|
Assert.Equal(0, stream.FlushAsyncCallCount);
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NoDataWritten_Flush_DoesNotFlushUnderlyingStream()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.FlushCallCount);
|
||||||
|
Assert.Equal(0, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public void FlushesBuffer_OnFlush(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
writer.Write(new string('a', byteLength));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, stream.FlushCallCount);
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NoDataWritten_FlushAsync_DoesNotFlushUnderlyingStream()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await writer.FlushAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, stream.FlushAsyncCallCount);
|
||||||
|
Assert.Equal(0, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public async Task FlushesBuffer_OnFlushAsync(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
await writer.WriteAsync(new string('a', byteLength));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await writer.FlushAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(1, stream.FlushAsyncCallCount);
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public void WriteChar_WritesToStream(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < byteLength; i++)
|
||||||
|
{
|
||||||
|
writer.Write('a');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public void WriteCharArray_WritesToStream(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
writer.Write((new string('a', byteLength)).ToCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public async Task WriteCharAsync_WritesToStream(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < byteLength; i++)
|
||||||
|
{
|
||||||
|
await writer.WriteAsync('a');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1023)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(1050)]
|
||||||
|
[InlineData(2048)]
|
||||||
|
public async Task WriteCharArrayAsync_WritesToStream(int byteLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stream = new TestMemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
await writer.WriteAsync((new string('a', byteLength)).ToCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(byteLength, stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("你好世界", "utf-16")]
|
||||||
|
[InlineData("こんにちは世界", "shift_jis")]
|
||||||
|
[InlineData("హలో ప్రపంచ", "iso-8859-1")]
|
||||||
|
[InlineData("வணக்கம் உலக", "utf-32")]
|
||||||
|
public async Task WritesData_InExpectedEncoding(string data, string encodingName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var encoding = Encoding.GetEncoding(encodingName);
|
||||||
|
var expectedBytes = encoding.GetBytes(data);
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, encoding);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
await writer.WriteAsync(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedBytes, stream.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData('ん', 1023, "utf-8")]
|
||||||
|
[InlineData('ん', 1024, "utf-8")]
|
||||||
|
[InlineData('ん', 1050, "utf-8")]
|
||||||
|
[InlineData('你', 1023, "utf-16")]
|
||||||
|
[InlineData('你', 1024, "utf-16")]
|
||||||
|
[InlineData('你', 1050, "utf-16")]
|
||||||
|
[InlineData('こ', 1023, "shift_jis")]
|
||||||
|
[InlineData('こ', 1024, "shift_jis")]
|
||||||
|
[InlineData('こ', 1050, "shift_jis")]
|
||||||
|
[InlineData('హ', 1023, "iso-8859-1")]
|
||||||
|
[InlineData('హ', 1024, "iso-8859-1")]
|
||||||
|
[InlineData('హ', 1050, "iso-8859-1")]
|
||||||
|
[InlineData('வ', 1023, "utf-32")]
|
||||||
|
[InlineData('வ', 1024, "utf-32")]
|
||||||
|
[InlineData('வ', 1050, "utf-32")]
|
||||||
|
public async Task WritesData_OfDifferentLength_InExpectedEncoding(
|
||||||
|
char character,
|
||||||
|
int charCount,
|
||||||
|
string encodingName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var encoding = Encoding.GetEncoding(encodingName);
|
||||||
|
string data = new string(character, charCount);
|
||||||
|
var expectedBytes = encoding.GetBytes(data);
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
var writer = new HttpResponseStreamWriter(stream, encoding);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using (writer)
|
||||||
|
{
|
||||||
|
await writer.WriteAsync(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedBytes, stream.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMemoryStream : MemoryStream
|
||||||
|
{
|
||||||
|
private int _flushCallCount;
|
||||||
|
private int _flushAsyncCallCount;
|
||||||
|
private int _closeCallCount;
|
||||||
|
private int _disposeCallCount;
|
||||||
|
|
||||||
|
public int FlushCallCount { get { return _flushCallCount; } }
|
||||||
|
|
||||||
|
public int FlushAsyncCallCount { get { return _flushAsyncCallCount; } }
|
||||||
|
|
||||||
|
public int CloseCallCount { get { return _closeCallCount; } }
|
||||||
|
|
||||||
|
public int DisposeCallCount { get { return _disposeCallCount; } }
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
_flushCallCount++;
|
||||||
|
base.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_flushAsyncCallCount++;
|
||||||
|
return base.FlushAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
_closeCallCount++;
|
||||||
|
base.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
_disposeCallCount++;
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public async Task ExecuteResultAsync_UsesPassedInFormatter()
|
public async Task ExecuteResultAsync_UsesPassedInFormatter()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = Enumerable.Concat(Encoding.UTF8.GetPreamble(), _abcdUTF8Bytes);
|
var expected = _abcdUTF8Bytes;
|
||||||
|
|
||||||
var context = GetHttpContext();
|
var context = GetHttpContext();
|
||||||
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue