Removing CopyTo from RazorTextWriter

This commit is contained in:
Pranav K 2015-11-30 11:24:25 -08:00
parent 7e1a6222aa
commit 70bdb6eb3e
6 changed files with 33 additions and 178 deletions

View File

@ -1,10 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
namespace Microsoft.AspNet.Mvc.Razor namespace Microsoft.AspNet.Mvc.Razor
@ -20,9 +19,9 @@ namespace Microsoft.AspNet.Mvc.Razor
ViewContext ViewContext { get; set; } ViewContext ViewContext { get; set; }
/// <summary> /// <summary>
/// Gets or sets the action invoked to render the body. /// Gets or sets the body content.
/// </summary> /// </summary>
Func<TextWriter, Task> RenderBodyDelegateAsync { get; set; } IHtmlContent BodyContent { get; set; }
/// <summary> /// <summary>
/// Gets or sets a flag that determines if the layout of this page is being rendered. /// Gets or sets a flag that determines if the layout of this page is being rendered.
@ -64,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.Razor
/// Verifies that all sections defined in <see cref="PreviousSectionWriters"/> were rendered, or /// Verifies that all sections defined in <see cref="PreviousSectionWriters"/> were rendered, or
/// the body was rendered if no sections were defined. /// the body was rendered if no sections were defined.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">if one or more sections were not rendered or if no sections were /// <exception cref="System.InvalidOperationException">if one or more sections were not rendered or if no sections were
/// defined and the body was not rendered.</exception> /// defined and the body was not rendered.</exception>
void EnsureRenderedBodyOrSections(); void EnsureRenderedBodyOrSections();
} }

View File

