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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
// The buffer size of the StreamWriter used in ViewResult.
|
||||
private const int ViewResultStreamWriterBufferSize = 1024;
|
||||
|
||||
public static TheoryData<MediaTypeHeaderValue, string, byte[]> ViewExecutorSetsContentTypeAndEncodingData
|
||||
{
|
||||
get
|
||||
|
|
@ -85,39 +82,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal(expectedContentData, memoryStream.ToArray());
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData
|
||||
{
|
||||
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)
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrown()
|
||||
{
|
||||
// Arrange
|
||||
var longString = new string('a', writtenLength);
|
||||
var expectedLength = 0;
|
||||
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
view.ToString();
|
||||
v.Writer.Write(longString);
|
||||
throw new Exception();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -142,11 +142,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.Formatters
|
|||
var formattedContent = "\"" + content + "\"";
|
||||
var mediaType = string.Format("application/json; charset={0}", encodingAsString);
|
||||
var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
|
||||
var preamble = encoding.GetPreamble();
|
||||
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 expectedData = encoding.GetBytes(formattedContent);
|
||||
|
||||
var memStream = new MemoryStream();
|
||||
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()
|
||||
{
|
||||
// Arrange
|
||||
var expected = Enumerable.Concat(Encoding.UTF8.GetPreamble(), _abcdUTF8Bytes);
|
||||
var expected = _abcdUTF8Bytes;
|
||||
|
||||
var context = GetHttpContext();
|
||||
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||
|
|
|
|||
Loading…
Reference in New Issue