Prevent flushing when writing out from the formatters.

Also make the XML formatter respect object as a return type.

Issues: MVC/1073, MVC/972, MVC/958

1072 is a followup work item to add test coverage.
This commit is contained in:
YishaiGalatzer 2014-08-27 20:47:07 -07:00
parent ddea73b934
commit e40dbcaebf
10 changed files with 121 additions and 15 deletions

View File

@ -97,7 +97,7 @@ namespace MvcSample.Web
Context.Response.WriteAsync("Hello World raw");
}
[Produces("application/json", "application/custom", "text/json", Type = typeof(User))]
[Produces("application/json", "application/xml", "application/custom", "text/json", Type = typeof(User))]
public object ReturnUser()
{
return CreateUser();

View File

@ -18,14 +18,17 @@ namespace Microsoft.AspNet.Mvc
private readonly Stream _innerStream;
/// <summary>
/// Initializes a new object of DelegatingStream
/// Initializes a new <see cref="DelegatingStream"/>.
/// </summary>
/// <param name="innerStream">The stream on which should not be closed</param>
/// <param name="innerStream">The stream which should not be closed or flushed.</param>
public DelegatingStream([NotNull] Stream innerStream)
{
_innerStream = innerStream;
}
/// <summary>
/// The inner stream this object delegates to.
/// </summary>
protected Stream InnerStream
{
get { return _innerStream; }
@ -122,7 +125,7 @@ namespace Microsoft.AspNet.Mvc
/// <inheritdoc />
public override void Flush()
{
_innerStream.Flush();
// Do nothing, we want to explicitly avoid flush because it turns on Chunked encoding.
}
/// <inheritdoc />

View File

@ -46,11 +46,6 @@ namespace Microsoft.AspNet.Mvc
{
var jsonSerializer = CreateJsonSerializer();
jsonSerializer.Serialize(jsonWriter, value);
// We're explicitly calling flush here to simplify the debugging experience because the
// underlying TextWriter might be long-lived. If this method ends up being called repeatedly
// for a request, we should revisit.
jsonWriter.Flush();
}
}
@ -77,7 +72,9 @@ namespace Microsoft.AspNet.Mvc
{
var response = context.ActionContext.HttpContext.Response;
var selectedEncoding = context.SelectedEncoding;
using (var writer = new StreamWriter(response.Body, selectedEncoding, 1024, leaveOpen: true))
using (var delegatingStream = new DelegatingStream(response.Body))
using (var writer = new StreamWriter(delegatingStream, selectedEncoding, 1024, leaveOpen: true))
{
WriteObject(writer, context.Object);
}

View File

@ -75,7 +75,8 @@ namespace Microsoft.AspNet.Mvc
/// <returns>The type of the object to be serialized.</returns>
public virtual Type GetObjectType([NotNull] OutputFormatterContext context)
{
if (context.DeclaredType == null)
if (context.DeclaredType == null ||
context.DeclaredType == typeof(object))
{
if (context.Object != null)
{

View File

@ -61,11 +61,13 @@ namespace Microsoft.AspNet.Mvc
/// <inheritdoc />
public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context)
{
var response = context.ActionContext.HttpContext.Response;
var tempWriterSettings = WriterSettings.Clone();
tempWriterSettings.Encoding = context.SelectedEncoding;
using (var xmlWriter = CreateXmlWriter(response.Body, tempWriterSettings))
var innerStream = context.ActionContext.HttpContext.Response.Body;
using (var outputStream = new DelegatingStream(innerStream))
using (var xmlWriter = CreateXmlWriter(outputStream, tempWriterSettings))
{
var dataContractSerializer = (DataContractSerializer)CreateSerializer(GetObjectType(context));
dataContractSerializer.WriteObject(xmlWriter, context.Object);

View File

@ -61,7 +61,11 @@ namespace Microsoft.AspNet.Mvc
var tempWriterSettings = WriterSettings.Clone();
tempWriterSettings.Encoding = context.SelectedEncoding;
using (var xmlWriter = CreateXmlWriter(response.Body, tempWriterSettings))
var innerStream = context.ActionContext.HttpContext.Response.Body;
using (var outputStream = new DelegatingStream(innerStream))
using (var xmlWriter = CreateXmlWriter(outputStream, tempWriterSettings))
{
var xmlSerializer = (XmlSerializer)CreateSerializer(GetObjectType(context));
xmlSerializer.Serialize(xmlWriter, context.Object);

View File

@ -3,6 +3,10 @@
#if NET45
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
@ -36,6 +40,52 @@ namespace Microsoft.AspNet.Mvc
// Assert
Assert.True(innerStream.CanRead);
}
[Fact]
public void InnerStreamIsNotFlushedOnDispose()
{
var stream = FlushReportingStream.GetThrowingStream();
var delegatingStream = new DelegatingStream(stream);
// Act & Assert
delegatingStream.Dispose();
}
[Fact]
public void InnerStreamIsNotFlushedOnClose()
{
// Arrange
var stream = FlushReportingStream.GetThrowingStream();
var delegatingStream = new DelegatingStream(stream);
// Act & Assert
delegatingStream.Close();
}
[Fact]
public void InnerStreamIsNotFlushedOnFlush()
{
// Arrange
var stream = FlushReportingStream.GetThrowingStream();
var delegatingStream = new DelegatingStream(stream);
// Act & Assert
delegatingStream.Flush();
}
[Fact]
public async Task InnerStreamIsNotFlushedOnFlushAsync()
{
// Arrange
var stream = FlushReportingStream.GetThrowingStream();
var delegatingStream = new DelegatingStream(stream);
// Act & Assert
await delegatingStream.FlushAsync();
}
}
}
#endif

View File

@ -0,0 +1,18 @@
using System.IO;
using System.Threading;
using Moq;
namespace Microsoft.AspNet.Mvc.Core
{
public static class FlushReportingStream
{
public static Stream GetThrowingStream()
{
var mock = new Mock<Stream>();
mock.Verify(m => m.Flush(), Times.Never());
mock.Verify(m => m.FlushAsync(It.IsAny<CancellationToken>()), Times.Never());
return mock.Object;
}
}
}

View File

@ -182,6 +182,21 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead);
}
[Fact]
public async Task XmlSerializerOutputFormatterDoesntFlushOutputStream()
{
// Arrange
var sampleInput = new DummyClass { SampleInt = 10 };
var formatter = new XmlDataContractSerializerOutputFormatter();
var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType());
var response = outputFormatterContext.ActionContext.HttpContext.Response;
response.Body = FlushReportingStream.GetThrowingStream();
// Act & Assert
await formatter.WriteAsync(outputFormatterContext);
}
[Fact]
public void XmlDataContractSerializer_CanWriteResult_ReturnsTrue_ForWritableType()
{

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@ -199,6 +200,21 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.True(formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml")));
}
[Fact]
public async Task XmlSerializerOutputFormatterDoesntFlushOutputStream()
{
// Arrange
var sampleInput = new DummyClass { SampleInt = 10 };
var formatter = new XmlSerializerOutputFormatter();
var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType());
var response = outputFormatterContext.ActionContext.HttpContext.Response;
response.Body = FlushReportingStream.GetThrowingStream();
// Act & Assert
await formatter.WriteAsync(outputFormatterContext);
}
private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType,
string contentType = "application/xml; charset=utf-8")
{