@ -105,7 +105,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public ITempDataDictionary TempData => ViewContext?.TempData; public ITempDataDictionary TempData => ViewContext?.TempData;
/// <inheritdoc /> /// <inheritdoc />
public Func<TextWriter, Task> RenderBodyDelegateAsync { get; set; } public IHtmlContent BodyContent { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public bool IsLayoutBeingRendered { get; set; } public bool IsLayoutBeingRendered { get; set; }
@ -249,26 +249,26 @@ namespace Microsoft.AspNet.Mvc.Razor
_originalWriter = null; _originalWriter = null;
} }
var tagHelperContentWrapperTextWriter = new TagHelperContentWrapperTextWriter(Output.Encoding); var tagHelperContent = new DefaultTagHelperContent();
var razorWriter = writer as RazorTextWriter; var razorWriter = writer as RazorTextWriter;
if (razorWriter != null) if (razorWriter != null)
{ {
razorWriter.CopyTo(tagHelperContentWrapperTextWriter); tagHelperContent.Append(razorWriter.Buffer);
} }
else else
{ {
var stringCollectionTextWriter = writer as StringCollectionTextWriter; var stringCollectionTextWriter = writer as StringCollectionTextWriter;
if (stringCollectionTextWriter != null) if (stringCollectionTextWriter != null)
{ {
stringCollectionTextWriter.CopyTo(tagHelperContentWrapperTextWriter, HtmlEncoder); tagHelperContent.Append(stringCollectionTextWriter.Content);
} }
else else
{ {
tagHelperContentWrapperTextWriter.Write(writer.ToString()); tagHelperContent.AppendHtml(writer.ToString());
} }
} }
return tagHelperContentWrapperTextWriter.Content; return tagHelperContent;
} }
/// <summary> /// <summary>
@ -669,16 +669,16 @@ namespace Microsoft.AspNet.Mvc.Razor
/// In a Razor layout page, renders the portion of a content page that is not within a named section. /// In a Razor layout page, renders the portion of a content page that is not within a named section.
/// </summary> /// </summary>
/// <returns>The HTML content to render.</returns> /// <returns>The HTML content to render.</returns>
protected virtual HelperResult RenderBody() protected virtual IHtmlContent RenderBody()
{ {
if (RenderBodyDelegateAsync == null) if (BodyContent == null)
{ {
var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody), Path); var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody), Path);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
_renderedBody = true; _renderedBody = true;
return new HelperResult(RenderBodyDelegateAsync); return BodyContent;
} }
/// <summary> /// <summary>
@ -886,7 +886,7 @@ namespace Microsoft.AspNet.Mvc.Razor
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames)); throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames));
} }
} }
else if (RenderBodyDelegateAsync != null && !_renderedBody) else if (BodyContent != null && !_renderedBody)
{ {
// There are no sections defined, but RenderBody was NOT called. // There are no sections defined, but RenderBody was NOT called.
// If a body was defined, then RenderBody should have been called. // If a body was defined, then RenderBody should have been called.

View File

@ -48,6 +48,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// <inheritdoc /> /// <inheritdoc />
public bool IsBuffering { get; private set; } = true; public bool IsBuffering { get; private set; } = true;
/// <summary>
/// Gets the buffered content.
/// </summary>
public IHtmlContent Buffer => BufferedWriter.Content;
// Internal for unit testing // Internal for unit testing
internal StringCollectionTextWriter BufferedWriter { get; } internal StringCollectionTextWriter BufferedWriter { get; }
@ -185,7 +190,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
IsBuffering = false; IsBuffering = false;
TargetWriter = UnbufferedWriter; TargetWriter = UnbufferedWriter;
CopyTo(UnbufferedWriter); Buffer.WriteTo(UnbufferedWriter, HtmlEncoder);
} }
UnbufferedWriter.Flush(); UnbufferedWriter.Flush();
@ -197,43 +202,16 @@ namespace Microsoft.AspNet.Mvc.Razor
/// to the unbuffered writer. /// to the unbuffered writer.
/// </summary> /// </summary>
/// <returns>A <see cref="Task"/> that represents the asynchronous copy and flush operations.</returns> /// <returns>A <see cref="Task"/> that represents the asynchronous copy and flush operations.</returns>
public override async Task FlushAsync() public override Task FlushAsync()
{ {
if (IsBuffering) if (IsBuffering)
{ {
IsBuffering = false; IsBuffering = false;
TargetWriter = UnbufferedWriter; TargetWriter = UnbufferedWriter;
await CopyToAsync(UnbufferedWriter); Buffer.WriteTo(UnbufferedWriter, HtmlEncoder);
} }
await UnbufferedWriter.FlushAsync(); return UnbufferedWriter.FlushAsync();
}
/// <inheritdoc />
public void CopyTo(TextWriter writer)
{
writer = UnWrapRazorTextWriter(writer);
BufferedWriter.CopyTo(writer, HtmlEncoder);
}
/// <inheritdoc />
public Task CopyToAsync(TextWriter writer)
{
writer = UnWrapRazorTextWriter(writer);
return BufferedWriter.CopyToAsync(writer, HtmlEncoder);
}
private static TextWriter UnWrapRazorTextWriter(TextWriter writer)
{
var targetRazorTextWriter = writer as RazorTextWriter;
if (targetRazorTextWriter != null)
{
writer = targetRazorTextWriter.IsBuffering ?
targetRazorTextWriter.BufferedWriter :
targetRazorTextWriter.UnbufferedWriter;
}
return writer;
} }
} }
} }

View File

