Allow EnableBuffering + Json.NET \ Xml input formatters to work better (#16616)

* Allow EnableBuffering + Json.NET \ Xml input formatters to work better

With EnableBufering, using Newtonsoft.Json or a XML input formatter
will throw a sync IO exception by default. Instead actively drain
the stream before deserializing the content.

Fixes https://github.com/aspnet/AspNetCore/issues/16615
This commit is contained in:
Pranav K 2019-10-29 13:51:23 -07:00 committed by GitHub
parent f19a4523ed
commit a168e50d12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 3 deletions

View File

@ -489,6 +489,55 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
testBufferedReadStream.Verify(v => v.DisposeAsync(), Times.Never());
}
[Fact]
public async Task ReadAsync_WithEnableBufferingWorks()
{
// Arrange
var formatter = GetInputFormatter();
var content = "{\"name\": \"Test\"}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
httpContext.Request.EnableBuffering();
var formatterContext = CreateInputFormatterContext(typeof(ComplexModel), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
var userModel = Assert.IsType<ComplexModel>(result.Model);
Assert.Equal("Test", userModel.Name);
var requestBody = httpContext.Request.Body;
requestBody.Position = 0;
Assert.Equal(content, new StreamReader(requestBody).ReadToEnd());
}
[Fact]
public async Task ReadAsync_WithEnableBufferingWorks_WithInputStreamAtOffset()
{
// Arrange
var formatter = GetInputFormatter();
var content = "abc{\"name\": \"Test\"}";
var contentBytes = Encoding.UTF8.GetBytes(content);
var httpContext = GetHttpContext(contentBytes);
httpContext.Request.EnableBuffering();
var requestBody = httpContext.Request.Body;
requestBody.Position = 3;
var formatterContext = CreateInputFormatterContext(typeof(ComplexModel), httpContext);
// Act
var result = await formatter.ReadAsync(formatterContext);
// Assert
var userModel = Assert.IsType<ComplexModel>(result.Model);
Assert.Equal("Test", userModel.Name);
requestBody.Position = 0;
Assert.Equal(content, new StreamReader(requestBody).ReadToEnd());
}
internal abstract string JsonFormatter_EscapedKeys_Bracket_Expected { get; }
internal abstract string JsonFormatter_EscapedKeys_Expected { get; }

View File

@ -120,7 +120,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Stream readStream = new NonDisposableStream(request.Body);
var disposeReadStream = false;
if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering)
if (readStream.CanSeek)
{
// The most common way of getting here is the user has request buffering on.
// However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
// reads as part of the deserialization.
// To avoid this, drain and reset the stream.
var position = request.Body.Position;
await readStream.DrainAsync(CancellationToken.None);
readStream.Position = position;
}
else if (!_options.SuppressInputFormatterBuffering)
{
// XmlDataContractSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning.

View File

@ -101,7 +101,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
Stream readStream = new NonDisposableStream(request.Body);
var disposeReadStream = false;
if (!request.Body.CanSeek && !_options.SuppressInputFormatterBuffering)
if (readStream.CanSeek)
{
// The most common way of getting here is the user has request buffering on.
// However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
// reads as part of the deserialization.
// To avoid this, drain and reset the stream.
var position = request.Body.Position;
await readStream.DrainAsync(CancellationToken.None);
readStream.Position = position;
}
else if (!_options.SuppressInputFormatterBuffering)
{
// XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning.

View File

@ -130,7 +130,17 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
var readStream = request.Body;
var disposeReadStream = false;
if (!request.Body.CanSeek && !suppressInputFormatterBuffering)
if (readStream.CanSeek)
{
// The most common way of getting here is the user has request buffering on.
// However, request buffering isn't eager, and consequently it will peform pass-thru synchronous
// reads as part of the deserialization.
// To avoid this, drain and reset the stream.
var position = request.Body.Position;
await readStream.DrainAsync(CancellationToken.None);
readStream.Position = position;
}
else if (!suppressInputFormatterBuffering)
{
// JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously
// read everything into a buffer, and then seek back to the beginning.