diff --git a/samples/MvcSample.Web/HomeController.cs b/samples/MvcSample.Web/HomeController.cs index a7f8251811..343d2a0f84 100644 --- a/samples/MvcSample.Web/HomeController.cs +++ b/samples/MvcSample.Web/HomeController.cs @@ -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(); diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs index afc27c9430..46ae6154e8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs @@ -18,14 +18,17 @@ namespace Microsoft.AspNet.Mvc private readonly Stream _innerStream; /// - /// Initializes a new object of DelegatingStream + /// Initializes a new . /// - /// The stream on which should not be closed + /// The stream which should not be closed or flushed. public DelegatingStream([NotNull] Stream innerStream) { _innerStream = innerStream; } + /// + /// The inner stream this object delegates to. + /// protected Stream InnerStream { get { return _innerStream; } @@ -122,7 +125,7 @@ namespace Microsoft.AspNet.Mvc /// public override void Flush() { - _innerStream.Flush(); + // Do nothing, we want to explicitly avoid flush because it turns on Chunked encoding. } /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs index edd413d9ce..c29fe2f3ef 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs @@ -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); } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index c3b0708dc7..cc6f2d8efb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -75,7 +75,8 @@ namespace Microsoft.AspNet.Mvc /// The type of the object to be serialized. public virtual Type GetObjectType([NotNull] OutputFormatterContext context) { - if (context.DeclaredType == null) + if (context.DeclaredType == null || + context.DeclaredType == typeof(object)) { if (context.Object != null) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs index 02af1d6b6a..0ee3ea2cde 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs @@ -61,11 +61,13 @@ namespace Microsoft.AspNet.Mvc /// 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); diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs index 81a74ea247..99bae29710 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs @@ -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); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs index 334fea6dd6..d3abd30fb3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs @@ -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 \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FlushReportingStream.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FlushReportingStream.cs new file mode 100644 index 0000000000..6a1c04e972 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/FlushReportingStream.cs @@ -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(); + mock.Verify(m => m.Flush(), Times.Never()); + mock.Verify(m => m.FlushAsync(It.IsAny()), Times.Never()); + + return mock.Object; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs index 21ed42dd72..1b23830e2a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs @@ -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() { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs index 75b2a698f3..941936968d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs @@ -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") {