@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Mvc.Razor
// in the layout. // in the layout.
previousPage.IsLayoutBeingRendered = true; previousPage.IsLayoutBeingRendered = true;
layoutPage.PreviousSectionWriters = previousPage.SectionWriters; layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
layoutPage.RenderBodyDelegateAsync = bodyWriter.CopyToAsync; layoutPage.BodyContent = bodyWriter.Buffer;
bodyWriter = await RenderPageAsync(layoutPage, context, viewStartPages: null); bodyWriter = await RenderPageAsync(layoutPage, context, viewStartPages: null);
renderedLayouts.Add(layoutPage); renderedLayouts.Add(layoutPage);
@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Razor
if (bodyWriter.IsBuffering) if (bodyWriter.IsBuffering)
{ {
// Only copy buffered content to the Output if we're currently buffering. // Only copy buffered content to the Output if we're currently buffering.
await bodyWriter.CopyToAsync(context.Writer); bodyWriter.Buffer.WriteTo(context.Writer, _htmlEncoder);
} }
} }

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.Razor
public class RazorPageTest public class RazorPageTest
{ {
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = writer => Task.FromResult(0); private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = writer => Task.FromResult(0);
private readonly Func<TextWriter, Task> NullAsyncWrite = CreateAsyncWriteDelegate(string.Empty); private readonly Func<TextWriter, Task> NullAsyncWrite = writer => writer.WriteAsync(string.Empty);
[Fact] [Fact]
public async Task WritingScopesRedirectContentWrittenToViewContextWriter() public async Task WritingScopesRedirectContentWrittenToViewContextWriter()
@ -311,7 +311,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
{ "baz", _nullRenderAsyncDelegate } { "baz", _nullRenderAsyncDelegate }
}; };
page.RenderBodyDelegateAsync = CreateAsyncWriteDelegate("body-content"); page.BodyContent = new HtmlString("body-content");
// Act // Act
await page.ExecuteAsync(); await page.ExecuteAsync();
@ -335,7 +335,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
{ "baz", _nullRenderAsyncDelegate } { "baz", _nullRenderAsyncDelegate }
}; };
page.RenderBodyDelegateAsync = CreateAsyncWriteDelegate("body-content"); page.BodyContent = new HtmlString("body-content");
// Act // Act
await page.ExecuteAsync(); await page.ExecuteAsync();
@ -444,7 +444,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
}); });
page.Path = path; page.Path = path;
page.RenderBodyDelegateAsync = CreateAsyncWriteDelegate("some content"); page.BodyContent = new HtmlString("some content");
// Act // Act
await page.ExecuteAsync(); await page.ExecuteAsync();
@ -464,7 +464,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
}); });
page.Path = path; page.Path = path;
page.RenderBodyDelegateAsync = CreateAsyncWriteDelegate("some content"); page.BodyContent = new HtmlString("some content");
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate> page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
{ {
{ sectionName, _nullRenderAsyncDelegate } { sectionName, _nullRenderAsyncDelegate }
@ -490,7 +490,7 @@ namespace Microsoft.AspNet.Mvc.Razor
v.RenderSection(sectionA); v.RenderSection(sectionA);
v.RenderSection(sectionB); v.RenderSection(sectionB);
}); });
page.RenderBodyDelegateAsync = CreateAsyncWriteDelegate("some content"); page.BodyContent = new HtmlString("some content");
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate> page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
{ {
{ sectionA, _nullRenderAsyncDelegate }, { sectionA, _nullRenderAsyncDelegate },
@ -524,7 +524,7 @@ namespace Microsoft.AspNet.Mvc.Razor
v.Write(v.RenderSection("footer")); v.Write(v.RenderSection("footer"));
v.WriteLiteral("Layout end"); v.WriteLiteral("Layout end");
}); });
page.RenderBodyDelegateAsync = CreateAsyncWriteDelegate("body content" + Environment.NewLine); page.BodyContent = new HtmlString("body content" + Environment.NewLine);
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate> page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
{ {
{ {
@ -1181,11 +1181,6 @@ namespace Microsoft.AspNet.Mvc.Razor
new HtmlHelperOptions()); new HtmlHelperOptions());
} }
private static Func<TextWriter, Task> CreateAsyncWriteDelegate(string value)
{
return async (writer) => await writer.WriteAsync(value);
}
public abstract class TestableRazorPage : RazorPage public abstract class TestableRazorPage : RazorPage
{ {
public TestableRazorPage() public TestableRazorPage()
@ -1202,7 +1197,7 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
} }
public HelperResult RenderBodyPublic() public IHtmlContent RenderBodyPublic()
{ {
return base.RenderBody(); return base.RenderBody();
} }

View File

