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"); 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() public object ReturnUser()
{ {
return CreateUser(); return CreateUser();

View File

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

View File

@ -46,11 +46,6 @@ namespace Microsoft.AspNet.Mvc
{ {
var jsonSerializer = CreateJsonSerializer(); var jsonSerializer = CreateJsonSerializer();
jsonSerializer.Serialize(jsonWriter, value); 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 response = context.ActionContext.HttpContext.Response;
var selectedEncoding = context.SelectedEncoding; 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); WriteObject(writer, context.Object);
} }

View File

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

View File

@ -61,11 +61,13 @@ namespace Microsoft.AspNet.Mvc
/// <inheritdoc /> /// <inheritdoc />
public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context) public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context)
{ {
var response = context.ActionContext.HttpContext.Response;
var tempWriterSettings = WriterSettings.Clone(); var tempWriterSettings = WriterSettings.Clone();
tempWriterSettings.Encoding = context.SelectedEncoding; 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)); var dataContractSerializer = (DataContractSerializer)CreateSerializer(GetObjectType(context));
dataContractSerializer.WriteObject(xmlWriter, context.Object); dataContractSerializer.WriteObject(xmlWriter, context.Object);

View File

@ -61,7 +61,11 @@ namespace Microsoft.AspNet.Mvc
var tempWriterSettings = WriterSettings.Clone(); var tempWriterSettings = WriterSettings.Clone();
tempWriterSettings.Encoding = context.SelectedEncoding; 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)); var xmlSerializer = (XmlSerializer)CreateSerializer(GetObjectType(context));
xmlSerializer.Serialize(xmlWriter, context.Object); xmlSerializer.Serialize(xmlWriter, context.Object);

View File

@ -3,6 +3,10 @@
#if NET45 #if NET45
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -36,6 +40,52 @@ namespace Microsoft.AspNet.Mvc
// Assert // Assert
Assert.True(innerStream.CanRead); 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 #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); 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] [Fact]
public void XmlDataContractSerializer_CanWriteResult_ReturnsTrue_ForWritableType() public void XmlDataContractSerializer_CanWriteResult_ReturnsTrue_ForWritableType()
{ {

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -199,6 +200,21 @@ namespace Microsoft.AspNet.Mvc.Core
Assert.True(formatter.CanWriteResult(outputFormatterContext, MediaTypeHeaderValue.Parse("application/xml"))); 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, private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType,
string contentType = "application/xml; charset=utf-8") string contentType = "application/xml; charset=utf-8")
{ {