From 297bb5d36da319cd31b441b1d8a0de52722cde57 Mon Sep 17 00:00:00 2001 From: Yishai Galatzer Date: Tue, 29 Apr 2014 10:25:32 -0700 Subject: [PATCH] Layout compilation error not showing in browser - GitHub WebFX #286 An exception thrown in a layout (or for that matter anything that is a rendering time exception) is not bubbling to the end user. The reason is that the StreamWrite is flushing because it's in a dispose pattern. The solution is to wrap the stream and prevent writes/flushes if an exception has been thrown. At the same time we stop writing BOM out to html files by default. Also specified charset explicitly - so there is matches the encoding of the page. --- .../ActionResults/ViewResult.cs | 95 ++++++++++++++++++- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs index 844dd8fad9..f39e6ac53f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs @@ -31,11 +31,23 @@ namespace Microsoft.AspNet.Mvc using (view as IDisposable) { - context.HttpContext.Response.ContentType = "text/html"; - using (var writer = new StreamWriter(context.HttpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true)) + context.HttpContext.Response.ContentType = "text/html; charset=utf-8"; + var wrappedStream = new StreamWrapper(context.HttpContext.Response.Body); + using (var writer = new StreamWriter(wrappedStream, new UTF8Encoding(false), 1024, leaveOpen: true)) { - var viewContext = new ViewContext(context, view, ViewData, writer); - await view.RenderAsync(viewContext); + try + { + var viewContext = new ViewContext(context, view, ViewData, writer); + await view.RenderAsync(viewContext); + } + catch + { + // Need to prevent writes/flushes on dispose because the StreamWriter will flush even if nothing + // got written. This leads to a response going out on the wire prematurely in case an exception + // is being thrown inside the try catch block. + wrappedStream.BlockWrites = true; + throw; + } } } } @@ -57,5 +69,80 @@ namespace Microsoft.AspNet.Mvc return result.View; } + + private class StreamWrapper : Stream + { + private readonly Stream _wrappedStream; + + public StreamWrapper([NotNull] Stream stream) + { + _wrappedStream = stream; + } + + public bool BlockWrites { get; set;} + + public override bool CanRead + { + get { return _wrappedStream.CanRead; } + } + + public override bool CanSeek + { + get { return _wrappedStream.CanSeek; } + } + + public override bool CanWrite + { + get { return _wrappedStream.CanWrite; } + } + + public override void Flush() + { + if (!BlockWrites) + { + _wrappedStream.Flush(); + } + } + + public override long Length + { + get { return _wrappedStream.Length; } + } + + public override long Position + { + get + { + return _wrappedStream.Position; + } + set + { + _wrappedStream.Position = value; + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _wrappedStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return Seek(offset, origin); + } + + public override void SetLength(long value) + { + SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (!BlockWrites) + { + _wrappedStream.Write(buffer, offset, count); + } + } + } } }