diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs b/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs index 25b197d48d..c195bbb289 100644 --- a/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/HttpResponseStreamWriter.cs @@ -15,7 +15,10 @@ namespace Microsoft.AspNet.Mvc /// public class HttpResponseStreamWriter : TextWriter { - private const int DefaultBufferSize = 1024; + /// + /// Default buffer size. + /// + public const int DefaultBufferSize = 1024; private readonly Stream _stream; private Encoder _encoder; private byte[] _byteBuffer; diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs index 6b0536d9e4..43036f9765 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs @@ -62,6 +62,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures htmlHelperOptions); await view.RenderAsync(viewContext); + // Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying + // response. In the absence of this line, the buffer gets synchronously written to the response + // as part of the Dispose which has a perf impact. + await writer.FlushAsync(); } } } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs index 5fec6b2aa0..3dec9fd8f2 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -89,7 +90,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures view.Setup(v => v.RenderAsync(It.IsAny())) .Callback((ViewContext v) => { - view.ToString(); v.Writer.Write("abcd"); }) .Returns(Task.FromResult(0)); @@ -99,9 +99,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures context.Response.Body = memoryStream; context.Response.ContentType = responseContentType; - var actionContext = new ActionContext(context, - new RouteData(), - new ActionDescriptor()); + var actionContext = new ActionContext( + context, + new RouteData(), + new ActionDescriptor()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); // Act @@ -135,9 +136,10 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; - var actionContext = new ActionContext(context, - new RouteData(), - new ActionDescriptor()); + var actionContext = new ActionContext( + context, + new RouteData(), + new ActionDescriptor()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); // Act @@ -153,5 +155,45 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures // Assert Assert.Equal(expectedLength, memoryStream.Length); } + + [Theory] + [InlineData(HttpResponseStreamWriter.DefaultBufferSize - 1)] + [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)] + [InlineData(2 * HttpResponseStreamWriter.DefaultBufferSize + 4)] + public async Task ExecuteAsync_AsynchronouslyFlushesToTheResponseStream_PriorToDispose(int writeLength) + { + // Arrange + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Returns((ViewContext v) => + Task.Run(async () => + { + var text = new string('a', writeLength); + await v.Writer.WriteAsync(text); + })); + + var context = new DefaultHttpContext(); + var stream = new Mock(); + context.Response.Body = stream.Object; + + var actionContext = new ActionContext( + context, + new RouteData(), + new ActionDescriptor()); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + // Act + await ViewExecutor.ExecuteAsync( + view.Object, + actionContext, + viewData, + Mock.Of(), + new HtmlHelperOptions(), + ViewExecutor.DefaultContentType); + + // Assert + stream.Verify(s => s.FlushAsync(It.IsAny()), Times.Once()); + stream.Verify(s => s.Write(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + } } } \ No newline at end of file