@ -283,123 +283,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
Assert.Equal("Hello, world!", stringWriter.ToString()); Assert.Equal("Hello, world!", stringWriter.ToString());
} }
[Fact]
public void Copy_CopiesContent_IfTargetTextWriterIsARazorTextWriterAndBuffering()
{
// Arrange
var source = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
var target = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
// Act
source.Write("Hello world");
source.Write(new char[1], 0, 1);
source.CopyTo(target);
// Assert
// Make sure content was written to the source.
Assert.Equal(2, source.BufferedWriter.Entries.Count);
Assert.Equal(1, target.BufferedWriter.Entries.Count);
Assert.Same(source.BufferedWriter.Content, Assert.Single(target.BufferedWriter.Entries));
}
[Fact]
public void Copy_CopiesContent_IfTargetTextWriterIsARazorTextWriterAndNotBuffering()
{
// Arrange
var unbufferedWriter = new Mock<TextWriter>();
var source = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
var target = new RazorTextWriter(unbufferedWriter.Object, Encoding.UTF8, new HtmlTestEncoder());
// Act
target.Flush();
source.Write("Hello world");
source.Write(new[] { 'a', 'b', 'c', 'd' }, 1, 2);
source.CopyTo(target);
// Assert
// Make sure content was written to the source.
Assert.Equal(2, source.BufferedWriter.Entries.Count);
Assert.Empty(target.BufferedWriter.Entries);
unbufferedWriter.Verify(v => v.Write("Hello world"), Times.Once());
unbufferedWriter.Verify(v => v.Write("bc"), Times.Once());
}
[Fact]
public void Copy_WritesContent_IfTargetTextWriterIsNotARazorTextWriter()
{
// Arrange
var source = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
var target = new StringWriter();
var expected = "Hello world" + Environment.NewLine + "abc";
// Act
source.WriteLine("Hello world");
source.Write(new[] { 'x', 'a', 'b', 'c' }, 1, 3);
source.CopyTo(target);
// Assert
Assert.Equal(expected, target.ToString());
}
[Fact]
public async Task CopyAsync_WritesContent_IfTargetTextWriterIsARazorTextWriterAndBuffering()
{
// Arrange
var source = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
var target = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
// Act
source.WriteLine("Hello world");
source.Write(new[] { 'x', 'a', 'b', 'c' }, 1, 3);
await source.CopyToAsync(target);
// Assert
Assert.Equal(3, source.BufferedWriter.Entries.Count);
Assert.Equal(1, target.BufferedWriter.Entries.Count);
Assert.Equal(source.BufferedWriter.Content, Assert.Single(target.BufferedWriter.Entries));
}
//[Fact]
// IHtmlContent currently does not support async writes. Hence disabling this test.
public async Task CopyAsync_WritesContent_IfTargetTextWriterIsARazorTextWriterAndNotBuffering()
{
// Arrange
var unbufferedWriter = new Mock<TextWriter>();
var source = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
var target = new RazorTextWriter(unbufferedWriter.Object, Encoding.UTF8, new HtmlTestEncoder());
// Act
await target.FlushAsync();
source.WriteLine("Hello from Asp.Net");
await source.WriteAsync(new[] { 'x', 'y', 'z', 'u' }, 0, 3);
await source.CopyToAsync(target);
// Assert
// Make sure content was written to the source.
Assert.Equal(3, source.BufferedWriter.Entries.Count);
Assert.Empty(target.BufferedWriter.Content.ToString());
unbufferedWriter.Verify(v => v.WriteAsync("Hello from Asp.Net"), Times.Once());
unbufferedWriter.Verify(v => v.WriteAsync(Environment.NewLine), Times.Once());
unbufferedWriter.Verify(v => v.WriteAsync("xyz"), Times.Once());
}
[Fact]
public async Task CopyAsync_WritesContent_IfTargetTextWriterIsNotARazorTextWriter()
{
// Arrange
var source = new RazorTextWriter(TextWriter.Null, Encoding.UTF8, new HtmlTestEncoder());
var target = new StringWriter();
var expected = "Hello world" + Environment.NewLine;
// Act
source.Write("Hello ");
await source.WriteLineAsync(new[] { 'w', 'o', 'r', 'l', 'd' });
await source.CopyToAsync(target);
// Assert
Assert.Equal(expected, target.ToString());
}
private class TestClass private class TestClass
{ {
public override string ToString() public override string ToString()