diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs
index ef3d4e8b80..02964ce0b2 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/InputFormatterContext.cs
@@ -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
///
/// The for recording errors.
///
- ///
- /// The of the model to deserialize.
+ ///
+ /// The of the model to deserialize.
+ ///
+ ///
+ /// A delegate which can create a for the request body.
///
public InputFormatterContext(
HttpContext httpContext,
string modelName,
ModelStateDictionary modelState,
- ModelMetadata metadata)
+ ModelMetadata metadata,
+ Func 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 of the request body deserialization.
///
public Type ModelType { get; }
+
+ ///
+ /// Gets a delegate which can create a for the request body.
+ ///
+ public Func ReaderFactory { get; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
index 4b8dc5195c..299e930a92 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
@@ -138,6 +138,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
+ services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton, DefaultArraySegmentPool>();
services.TryAddSingleton, DefaultArraySegmentPool>();
diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs b/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs
index c56f2ec288..416d3dd2fe 100644
--- a/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs
@@ -16,6 +16,8 @@ namespace Microsoft.AspNet.Mvc
///
public class HttpResponseStreamWriter : TextWriter
{
+ private const int MinBufferSize = 128;
+
///
/// Default buffer size.
///
@@ -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(new byte[encoding.GetMaxByteCount(bufferSize)]);
_charBuffer = new ArraySegment(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)
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/HttpRequestStreamReader.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/HttpRequestStreamReader.cs
new file mode 100644
index 0000000000..2271529780
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/HttpRequestStreamReader.cs
@@ -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 _leasedByteBuffer;
+ private readonly LeasedArraySegment _leasedCharBuffer;
+
+ private readonly int _byteBufferSize;
+ private readonly ArraySegment _byteBuffer;
+ private readonly ArraySegment _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(new byte[bufferSize]);
+ var maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
+ _charBuffer = new ArraySegment(new char[maxCharsPerBuffer]);
+ }
+
+ public HttpRequestStreamReader(
+ Stream stream,
+ Encoding encoding,
+ int bufferSize,
+ LeasedArraySegment leasedByteBuffer,
+ LeasedArraySegment 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 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 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs
new file mode 100644
index 0000000000..c9f82988a8
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs
@@ -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
+{
+ ///
+ /// Creates instances for reading from .
+ ///
+ public interface IHttpRequestStreamReaderFactory
+ {
+ ///
+ /// Creates a new .
+ ///
+ /// The , usually .
+ /// The , usually .
+ /// A .
+ TextReader CreateReader(Stream stream, Encoding encoding);
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/MemoryPoolHttpRequestStreamReaderFactory.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/MemoryPoolHttpRequestStreamReaderFactory.cs
new file mode 100644
index 0000000000..287c2f3c02
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/MemoryPoolHttpRequestStreamReaderFactory.cs
@@ -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
+{
+ ///
+ /// An that uses pooled buffers.
+ ///
+ public class MemoryPoolHttpRequestStreamReaderFactory : IHttpRequestStreamReaderFactory
+ {
+ ///
+ /// The default size of created char buffers.
+ ///
+ public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8.
+
+ private readonly IArraySegmentPool _bytePool;
+ private readonly IArraySegmentPool _charPool;
+
+ ///
+ /// Creates a new .
+ ///
+ ///
+ /// The for creating buffers.
+ ///
+ ///
+ /// The for creating buffers.
+ ///
+ public MemoryPoolHttpRequestStreamReaderFactory(
+ IArraySegmentPool bytePool,
+ IArraySegmentPool charPool)
+ {
+ if (bytePool == null)
+ {
+ throw new ArgumentNullException(nameof(bytePool));
+ }
+
+ if (charPool == null)
+ {
+ throw new ArgumentNullException(nameof(charPool));
+ }
+
+ _bytePool = bytePool;
+ _charPool = charPool;
+ }
+
+ ///
+ public TextReader CreateReader(Stream stream, Encoding encoding)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+
+ LeasedArraySegment bytes = null;
+ LeasedArraySegment 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;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
index 911312991a..e20fee051a 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs
@@ -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
///
public class MvcCoreMvcOptionsSetup : ConfigureOptions
{
- 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());
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs
index 739410f6bc..234e6ca1c9 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs
@@ -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
///
public class BodyModelBinder : IModelBinder
{
+ private readonly Func _readerFactory;
+
+ ///
+ /// Creates a new .
+ ///
+ ///
+ /// The , used to create
+ /// instances for reading the request body.
+ ///
+ public BodyModelBinder(IHttpRequestStreamReaderFactory readerFactory)
+ {
+ _readerFactory = readerFactory.CreateReader;
+ }
+
///
public Task 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));
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index e62d6a880e..597acc1cd5 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -1050,6 +1050,54 @@ namespace Microsoft.AspNet.Mvc.Core
return GetString("UrlNotLocal");
}
+ ///
+ /// 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.
+ ///
+ internal static string HttpRequestStreamReader_InvalidBufferSize
+ {
+ get { return GetString("HttpRequestStreamReader_InvalidBufferSize"); }
+ }
+
+ ///
+ /// 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.
+ ///
+ 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);
+ }
+
+ ///
+ /// The stream must support reading.
+ ///
+ internal static string HttpRequestStreamReader_StreamNotReadable
+ {
+ get { return GetString("HttpRequestStreamReader_StreamNotReadable"); }
+ }
+
+ ///
+ /// The stream must support reading.
+ ///
+ internal static string FormatHttpRequestStreamReader_StreamNotReadable()
+ {
+ return GetString("HttpRequestStreamReader_StreamNotReadable");
+ }
+
+ ///
+ /// The stream must support writing.
+ ///
+ internal static string HttpResponseStreamWriter_StreamNotWritable
+ {
+ get { return GetString("HttpResponseStreamWriter_StreamNotWritable"); }
+ }
+
+ ///
+ /// The stream must support writing.
+ ///
+ internal static string FormatHttpResponseStreamWriter_StreamNotWritable()
+ {
+ return GetString("HttpResponseStreamWriter_StreamNotWritable");
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index d57c5ed85d..839ec798a1 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -322,4 +322,13 @@
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.
+
+ 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.
+
+
+ The stream must support reading.
+
+
+ The stream must support writing.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs
index badf691390..7783835f0f 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonInputFormatter.cs
@@ -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 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 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();
}
}
- ///
- /// Called during deserialization to get the .
- ///
- /// The for the read.
- /// The from which to read.
- /// The to use when reading.
- /// The used during deserialization.
- 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));
- }
-
///
/// Called during deserialization to get the .
///
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/InputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/InputFormatterTest.cs
index edce8d3bfd..066aa140f5 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/InputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/InputFormatterTest.cs
@@ -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);
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/HttpResponseStreamReaderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/HttpResponseStreamReaderTest.cs
new file mode 100644
index 0000000000..91386c00c7
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/HttpResponseStreamReaderTest.cs
@@ -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();
+ for (var i = 0; i < 1000; i++)
+ {
+ data.AddRange(testData);
+ }
+
+ return new MemoryStream(data.ToArray());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/MemoryPoolHttpResponseStreamWriterFactoryTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/MemoryPoolHttpResponseStreamWriterFactoryTest.cs
index 26b8e3d170..e368ce6691 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/MemoryPoolHttpResponseStreamWriterFactoryTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Infrastructure/MemoryPoolHttpResponseStreamWriterFactoryTest.cs
@@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
var bytePool = new Mock>(MockBehavior.Strict);
bytePool
.Setup(p => p.Lease(It.IsAny()))
- .Returns(new LeasedArraySegment(new ArraySegment(new byte[0]), bytePool.Object));
+ .Returns(new LeasedArraySegment(new ArraySegment(new byte[4096]), bytePool.Object));
bytePool
.Setup(p => p.Return(It.IsAny>()))
.Verifiable();
@@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
var charPool = new Mock>(MockBehavior.Strict);
charPool
.Setup(p => p.Lease(MemoryPoolHttpResponseStreamWriterFactory.DefaultBufferSize))
- .Returns(new LeasedArraySegment(new ArraySegment(new char[0]), charPool.Object));
+ .Returns(new LeasedArraySegment(new ArraySegment(new char[4096]), charPool.Object));
charPool
.Setup(p => p.Return(It.IsAny>()))
.Verifiable();
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
index 6bed04e909..5661f8eaae 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs
@@ -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,
};
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
index e11be51c8c..155446b16e 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs
@@ -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);
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
index 86bb77883b..e42eacf9ad 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs
@@ -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);
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
index f4b3e17db3..061c58f7b4 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
@@ -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(
diff --git a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
index 4a11d84d85..692531097c 100644
--- a/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs
@@ -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(
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs
index c0d127f4a2..c57f76e676 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/TestMvcOptions.cs
@@ -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,
diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/TestHttpRequestStreamReaderFactory.cs b/test/Microsoft.AspNet.Mvc.TestCommon/TestHttpRequestStreamReaderFactory.cs
new file mode 100644
index 0000000000..3bdfcc53e1
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.TestCommon/TestHttpRequestStreamReaderFactory.cs
@@ -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);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs
index 7c9df6ac6c..81ae3de7d6 100644
--- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs
@@ -302,6 +302,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
var context = new DefaultHttpContext();
var stream = new Mock();
+ stream.SetupGet(s => s.CanWrite).Returns(true);
context.Response.Body = stream.Object;
var actionContext = new ActionContext(