Add InputFormatter buffer pooling
This commit is contained in:
parent
ff91e796c1
commit
e2c2676042
|
|
@ -2,6 +2,8 @@
|
|||
// 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 Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
|
|
@ -22,14 +24,18 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
/// <param name="modelState">
|
||||
/// The <see cref="ModelStateDictionary"/> for recording errors.
|
||||
/// </param>
|
||||
/// <param name="modelType">
|
||||
/// The <see cref="Type"/> of the model to deserialize.
|
||||
/// <param name="metadata">
|
||||
/// The <see cref="ModelMetadata"/> of the model to deserialize.
|
||||
/// </param>
|
||||
/// <param name="readerFactory">
|
||||
/// A delegate which can create a <see cref="TextReader"/> for the request body.
|
||||
/// </param>
|
||||
public InputFormatterContext(
|
||||
HttpContext httpContext,
|
||||
string modelName,
|
||||
ModelStateDictionary modelState,
|
||||
ModelMetadata metadata)
|
||||
ModelMetadata metadata,
|
||||
Func<Stream, Encoding, TextReader> readerFactory)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
|
|
@ -51,10 +57,16 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
if (readerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(readerFactory));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
ModelName = modelName;
|
||||
ModelState = modelState;
|
||||
Metadata = metadata;
|
||||
ReaderFactory = readerFactory;
|
||||
ModelType = metadata.ModelType;
|
||||
}
|
||||
|
||||
|
|
@ -82,5 +94,10 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
/// Gets the requested <see cref="Type"/> of the request body deserialization.
|
||||
/// </summary>
|
||||
public Type ModelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a delegate which can create a <see cref="TextReader"/> for the request body.
|
||||
/// </summary>
|
||||
public Func<Stream, Encoding, TextReader> ReaderFactory { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
services.TryAddSingleton<IActionBindingContextAccessor, ActionBindingContextAccessor>();
|
||||
services.TryAddSingleton<IUrlHelper, UrlHelper>();
|
||||
services.TryAddSingleton<IHttpRequestStreamReaderFactory, MemoryPoolHttpRequestStreamReaderFactory>();
|
||||
services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>();
|
||||
services.TryAddSingleton<IArraySegmentPool<byte>, DefaultArraySegmentPool<byte>>();
|
||||
services.TryAddSingleton<IArraySegmentPool<char>, DefaultArraySegmentPool<char>>();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
/// </summary>
|
||||
public class HttpResponseStreamWriter : TextWriter
|
||||
{
|
||||
private const int MinBufferSize = 128;
|
||||
|
||||
/// <summary>
|
||||
/// Default buffer size.
|
||||
/// </summary>
|
||||
|
|
@ -42,6 +44,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (!stream.CanWrite)
|
||||
{
|
||||
throw new ArgumentException(Resources.HttpResponseStreamWriter_StreamNotWritable, nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
|
|
@ -51,6 +58,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
Encoding = encoding;
|
||||
_charBufferSize = bufferSize;
|
||||
|
||||
if (bufferSize < MinBufferSize)
|
||||
{
|
||||
bufferSize = MinBufferSize;
|
||||
}
|
||||
|
||||
_encoder = encoding.GetEncoder();
|
||||
_byteBuffer = new ArraySegment<byte>(new byte[encoding.GetMaxByteCount(bufferSize)]);
|
||||
_charBuffer = new ArraySegment<char>(new char[bufferSize]);
|
||||
|
|
@ -68,6 +80,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (!stream.CanWrite)
|
||||
{
|
||||
throw new ArgumentException(Resources.HttpResponseStreamWriter_StreamNotWritable, nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
|
|
@ -83,6 +100,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
throw new ArgumentNullException(nameof(leasedCharBuffer));
|
||||
}
|
||||
|
||||
if (bufferSize <= 0 || bufferSize > leasedCharBuffer.Data.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
}
|
||||
|
||||
var requiredLength = encoding.GetMaxByteCount(bufferSize);
|
||||
if (requiredLength > leasedByteBuffer.Data.Count)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,431 @@
|
|||
// 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.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.Extensions.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Infrastructure
|
||||
{
|
||||
public class HttpRequestStreamReader : TextReader
|
||||
{
|
||||
private const int DefaultBufferSize = 1024;
|
||||
private const int MinBufferSize = 128;
|
||||
private const int MaxSharedBuilderCapacity = 360; // also the max capacity used in StringBuilderCache
|
||||
|
||||
private Stream _stream;
|
||||
private readonly Encoding _encoding;
|
||||
private readonly Decoder _decoder;
|
||||
|
||||
private readonly LeasedArraySegment<byte> _leasedByteBuffer;
|
||||
private readonly LeasedArraySegment<char> _leasedCharBuffer;
|
||||
|
||||
private readonly int _byteBufferSize;
|
||||
private readonly ArraySegment<byte> _byteBuffer;
|
||||
private readonly ArraySegment<char> _charBuffer;
|
||||
|
||||
private int _charBufferIndex;
|
||||
private int _charsRead;
|
||||
private int _bytesRead;
|
||||
|
||||
private bool _isBlocked;
|
||||
|
||||
public HttpRequestStreamReader(Stream stream, Encoding encoding)
|
||||
: this(stream, encoding, DefaultBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpRequestStreamReader(Stream stream, Encoding encoding, int bufferSize)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
_encoding = encoding;
|
||||
_decoder = encoding.GetDecoder();
|
||||
|
||||
if (bufferSize < MinBufferSize)
|
||||
{
|
||||
bufferSize = MinBufferSize;
|
||||
}
|
||||
|
||||
_byteBufferSize = bufferSize;
|
||||
_byteBuffer = new ArraySegment<byte>(new byte[bufferSize]);
|
||||
var maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
|
||||
_charBuffer = new ArraySegment<char>(new char[maxCharsPerBuffer]);
|
||||
}
|
||||
|
||||
public HttpRequestStreamReader(
|
||||
Stream stream,
|
||||
Encoding encoding,
|
||||
int bufferSize,
|
||||
LeasedArraySegment<byte> leasedByteBuffer,
|
||||
LeasedArraySegment<char> leasedCharBuffer)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
if (leasedByteBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(leasedByteBuffer));
|
||||
}
|
||||
|
||||
if (leasedCharBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(leasedCharBuffer));
|
||||
}
|
||||
|
||||
if (bufferSize <= 0 || bufferSize > leasedByteBuffer.Data.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
}
|
||||
|
||||
var requiredLength = encoding.GetMaxCharCount(bufferSize);
|
||||
if (requiredLength > leasedCharBuffer.Data.Count)
|
||||
{
|
||||
var message = Resources.FormatHttpRequestStreamReader_InvalidBufferSize(
|
||||
requiredLength,
|
||||
bufferSize,
|
||||
encoding.EncodingName,
|
||||
typeof(Encoding).FullName,
|
||||
nameof(Encoding.GetMaxCharCount));
|
||||
throw new ArgumentException(message, nameof(leasedCharBuffer));
|
||||
}
|
||||
|
||||
_stream = stream;
|
||||
_encoding = encoding;
|
||||
_byteBufferSize = bufferSize;
|
||||
_leasedByteBuffer = leasedByteBuffer;
|
||||
_leasedCharBuffer = leasedCharBuffer;
|
||||
|
||||
_decoder = encoding.GetDecoder();
|
||||
_byteBuffer = _leasedByteBuffer.Data;
|
||||
_charBuffer = _leasedCharBuffer.Data;
|
||||
}
|
||||
|
||||
#if dnx451
|
||||
public override void Close()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _stream != null)
|
||||
{
|
||||
_stream = null;
|
||||
|
||||
if (_leasedByteBuffer != null)
|
||||
{
|
||||
_leasedByteBuffer.Owner.Return(_leasedByteBuffer);
|
||||
}
|
||||
|
||||
if (_leasedCharBuffer != null)
|
||||
{
|
||||
_leasedCharBuffer.Owner.Return(_leasedCharBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override int Peek()
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException("stream");
|
||||
}
|
||||
|
||||
if (_charBufferIndex == _charsRead)
|
||||
{
|
||||
if (_isBlocked || ReadIntoBuffer() == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return _charBuffer.Array[_charBuffer.Offset + _charBufferIndex];
|
||||
}
|
||||
|
||||
public override int Read()
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException("stream");
|
||||
}
|
||||
|
||||
if (_charBufferIndex == _charsRead)
|
||||
{
|
||||
if (ReadIntoBuffer() == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return _charBuffer.Array[_charBuffer.Offset + _charBufferIndex++];
|
||||
}
|
||||
|
||||
public override int Read(char[] buffer, int index, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
if (count < 0 || index + count > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
|
||||
if (_stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException("stream");
|
||||
}
|
||||
|
||||
var charsRead = 0;
|
||||
while (count > 0)
|
||||
{
|
||||
var charsRemaining = _charsRead - _charBufferIndex;
|
||||
if (charsRemaining == 0)
|
||||
{
|
||||
charsRemaining = ReadIntoBuffer();
|
||||
}
|
||||
|
||||
if (charsRemaining == 0)
|
||||
{
|
||||
break; // We're at EOF
|
||||
}
|
||||
|
||||
if (charsRemaining > count)
|
||||
{
|
||||
charsRemaining = count;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(
|
||||
_charBuffer.Array,
|
||||
(_charBuffer.Offset + _charBufferIndex) * 2,
|
||||
buffer,
|
||||
(index + charsRead) * 2,
|
||||
charsRemaining * 2);
|
||||
_charBufferIndex += charsRemaining;
|
||||
|
||||
charsRead += charsRemaining;
|
||||
count -= charsRemaining;
|
||||
|
||||
// If we got back fewer chars than we asked for, then it's likely the underlying stream is blocked.
|
||||
// Send the data back to the caller so they can process it.
|
||||
if (_isBlocked)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return charsRead;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(char[] buffer, int index, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
if (count < 0 || index + count > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
|
||||
if (_stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException("stream");
|
||||
}
|
||||
|
||||
if (_charBufferIndex == _charsRead && await ReadIntoBufferAsync() == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var charsRead = 0;
|
||||
while (count > 0)
|
||||
{
|
||||
// n is the characters available in _charBuffer
|
||||
var n = _charsRead - _charBufferIndex;
|
||||
|
||||
// charBuffer is empty, let's read from the stream
|
||||
if (n == 0)
|
||||
{
|
||||
_charsRead = 0;
|
||||
_charBufferIndex = 0;
|
||||
_bytesRead = 0;
|
||||
|
||||
// We loop here so that we read in enough bytes to yield at least 1 char.
|
||||
// We break out of the loop if the stream is blocked (EOF is reached).
|
||||
do
|
||||
{
|
||||
Debug.Assert(n == 0);
|
||||
_bytesRead = await _stream.ReadAsync(
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
_byteBufferSize);
|
||||
if (_bytesRead == 0) // EOF
|
||||
{
|
||||
_isBlocked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// _isBlocked == whether we read fewer bytes than we asked for.
|
||||
_isBlocked = (_bytesRead < _byteBufferSize);
|
||||
|
||||
Debug.Assert(n == 0);
|
||||
|
||||
_charBufferIndex = 0;
|
||||
n = _decoder.GetChars(
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
_bytesRead,
|
||||
_charBuffer.Array,
|
||||
_charBuffer.Offset);
|
||||
|
||||
Debug.Assert(n > 0);
|
||||
|
||||
_charsRead += n; // Number of chars in StreamReader's buffer.
|
||||
}
|
||||
while (n == 0);
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
break; // We're at EOF
|
||||
}
|
||||
}
|
||||
|
||||
// Got more chars in charBuffer than the user requested
|
||||
if (n > count)
|
||||
{
|
||||
n = count;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(
|
||||
_charBuffer.Array,
|
||||
(_charBuffer.Offset + _charBufferIndex) * 2,
|
||||
buffer,
|
||||
(index + charsRead) * 2,
|
||||
n * 2);
|
||||
|
||||
_charBufferIndex += n;
|
||||
|
||||
charsRead += n;
|
||||
count -= n;
|
||||
|
||||
// This function shouldn't block for an indefinite amount of time,
|
||||
// or reading from a network stream won't work right. If we got
|
||||
// fewer bytes than we requested, then we want to break right here.
|
||||
if (_isBlocked)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return charsRead;
|
||||
}
|
||||
|
||||
private int ReadIntoBuffer()
|
||||
{
|
||||
_charsRead = 0;
|
||||
_charBufferIndex = 0;
|
||||
_bytesRead = 0;
|
||||
|
||||
do
|
||||
{
|
||||
_bytesRead = _stream.Read(_byteBuffer.Array, _byteBuffer.Offset, _byteBufferSize);
|
||||
if (_bytesRead == 0) // We're at EOF
|
||||
{
|
||||
return _charsRead;
|
||||
}
|
||||
|
||||
_isBlocked = (_bytesRead < _byteBufferSize);
|
||||
_charsRead += _decoder.GetChars(
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
_bytesRead,
|
||||
_charBuffer.Array,
|
||||
_charBuffer.Offset + _charsRead);
|
||||
}
|
||||
while (_charsRead == 0);
|
||||
|
||||
return _charsRead;
|
||||
}
|
||||
|
||||
private async Task<int> ReadIntoBufferAsync()
|
||||
{
|
||||
_charsRead = 0;
|
||||
_charBufferIndex = 0;
|
||||
_bytesRead = 0;
|
||||
|
||||
do
|
||||
{
|
||||
|
||||
_bytesRead = await _stream.ReadAsync(
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
_byteBufferSize).ConfigureAwait(false);
|
||||
if (_bytesRead == 0)
|
||||
{
|
||||
// We're at EOF
|
||||
return _charsRead;
|
||||
}
|
||||
|
||||
// _isBlocked == whether we read fewer bytes than we asked for.
|
||||
_isBlocked = (_bytesRead < _byteBufferSize);
|
||||
|
||||
_charsRead += _decoder.GetChars(
|
||||
_byteBuffer.Array,
|
||||
_byteBuffer.Offset,
|
||||
_bytesRead,
|
||||
_charBuffer.Array,
|
||||
_charBuffer.Offset + _charsRead);
|
||||
}
|
||||
while (_charsRead == 0);
|
||||
|
||||
return _charsRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates <see cref="TextReader"/> instances for reading from <see cref="Http.HttpRequest.Body"/>.
|
||||
/// </summary>
|
||||
public interface IHttpRequestStreamReaderFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TextReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="stream">The <see cref="Stream"/>, usually <see cref="Http.HttpRequest.Body"/>.</param>
|
||||
/// <param name="encoding">The <see cref="Encoding"/>, usually <see cref="Encoding.UTF8"/>.</param>
|
||||
/// <returns>A <see cref="TextReader"/>.</returns>
|
||||
TextReader CreateReader(Stream stream, Encoding encoding);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// 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 Microsoft.Extensions.MemoryPool;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IHttpRequestStreamReaderFactory"/> that uses pooled buffers.
|
||||
/// </summary>
|
||||
public class MemoryPoolHttpRequestStreamReaderFactory : IHttpRequestStreamReaderFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The default size of created char buffers.
|
||||
/// </summary>
|
||||
public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8.
|
||||
|
||||
private readonly IArraySegmentPool<byte> _bytePool;
|
||||
private readonly IArraySegmentPool<char> _charPool;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryPoolHttpRequestStreamReaderFactory"/>.
|
||||
/// </summary>
|
||||
/// <param name="bytePool">
|
||||
/// The <see cref="IArraySegmentPool{byte}"/> for creating <see cref="byte"/> buffers.
|
||||
/// </param>
|
||||
/// <param name="charPool">
|
||||
/// The <see cref="IArraySegmentPool{char}"/> for creating <see cref="char"/> buffers.
|
||||
/// </param>
|
||||
public MemoryPoolHttpRequestStreamReaderFactory(
|
||||
IArraySegmentPool<byte> bytePool,
|
||||
IArraySegmentPool<char> charPool)
|
||||
{
|
||||
if (bytePool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytePool));
|
||||
}
|
||||
|
||||
if (charPool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(charPool));
|
||||
}
|
||||
|
||||
_bytePool = bytePool;
|
||||
_charPool = charPool;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextReader CreateReader(Stream stream, Encoding encoding)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
LeasedArraySegment<byte> bytes = null;
|
||||
LeasedArraySegment<char> chars = null;
|
||||
|
||||
try
|
||||
{
|
||||
bytes = _bytePool.Lease(DefaultBufferSize);
|
||||
|
||||
// We need to compute the minimum size of the char buffer based on the size of the byte buffer,
|
||||
// so that we have enough room to encode the buffer in one shot.
|
||||
var minimumSize = encoding.GetMaxCharCount(DefaultBufferSize);
|
||||
chars = _charPool.Lease(minimumSize);
|
||||
|
||||
return new HttpRequestStreamReader(stream, encoding, DefaultBufferSize, bytes, chars);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bytes != null)
|
||||
{
|
||||
bytes.Owner.Return(bytes);
|
||||
}
|
||||
|
||||
if (chars != null)
|
||||
{
|
||||
chars.Owner.Return(chars);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Formatters;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
|
||||
|
|
@ -17,12 +18,12 @@ namespace Microsoft.AspNet.Mvc.Internal
|
|||
/// </summary>
|
||||
public class MvcCoreMvcOptionsSetup : ConfigureOptions<MvcOptions>
|
||||
{
|
||||
public MvcCoreMvcOptionsSetup()
|
||||
: base(ConfigureMvc)
|
||||
public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory)
|
||||
: base((options) => ConfigureMvc(options, readerFactory))
|
||||
{
|
||||
}
|
||||
|
||||
public static void ConfigureMvc(MvcOptions options)
|
||||
public static void ConfigureMvc(MvcOptions options, IHttpRequestStreamReaderFactory readerFactory)
|
||||
{
|
||||
// Set up default error messages
|
||||
var messageProvider = options.ModelBindingMessageProvider;
|
||||
|
|
@ -33,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Internal
|
|||
// Set up ModelBinding
|
||||
options.ModelBinders.Add(new BinderTypeBasedModelBinder());
|
||||
options.ModelBinders.Add(new ServicesModelBinder());
|
||||
options.ModelBinders.Add(new BodyModelBinder());
|
||||
options.ModelBinders.Add(new BodyModelBinder(readerFactory));
|
||||
options.ModelBinders.Add(new HeaderModelBinder());
|
||||
options.ModelBinders.Add(new SimpleTypeModelBinder());
|
||||
options.ModelBinders.Add(new CancellationTokenModelBinder());
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
// 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.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Formatters;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -15,6 +18,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
public class BodyModelBinder : IModelBinder
|
||||
{
|
||||
private readonly Func<Stream, Encoding, TextReader> _readerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BodyModelBinder"/>.
|
||||
/// </summary>
|
||||
/// <param name="readerFactory">
|
||||
/// The <see cref="IHttpRequestStreamReaderFactory"/>, used to create <see cref="System.IO.TextReader"/>
|
||||
/// instances for reading the request body.
|
||||
/// </param>
|
||||
public BodyModelBinder(IHttpRequestStreamReaderFactory readerFactory)
|
||||
{
|
||||
_readerFactory = readerFactory.CreateReader;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
|
|
@ -59,7 +76,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
httpContext,
|
||||
modelBindingKey,
|
||||
bindingContext.ModelState,
|
||||
bindingContext.ModelMetadata);
|
||||
bindingContext.ModelMetadata,
|
||||
_readerFactory);
|
||||
|
||||
var formatters = bindingContext.OperationBindingContext.InputFormatters;
|
||||
var formatter = formatters.FirstOrDefault(f => f.CanRead(formatterContext));
|
||||
|
||||
|
|
|
|||
|
|
@ -1050,6 +1050,54 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return GetString("UrlNotLocal");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The char buffer must have a length of at least '{0}' to be used with a byte buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the char buffer.
|
||||
/// </summary>
|
||||
internal static string HttpRequestStreamReader_InvalidBufferSize
|
||||
{
|
||||
get { return GetString("HttpRequestStreamReader_InvalidBufferSize"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The char buffer must have a length of at least '{0}' to be used with a byte buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the char buffer.
|
||||
/// </summary>
|
||||
internal static string FormatHttpRequestStreamReader_InvalidBufferSize(object p0, object p1, object p2, object p3, object p4)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HttpRequestStreamReader_InvalidBufferSize"), p0, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stream must support reading.
|
||||
/// </summary>
|
||||
internal static string HttpRequestStreamReader_StreamNotReadable
|
||||
{
|
||||
get { return GetString("HttpRequestStreamReader_StreamNotReadable"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stream must support reading.
|
||||
/// </summary>
|
||||
internal static string FormatHttpRequestStreamReader_StreamNotReadable()
|
||||
{
|
||||
return GetString("HttpRequestStreamReader_StreamNotReadable");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stream must support writing.
|
||||
/// </summary>
|
||||
internal static string HttpResponseStreamWriter_StreamNotWritable
|
||||
{
|
||||
get { return GetString("HttpResponseStreamWriter_StreamNotWritable"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stream must support writing.
|
||||
/// </summary>
|
||||
internal static string FormatHttpResponseStreamWriter_StreamNotWritable()
|
||||
{
|
||||
return GetString("HttpResponseStreamWriter_StreamNotWritable");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -322,4 +322,13 @@
|
|||
<data name="UrlNotLocal" xml:space="preserve">
|
||||
<value>The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.</value>
|
||||
</data>
|
||||
<data name="HttpRequestStreamReader_InvalidBufferSize" xml:space="preserve">
|
||||
<value>The char buffer must have a length of at least '{0}' to be used with a byte buffer of size '{1}' and encoding '{2}'. Use '{3}.{4}' to compute the correct size for the char buffer.</value>
|
||||
</data>
|
||||
<data name="HttpRequestStreamReader_StreamNotReadable" xml:space="preserve">
|
||||
<value>The stream must support reading.</value>
|
||||
</data>
|
||||
<data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve">
|
||||
<value>The stream must support writing.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Infrastructure;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -72,98 +73,71 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
}
|
||||
|
||||
var request = context.HttpContext.Request;
|
||||
using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding))
|
||||
using (var streamReader = context.ReaderFactory(request.Body, effectiveEncoding))
|
||||
{
|
||||
jsonReader.CloseInput = false;
|
||||
|
||||
var successful = true;
|
||||
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = (sender, eventArgs) =>
|
||||
using (var jsonReader = new JsonTextReader(streamReader))
|
||||
{
|
||||
successful = false;
|
||||
jsonReader.CloseInput = false;
|
||||
|
||||
var exception = eventArgs.ErrorContext.Error;
|
||||
|
||||
// Handle path combinations such as "" + "Property", "Parent" + "Property", or "Parent" + "[12]".
|
||||
var key = eventArgs.ErrorContext.Path;
|
||||
if (!string.IsNullOrEmpty(context.ModelName))
|
||||
var successful = true;
|
||||
EventHandler<Newtonsoft.Json.Serialization.ErrorEventArgs> errorHandler = (sender, eventArgs) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventArgs.ErrorContext.Path))
|
||||
successful = false;
|
||||
|
||||
var exception = eventArgs.ErrorContext.Error;
|
||||
|
||||
// Handle path combinations such as "" + "Property", "Parent" + "Property", or "Parent" + "[12]".
|
||||
var key = eventArgs.ErrorContext.Path;
|
||||
if (!string.IsNullOrEmpty(context.ModelName))
|
||||
{
|
||||
key = context.ModelName;
|
||||
}
|
||||
else if (eventArgs.ErrorContext.Path[0] == '[')
|
||||
{
|
||||
key = context.ModelName + eventArgs.ErrorContext.Path;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = context.ModelName + "." + eventArgs.ErrorContext.Path;
|
||||
if (string.IsNullOrEmpty(eventArgs.ErrorContext.Path))
|
||||
{
|
||||
key = context.ModelName;
|
||||
}
|
||||
else if (eventArgs.ErrorContext.Path[0] == '[')
|
||||
{
|
||||
key = context.ModelName + eventArgs.ErrorContext.Path;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = context.ModelName + "." + eventArgs.ErrorContext.Path;
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path);
|
||||
context.ModelState.TryAddModelError(key, eventArgs.ErrorContext.Error, metadata);
|
||||
|
||||
// Error must always be marked as handled
|
||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||
// overflow the stack for x64 CLR processes
|
||||
eventArgs.ErrorContext.Handled = true;
|
||||
};
|
||||
|
||||
var type = context.ModelType;
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
jsonSerializer.Error += errorHandler;
|
||||
|
||||
object model;
|
||||
try
|
||||
{
|
||||
model = jsonSerializer.Deserialize(jsonReader, type);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
|
||||
jsonSerializer.Error -= errorHandler;
|
||||
}
|
||||
|
||||
var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path);
|
||||
context.ModelState.TryAddModelError(key, eventArgs.ErrorContext.Error, metadata);
|
||||
if (successful)
|
||||
{
|
||||
return InputFormatterResult.SuccessAsync(model);
|
||||
}
|
||||
|
||||
// Error must always be marked as handled
|
||||
// Failure to do so can cause the exception to be rethrown at every recursive level and
|
||||
// overflow the stack for x64 CLR processes
|
||||
eventArgs.ErrorContext.Handled = true;
|
||||
};
|
||||
|
||||
var type = context.ModelType;
|
||||
var jsonSerializer = CreateJsonSerializer();
|
||||
jsonSerializer.Error += errorHandler;
|
||||
|
||||
object model;
|
||||
try
|
||||
{
|
||||
model = jsonSerializer.Deserialize(jsonReader, type);
|
||||
return InputFormatterResult.FailureAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up the error handler in case CreateJsonSerializer() reuses a serializer
|
||||
jsonSerializer.Error -= errorHandler;
|
||||
}
|
||||
|
||||
if (successful)
|
||||
{
|
||||
return InputFormatterResult.SuccessAsync(model);
|
||||
}
|
||||
|
||||
return InputFormatterResult.FailureAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to get the <see cref="JsonReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="InputFormatterContext"/> for the read.</param>
|
||||
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
|
||||
/// <param name="effectiveEncoding">The <see cref="Encoding"/> to use when reading.</param>
|
||||
/// <returns>The <see cref="JsonReader"/> used during deserialization.</returns>
|
||||
protected virtual JsonReader CreateJsonReader(
|
||||
InputFormatterContext context,
|
||||
Stream readStream,
|
||||
Encoding effectiveEncoding)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (readStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(readStream));
|
||||
}
|
||||
|
||||
if (effectiveEncoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(effectiveEncoding));
|
||||
}
|
||||
|
||||
return new JsonTextReader(new StreamReader(readStream, effectiveEncoding));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -76,7 +77,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -106,7 +108,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -139,7 +142,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -169,7 +173,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -205,7 +210,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -234,7 +240,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -268,7 +275,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
@ -299,7 +307,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(context);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,226 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Infrastructure
|
||||
{
|
||||
public class HttpResponseStreamReaderTest
|
||||
{
|
||||
private static readonly char[] CharData = new char[]
|
||||
{
|
||||
char.MinValue,
|
||||
char.MaxValue,
|
||||
'\t',
|
||||
' ',
|
||||
'$',
|
||||
'@',
|
||||
'#',
|
||||
'\0',
|
||||
'\v',
|
||||
'\'',
|
||||
'\u3190',
|
||||
'\uC3A0',
|
||||
'A',
|
||||
'5',
|
||||
'\r',
|
||||
'\uFE70',
|
||||
'-',
|
||||
';',
|
||||
'\r',
|
||||
'\n',
|
||||
'T',
|
||||
'3',
|
||||
'\n',
|
||||
'K',
|
||||
'\u00E6',
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public static async Task ReadToEndAsync()
|
||||
{
|
||||
// Arrange
|
||||
var reader = new HttpRequestStreamReader(GetLargeStream(), Encoding.UTF8);
|
||||
|
||||
var result = await reader.ReadToEndAsync();
|
||||
|
||||
Assert.Equal(5000, result.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TestRead()
|
||||
{
|
||||
// Arrange
|
||||
var reader = CreateReader();
|
||||
|
||||
// Act & Assert
|
||||
for (var i = 0; i < CharData.Length; i++)
|
||||
{
|
||||
var tmp = reader.Read();
|
||||
Assert.Equal((int)CharData[i], tmp);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TestPeek()
|
||||
{
|
||||
// Arrange
|
||||
var reader = CreateReader();
|
||||
|
||||
// Act & Assert
|
||||
for (var i = 0; i < CharData.Length; i++)
|
||||
{
|
||||
var peek = reader.Peek();
|
||||
Assert.Equal((int)CharData[i], peek);
|
||||
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void EmptyStream()
|
||||
{
|
||||
// Arrange
|
||||
var reader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8);
|
||||
var buffer = new char[10];
|
||||
|
||||
// Act
|
||||
var read = reader.Read(buffer, 0, 1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, read);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Read_ReadAllCharactersAtOnce()
|
||||
{
|
||||
// Arrange
|
||||
var reader = CreateReader();
|
||||
var chars = new char[CharData.Length];
|
||||
|
||||
// Act
|
||||
var read = reader.Read(chars, 0, chars.Length);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(chars.Length, read);
|
||||
for (var i = 0; i < CharData.Length; i++)
|
||||
{
|
||||
Assert.Equal(CharData[i], chars[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static async Task Read_ReadInTwoChunks()
|
||||
{
|
||||
// Arrange
|
||||
var reader = CreateReader();
|
||||
var chars = new char[CharData.Length];
|
||||
|
||||
// Act
|
||||
var read = await reader.ReadAsync(chars, 4, 3);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(read, 3);
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Assert.Equal(CharData[i], chars[i + 4]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void ReadLine_ReadMultipleLines()
|
||||
{
|
||||
// Arrange
|
||||
var reader = CreateReader();
|
||||
var valueString = new string(CharData);
|
||||
|
||||
// Act & Assert
|
||||
var data = reader.ReadLine();
|
||||
Assert.Equal(valueString.Substring(0, valueString.IndexOf('\r')), data);
|
||||
|
||||
data = reader.ReadLine();
|
||||
Assert.Equal(valueString.Substring(valueString.IndexOf('\r') + 1, 3), data);
|
||||
|
||||
data = reader.ReadLine();
|
||||
Assert.Equal(valueString.Substring(valueString.IndexOf('\n') + 1, 2), data);
|
||||
|
||||
data = reader.ReadLine();
|
||||
Assert.Equal((valueString.Substring(valueString.LastIndexOf('\n') + 1)), data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void ReadLine_ReadWithNoNewlines()
|
||||
{
|
||||
// Arrange
|
||||
var reader = CreateReader();
|
||||
var valueString = new string(CharData);
|
||||
var temp = new char[10];
|
||||
|
||||
// Act
|
||||
reader.Read(temp, 0, 1);
|
||||
var data = reader.ReadLine();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(valueString.Substring(1, valueString.IndexOf('\r') - 1), data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static async Task ReadLineAsync_MultipleContinuousLines()
|
||||
{
|
||||
// Arrange
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream);
|
||||
writer.Write("\n\n\r\r\n");
|
||||
writer.Flush();
|
||||
stream.Position = 0;
|
||||
|
||||
var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
|
||||
|
||||
// Act & Assert
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var data = await reader.ReadLineAsync();
|
||||
Assert.Equal(string.Empty, data);
|
||||
}
|
||||
|
||||
var eol = await reader.ReadLineAsync();
|
||||
Assert.Null(eol);
|
||||
}
|
||||
|
||||
private static HttpRequestStreamReader CreateReader()
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream);
|
||||
writer.Write(CharData);
|
||||
writer.Flush();
|
||||
stream.Position = 0;
|
||||
|
||||
return new HttpRequestStreamReader(stream, Encoding.UTF8);
|
||||
}
|
||||
|
||||
private static MemoryStream GetSmallStream()
|
||||
{
|
||||
var testData = new byte[] { 72, 69, 76, 76, 79 };
|
||||
return new MemoryStream(testData);
|
||||
}
|
||||
|
||||
private static MemoryStream GetLargeStream()
|
||||
{
|
||||
var testData = new byte[] { 72, 69, 76, 76, 79 };
|
||||
// System.Collections.Generic.
|
||||
|
||||
var data = new List<byte>();
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
data.AddRange(testData);
|
||||
}
|
||||
|
||||
return new MemoryStream(data.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
|
|||
var bytePool = new Mock<IArraySegmentPool<byte>>(MockBehavior.Strict);
|
||||
bytePool
|
||||
.Setup(p => p.Lease(It.IsAny<int>()))
|
||||
.Returns(new LeasedArraySegment<byte>(new ArraySegment<byte>(new byte[0]), bytePool.Object));
|
||||
.Returns(new LeasedArraySegment<byte>(new ArraySegment<byte>(new byte[4096]), bytePool.Object));
|
||||
bytePool
|
||||
.Setup(p => p.Return(It.IsAny<LeasedArraySegment<byte>>()))
|
||||
.Verifiable();
|
||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
|
|||
var charPool = new Mock<IArraySegmentPool<char>>(MockBehavior.Strict);
|
||||
charPool
|
||||
.Setup(p => p.Lease(MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize))
|
||||
.Returns(new LeasedArraySegment<char>(new ArraySegment<char>(new char[0]), charPool.Object));
|
||||
.Returns(new LeasedArraySegment<char>(new ArraySegment<char>(new char[4096]), charPool.Object));
|
||||
charPool
|
||||
.Setup(p => p.Return(It.IsAny<LeasedArraySegment<char>>()))
|
||||
.Verifiable();
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
new[] { inputFormatter },
|
||||
metadataProvider: provider);
|
||||
|
||||
var binder = new BodyModelBinder();
|
||||
var binder = new BodyModelBinder(new TestHttpRequestStreamReaderFactory());
|
||||
|
||||
// Act
|
||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||
|
|
@ -257,7 +257,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var operationBindingContext = new OperationBindingContext
|
||||
{
|
||||
InputFormatters = inputFormatters.ToList(),
|
||||
ModelBinder = new BodyModelBinder(),
|
||||
ModelBinder = new BodyModelBinder(new TestHttpRequestStreamReaderFactory()),
|
||||
MetadataProvider = metadataProvider,
|
||||
HttpContext = httpContext,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(formatterContext);
|
||||
|
|
@ -92,7 +93,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -117,7 +119,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -145,7 +148,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -173,7 +177,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -200,7 +205,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: "names",
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -228,7 +234,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
modelState.MaxAllowedErrors = 3;
|
||||
modelState.AddModelError("key1", "error1");
|
||||
|
|
@ -286,7 +293,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await jsonFormatter.ReadAsync(inputFormatterContext);
|
||||
|
|
@ -321,7 +329,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await jsonFormatter.ReadAsync(inputFormatterContext);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -61,7 +62,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -96,7 +98,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(formatterContext);
|
||||
|
|
@ -123,7 +126,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(formatterContext);
|
||||
|
|
@ -151,7 +155,8 @@ namespace Microsoft.AspNet.Mvc.Formatters
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(formatterContext);
|
||||
|
|
@ -345,7 +346,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context));
|
||||
|
|
@ -411,7 +413,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -556,7 +559,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = formatter.CanRead(formatterContext);
|
||||
|
|
@ -350,7 +351,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act and Assert
|
||||
var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context));
|
||||
|
|
@ -413,7 +415,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: modelState,
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
|
||||
// Act
|
||||
var result = await formatter.ReadAsync(context);
|
||||
|
|
@ -437,7 +440,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml
|
|||
httpContext,
|
||||
modelName: string.Empty,
|
||||
modelState: new ModelStateDictionary(),
|
||||
metadata: metadata);
|
||||
metadata: metadata,
|
||||
readerFactory: new TestHttpRequestStreamReaderFactory().CreateReader);
|
||||
}
|
||||
|
||||
private static HttpContext GetHttpContext(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using Microsoft.AspNet.Mvc.DataAnnotations.Internal;
|
||||
using Microsoft.AspNet.Mvc.Formatters.Json.Internal;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
using Microsoft.AspNet.Mvc.TestCommon;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.OptionsModel;
|
||||
|
||||
|
|
@ -15,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
public TestMvcOptions()
|
||||
{
|
||||
Value = new MvcOptions();
|
||||
MvcCoreMvcOptionsSetup.ConfigureMvc(Value);
|
||||
MvcCoreMvcOptionsSetup.ConfigureMvc(Value, new TestHttpRequestStreamReaderFactory());
|
||||
var collection = new ServiceCollection().AddOptions();
|
||||
MvcDataAnnotationsMvcOptionsSetup.ConfigureMvc(
|
||||
Value,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// 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 Microsoft.AspNet.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class TestHttpRequestStreamReaderFactory : IHttpRequestStreamReaderFactory
|
||||
{
|
||||
public TextReader CreateReader(Stream stream, Encoding encoding)
|
||||
{
|
||||
return new HttpRequestStreamReader(stream, encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,6 +302,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
|
||||
var context = new DefaultHttpContext();
|
||||
var stream = new Mock<Stream>();
|
||||
stream.SetupGet(s => s.CanWrite).Returns(true);
|
||||
context.Response.Body = stream.Object;
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
|
|
|
|||
Loading…
Reference in New Issue