diff --git a/src/Microsoft.AspNet.Mvc.Razor/BufferEntryCollection.cs b/src/Microsoft.AspNet.Mvc.Razor/BufferEntryCollection.cs
new file mode 100644
index 0000000000..a4e5d28bdd
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/BufferEntryCollection.cs
@@ -0,0 +1,176 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// Represents a hierarchy of strings and provides an enumerator that iterates over it as a sequence.
+ ///
+ public class BufferEntryCollection : IEnumerable
+ {
+ // Specifies the maximum size we'll allow for direct conversion from
+ // char arrays to string.
+ private const int MaxCharToStringLength = 1024;
+ private readonly List _buffer = new List();
+
+ public IReadOnlyList BufferEntries
+ {
+ get { return _buffer; }
+ }
+
+ ///
+ /// Adds a string value to the buffer.
+ ///
+ /// The value to add.
+ public void Add(string value)
+ {
+ _buffer.Add(value);
+ }
+
+ ///
+ /// Adds a subarray of characters to the buffer.
+ ///
+ /// The array to add.
+ /// The character position in the array at which to start copying data.
+ /// The number of characters to copy.
+ public void Add([NotNull] char[] value, int index, int count)
+ {
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("count");
+ }
+ if (value.Length - index < count)
+ {
+ throw new ArgumentOutOfRangeException("count");
+ }
+
+ while (count > 0)
+ {
+ // Split large char arrays into 1KB strings.
+ var currentCount = Math.Min(count, MaxCharToStringLength);
+ Add(new string(value, index, currentCount));
+ index += currentCount;
+ count -= currentCount;
+ }
+ }
+
+ ///
+ /// Adds an instance of to the buffer.
+ ///
+ /// The buffer collection to add.
+ public void Add([NotNull] BufferEntryCollection buffer)
+ {
+ _buffer.Add(buffer.BufferEntries);
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return new BufferEntryEnumerator(_buffer);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ private sealed class BufferEntryEnumerator : IEnumerator
+ {
+ private readonly Stack> _enumerators = new Stack>();
+ private readonly List _initialBuffer;
+
+ public BufferEntryEnumerator(List buffer)
+ {
+ _initialBuffer = buffer;
+ Reset();
+ }
+
+ public IEnumerator CurrentEnumerator
+ {
+ get
+ {
+ return _enumerators.Peek();
+ }
+ }
+
+ public string Current
+ {
+ get
+ {
+ var currentEnumerator = CurrentEnumerator;
+ Debug.Assert(currentEnumerator != null);
+
+ return (string)currentEnumerator.Current;
+ }
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ return Current;
+ }
+ }
+
+ public bool MoveNext()
+ {
+ var currentEnumerator = CurrentEnumerator;
+ if (currentEnumerator.MoveNext())
+ {
+ var current = currentEnumerator.Current;
+ var buffer = current as List;
+ if (buffer != null)
+ {
+ // If the next item is a collection, recursively call in to it.
+ var enumerator = buffer.GetEnumerator();
+ _enumerators.Push(enumerator);
+ return MoveNext();
+ }
+
+ return true;
+ }
+ else if (_enumerators.Count > 1)
+ {
+ // The current enumerator is exhausted and we have a parent.
+ // Pop the current enumerator out and continue with it's parent.
+ var enumerator = _enumerators.Pop();
+ enumerator.Dispose();
+
+ return MoveNext();
+ }
+
+ // We've exactly one element in our stack which cannot move next.
+ return false;
+ }
+
+ public void Reset()
+ {
+ DisposeEnumerators();
+
+ _enumerators.Push(_initialBuffer.GetEnumerator());
+ }
+
+ public void Dispose()
+ {
+ DisposeEnumerators();
+ }
+
+ private void DisposeEnumerators()
+ {
+ while (_enumerators.Count > 0)
+ {
+ _enumerators.Pop().Dispose();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
index 1b39030d2a..057f066d2a 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// 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.IO;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Razor
@@ -16,13 +18,17 @@ namespace Microsoft.AspNet.Mvc.Razor
///
ViewContext ViewContext { get; set; }
+ ///
+ /// Gets or sets the action invoked to render the body.
+ ///
+ // TODO: https://github.com/aspnet/Mvc/issues/845 tracks making this async
+ Action RenderBodyDelegate { get; set; }
+
///
/// Gets the path to the page.
///
string Path { get; set; }
- string BodyContent { get; set; }
-
///
/// Gets or sets the path of a layout page.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj b/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj
index e895509e69..68240d76e0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj
+++ b/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj
@@ -22,6 +22,7 @@
+
@@ -30,6 +31,8 @@
+
+
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/AssemblyInfo.cs
index 6d457fde04..d7eeff62c3 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/AssemblyInfo.cs
@@ -1,6 +1,6 @@
-// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Test")]
\ No newline at end of file
+[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Test")]
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
index 6aab1aca64..a18a792994 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
@@ -88,7 +88,8 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- public string BodyContent { get; set; }
+ ///
+ public Action RenderBodyDelegate { get; set; }
///
public Dictionary PreviousSectionWriters { get; set; }
@@ -243,14 +244,15 @@ namespace Microsoft.AspNet.Mvc.Razor
WritePositionTaggedLiteral(writer, value.Value, value.Position);
}
- protected virtual HtmlString RenderBody()
+ protected virtual HelperResult RenderBody()
{
- if (BodyContent == null)
+ if (RenderBodyDelegate == null)
{
throw new InvalidOperationException(Resources.FormatRenderBodyCannotBeCalled("RenderBody"));
}
+
_renderedBody = true;
- return new HtmlString(BodyContent);
+ return new HelperResult(RenderBodyDelegate);
}
public void DefineSection(string name, HelperResult action)
@@ -315,7 +317,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
// If BodyContent is set, ensure it was rendered.
- if (BodyContent != null && !_renderedBody)
+ if (RenderBodyDelegate != null && !_renderedBody)
{
// If a body was defined, then RenderBody should have been called.
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs
new file mode 100644
index 0000000000..5159e48a19
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorTextWriter.cs
@@ -0,0 +1,185 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// A that represents individual write operations as a sequence of strings.
+ ///
+ ///
+ /// This is primarily designed to avoid creating large in-memory strings.
+ /// Refer to https://aspnetwebstack.codeplex.com/workitem/585 for more details.
+ ///
+ public class RazorTextWriter : TextWriter
+ {
+ private static readonly Task _completedTask = Task.FromResult(0);
+ private readonly Encoding _encoding;
+
+ public RazorTextWriter(Encoding encoding)
+ {
+ _encoding = encoding;
+ Buffer = new BufferEntryCollection();
+ }
+
+ public override Encoding Encoding
+ {
+ get { return _encoding; }
+ }
+
+ public BufferEntryCollection Buffer { get; private set; }
+
+ ///
+ public override void Write(char value)
+ {
+ Buffer.Add(value.ToString());
+ }
+
+ ///
+ public override void Write([NotNull] char[] buffer, int index, int count)
+ {
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("count");
+ }
+ if (buffer.Length - index < count)
+ {
+ throw new ArgumentOutOfRangeException("count");
+ }
+
+ Buffer.Add(buffer, index, count);
+ }
+
+ ///
+ public override void Write(string value)
+ {
+ if (!string.IsNullOrEmpty(value))
+ {
+ Buffer.Add(value);
+ }
+ }
+
+ ///
+ public override Task WriteAsync(char value)
+ {
+ Write(value);
+ return _completedTask;
+ }
+
+ ///
+ public override Task WriteAsync([NotNull] char[] buffer, int index, int count)
+ {
+ Write(buffer, index, count);
+ return _completedTask;
+ }
+
+ ///
+ public override Task WriteAsync(string value)
+ {
+ Write(value);
+ return _completedTask;
+ }
+
+ ///
+ public override void WriteLine()
+ {
+ Buffer.Add(Environment.NewLine);
+ }
+
+ ///
+ public override void WriteLine(string value)
+ {
+ Write(value);
+ WriteLine();
+ }
+
+ ///
+ public override Task WriteLineAsync(char value)
+ {
+ WriteLine(value);
+ return _completedTask;
+ }
+
+ ///
+ public override Task WriteLineAsync(char[] value, int start, int offset)
+ {
+ WriteLine(value, start, offset);
+ return _completedTask;
+ }
+
+ ///
+ public override Task WriteLineAsync(string value)
+ {
+ WriteLine(value);
+ return _completedTask;
+ }
+
+ ///
+ public override Task WriteLineAsync()
+ {
+ WriteLine();
+ return _completedTask;
+ }
+
+ ///
+ /// Copies the content of the to the instance.
+ ///
+ /// The writer to copy contents to.
+ public void CopyTo(TextWriter writer)
+ {
+ var targetRazorTextWriter = writer as RazorTextWriter;
+ if (targetRazorTextWriter != null)
+ {
+ targetRazorTextWriter.Buffer.Add(Buffer);
+ }
+ else
+ {
+ WriteList(writer, Buffer);
+ }
+ }
+
+ ///
+ /// Copies the content of the to the specified instance.
+ ///
+ /// The writer to copy contents to.
+ /// A task that represents the asynchronous copy operation.
+ public Task CopyToAsync(TextWriter writer)
+ {
+ var targetRazorTextWriter = writer as RazorTextWriter;
+ if (targetRazorTextWriter != null)
+ {
+ targetRazorTextWriter.Buffer.Add(Buffer);
+ }
+ else
+ {
+ return WriteListAsync(writer, Buffer);
+ }
+
+ return _completedTask;
+ }
+
+ private static void WriteList(TextWriter writer, BufferEntryCollection values)
+ {
+ foreach (var value in values)
+ {
+ writer.Write(value);
+ }
+ }
+
+ private static async Task WriteListAsync(TextWriter writer, BufferEntryCollection values)
+ {
+ foreach (var value in values)
+ {
+ await writer.WriteAsync(value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
index 07efd3fbcc..31bdd04ea0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
@@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.IO;
-using System.Linq;
-using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
@@ -48,8 +45,8 @@ namespace Microsoft.AspNet.Mvc.Razor
{
if (_executeViewHierarchy)
{
- var bodyContent = await RenderPageAsync(_page, context, executeViewStart: true);
- await RenderLayoutAsync(context, bodyContent);
+ var bodyWriter = await RenderPageAsync(_page, context, executeViewStart: true);
+ await RenderLayoutAsync(context, bodyWriter);
}
else
{
@@ -57,17 +54,16 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- private async Task RenderPageAsync(IRazorPage page,
- ViewContext context,
- bool executeViewStart)
+ private async Task RenderPageAsync(IRazorPage page,
+ ViewContext context,
+ bool executeViewStart)
{
- var contentBuilder = new StringBuilder(1024);
- using (var bodyWriter = new StringWriter(contentBuilder))
+ using (var bufferedWriter = new RazorTextWriter(context.Writer.Encoding))
{
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
// and ViewComponents to reference it.
var oldWriter = context.Writer;
- context.Writer = bodyWriter;
+ context.Writer = bufferedWriter;
try
{
if (executeViewStart)
@@ -75,15 +71,15 @@ namespace Microsoft.AspNet.Mvc.Razor
// Execute view starts using the same context + writer as the page to render.
await RenderViewStartAsync(context);
}
+
await RenderPageCoreAsync(page, context);
+ return bufferedWriter;
}
finally
{
context.Writer = oldWriter;
}
}
-
- return contentBuilder.ToString();
}
private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
@@ -107,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
private async Task RenderLayoutAsync(ViewContext context,
- string bodyContent)
+ RazorTextWriter bodyWriter)
{
// A layout page can specify another layout page. We'll need to continue
// looking for layout pages until they're no longer specified.
@@ -122,9 +118,8 @@ namespace Microsoft.AspNet.Mvc.Razor
}
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
- layoutPage.BodyContent = bodyContent;
-
- bodyContent = await RenderPageAsync(layoutPage, context, executeViewStart: false);
+ layoutPage.RenderBodyDelegate = bodyWriter.CopyTo;
+ bodyWriter = await RenderPageAsync(layoutPage, context, executeViewStart: false);
// Verify that RenderBody is called, or that RenderSection is called for all sections
layoutPage.EnsureBodyAndSectionsWereRendered();
@@ -132,7 +127,7 @@ namespace Microsoft.AspNet.Mvc.Razor
previousPage = layoutPage;
}
- await context.Writer.WriteAsync(bodyContent);
+ await bodyWriter.CopyToAsync(context.Writer);
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
index 448c9a3636..9cc9c1569c 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj
@@ -104,6 +104,7 @@
+
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/BufferEntryCollectionTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/BufferEntryCollectionTest.cs
new file mode 100644
index 0000000000..cd6999435b
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/BufferEntryCollectionTest.cs
@@ -0,0 +1,160 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// 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.Linq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ public class BufferEntryCollectionTest
+ {
+ [Fact]
+ public void Add_AddsBufferEntries()
+ {
+ // Arrange
+ var collection = new BufferEntryCollection();
+ var inner = new BufferEntryCollection();
+
+ // Act
+ collection.Add("Hello");
+ collection.Add(new[] { 'a', 'b', 'c' }, 1, 2);
+ collection.Add(inner);
+
+ // Assert
+ Assert.Equal("Hello", collection.BufferEntries[0]);
+ Assert.Equal("bc", collection.BufferEntries[1]);
+ Assert.Same(inner.BufferEntries, collection.BufferEntries[2]);
+ }
+
+ [Fact]
+ public void AddChar_ThrowsIfIndexIsOutOfBounds()
+ {
+ // Arrange
+ var collection = new BufferEntryCollection();
+
+ // Act and Assert
+ var ex = Assert.Throws(
+ () => collection.Add(new[] { 'a', 'b', 'c' }, -1, 2));
+ Assert.Equal("index", ex.ParamName);
+ }
+
+ [Fact]
+ public void AddChar_ThrowsIfCountWouldCauseOutOfBoundReads()
+ {
+ // Arrange
+ var collection = new BufferEntryCollection();
+
+ // Act and Assert
+ var ex = Assert.Throws(
+ () => collection.Add(new[] { 'a', 'b', 'c' }, 1, 3));
+ Assert.Equal("count", ex.ParamName);
+ }
+
+ public static IEnumerable AddWithChar_RepresentsStringsAsChunkedEntriesData
+ {
+ get
+ {
+ var charArray1 = new[] { 'a' };
+ var expected1 = new[] { "a" };
+ yield return new object[] { charArray1, 0, 1, expected1 };
+
+ var charArray2 = Enumerable.Repeat('a', 10).ToArray();
+ var expected2 = new[] { new string(charArray2) };
+ yield return new object[] { charArray2, 0, 10, expected2 };
+
+ var charArray3 = Enumerable.Repeat('b', 1024).ToArray();
+ var expected3 = new[] { new string('b', 1023) };
+ yield return new object[] { charArray3, 1, 1023, expected3 };
+
+ var charArray4 = Enumerable.Repeat('c', 1027).ToArray();
+ var expected4 = new[] { new string('c', 1024), "cc" };
+ yield return new object[] { charArray4, 1, 1026, expected4 };
+
+ var charArray5 = Enumerable.Repeat('d', 4099).ToArray();
+ var expected5 = new[] { new string('d', 1024), new string('d', 1024), new string('d', 1024), new string('d', 1024), "d" };
+ yield return new object[] { charArray5, 2, 4097, expected5 };
+
+ var charArray6 = Enumerable.Repeat('e', 1025).ToArray();
+ var expected6 = new[] { "ee" };
+ yield return new object[] { charArray6, 1023, 2, expected6 };
+ }
+ }
+
+ [Theory]
+ [MemberData("AddWithChar_RepresentsStringsAsChunkedEntriesData")]
+ public void AddWithChar_RepresentsStringsAsChunkedEntries(char[] value, int index, int count, IList expected)
+ {
+ // Arrange
+ var collection = new BufferEntryCollection();
+
+ // Act
+ collection.Add(value, index, count);
+
+ // Assert
+ Assert.Equal(expected, collection.BufferEntries);
+ }
+
+ public static IEnumerable Enumerator_TraversesThroughBufferData
+ {
+ get
+ {
+ var collection1 = new BufferEntryCollection();
+ collection1.Add("foo");
+ collection1.Add("bar");
+
+ var expected1 = new[]
+ {
+ "foo",
+ "bar"
+ };
+ yield return new object[] { collection1, expected1 };
+
+ // Nested collection
+ var nestedCollection2 = new BufferEntryCollection();
+ nestedCollection2.Add("level 1");
+ var nestedCollection2SecondLevel = new BufferEntryCollection();
+ nestedCollection2SecondLevel.Add("level 2");
+ nestedCollection2.Add(nestedCollection2SecondLevel);
+ var collection2 = new BufferEntryCollection();
+ collection2.Add("foo");
+ collection2.Add(nestedCollection2);
+ collection2.Add("qux");
+
+ var expected2 = new[]
+ {
+ "foo",
+ "level 1",
+ "level 2",
+ "qux"
+ };
+ yield return new object[] { collection2, expected2 };
+
+ // Nested collection
+ var collection3 = new BufferEntryCollection();
+ collection3.Add("Hello");
+ var emptyNestedCollection = new BufferEntryCollection();
+ emptyNestedCollection.Add(new BufferEntryCollection());
+ collection3.Add(emptyNestedCollection);
+ collection3.Add("world");
+
+ var expected3 = new[]
+ {
+ "Hello",
+ "world"
+ };
+ yield return new object[] { collection3, expected3 };
+ }
+ }
+
+
+ [Theory]
+ [MemberData("Enumerator_TraversesThroughBufferData")]
+ public void Enumerator_TraversesThroughBuffer(BufferEntryCollection buffer, string[] expected)
+ {
+ // Act and Assert
+ Assert.Equal(expected, buffer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj b/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj
index 75641c822d..f6d31ead62 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Microsoft.AspNet.Mvc.Razor.Test.kproj
@@ -21,9 +21,11 @@
+
+
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
index b59cd388b3..081df63fe2 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
@@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
{ "baz", new HelperResult(writer => { }) }
};
- page.BodyContent = "body-content";
+ page.RenderBodyDelegate = CreateBodyAction("body-content");
// Act
await page.ExecuteAsync();
@@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
{ "baz", new HelperResult(writer => { }) }
};
- page.BodyContent = "body-content";
+ page.RenderBodyDelegate = CreateBodyAction("body-content");
// Act
await page.ExecuteAsync();
@@ -211,7 +211,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var page = CreatePage(v =>
{
});
- page.BodyContent = "some content";
+ page.RenderBodyDelegate = CreateBodyAction("some content");
// Act
await page.ExecuteAsync();
@@ -240,7 +240,7 @@ Layout end
v.WriteLiteral("Layout end" + Environment.NewLine);
});
- page.BodyContent = "body content" + Environment.NewLine;
+ page.RenderBodyDelegate = CreateBodyAction("body content" + Environment.NewLine);
page.PreviousSectionWriters = new Dictionary
{
{
@@ -289,9 +289,14 @@ Layout end
new StringWriter());
}
+ private static Action CreateBodyAction(string value)
+ {
+ return writer => writer.Write(value);
+ }
+
public abstract class TestableRazorPage : RazorPage
{
- public HtmlString RenderBodyPublic()
+ public HelperResult RenderBodyPublic()
{
return base.RenderBody();
}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs
new file mode 100644
index 0000000000..ee3a49ae37
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorTextWriterTest.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// 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.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Testing;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Razor.Test
+{
+ public class RazorTextWriterTest
+ {
+ [Fact]
+ [ReplaceCulture]
+ public void Write_WritesDataTypes_ToBuffer()
+ {
+ // Arrange
+ var expected = new[] { "True", "3", "18446744073709551615", "Hello world", "3.14", "2.718", "m" };
+ var writer = new RazorTextWriter(Encoding.UTF8);
+
+ // Act
+ writer.Write(true);
+ writer.Write(3);
+ writer.Write(ulong.MaxValue);
+ writer.Write(new TestClass());
+ writer.Write(3.14);
+ writer.Write(2.718m);
+ writer.Write('m');
+
+ // Assert
+ Assert.Equal(expected, writer.Buffer.BufferEntries);
+ }
+
+ [Fact]
+ [ReplaceCulture]
+ public void WriteLine_WritesDataTypes_ToBuffer()
+ {
+ // Arrange
+ var newLine = Environment.NewLine;
+ var expected = new List { "False", newLine, "1.1", newLine, "3", newLine };
+ var writer = new RazorTextWriter(Encoding.UTF8);
+
+ // Act
+ writer.WriteLine(false);
+ writer.WriteLine(1.1f);
+ writer.WriteLine(3L);
+
+ // Assert
+ Assert.Equal(expected, writer.Buffer.BufferEntries);
+ }
+
+ [Fact]
+ public async Task Write_WritesCharBuffer()
+ {
+ // Arrange
+ var input1 = new ArraySegment(new char[] { 'a', 'b', 'c', 'd' }, 1, 3);
+ var input2 = new ArraySegment(new char[] { 'e', 'f' }, 0, 2);
+ var input3 = new ArraySegment(new char[] { 'g', 'h', 'i', 'j' }, 3, 1);
+ var writer = new RazorTextWriter(Encoding.UTF8);
+
+ // Act
+ writer.Write(input1.Array, input1.Offset, input1.Count);
+ await writer.WriteAsync(input2.Array, input2.Offset, input2.Count);
+ await writer.WriteLineAsync(input3.Array, input3.Offset, input3.Count);
+
+ // Assert
+ var buffer = writer.Buffer.BufferEntries;
+ Assert.Equal(4, buffer.Count);
+ Assert.Equal("bcd", buffer[0]);
+ Assert.Equal("ef", buffer[1]);
+ Assert.Equal("j", buffer[2]);
+ Assert.Equal(Environment.NewLine, buffer[3]);
+ }
+
+ [Fact]
+ public async Task WriteLines_WritesCharBuffer()
+ {
+ // Arrange
+ var newLine = Environment.NewLine;
+ var writer = new RazorTextWriter(Encoding.UTF8);
+
+ // Act
+ writer.WriteLine();
+ await writer.WriteLineAsync();
+
+ // Assert
+ var actual = writer.Buffer.BufferEntries;
+ Assert.Equal(new[] { newLine, newLine }, actual);
+ }
+
+ [Fact]
+ public async Task Write_WritesStringBuffer()
+ {
+ // Arrange
+ var newLine = Environment.NewLine;
+ var input1 = "Hello";
+ var input2 = "from";
+ var input3 = "ASP";
+ var input4 = ".Net";
+ var writer = new RazorTextWriter(Encoding.UTF8);
+
+ // Act
+ writer.Write(input1);
+ writer.WriteLine(input2);
+ await writer.WriteAsync(input3);
+ await writer.WriteLineAsync(input4);
+
+ // Assert
+ var actual = writer.Buffer.BufferEntries;
+ Assert.Equal(new[] { input1, input2, newLine, input3, input4, newLine }, actual);
+ }
+
+ [Fact]
+ public void Copy_CopiesContent_IfTargetTextWriterIsARazorTextWriter()
+ {
+ // Arrange
+ var source = new RazorTextWriter(Encoding.UTF8);
+ var target = new RazorTextWriter(Encoding.UTF8);
+
+ // 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.Buffer.BufferEntries.Count);
+ Assert.Equal(1, target.Buffer.BufferEntries.Count);
+ Assert.Same(source.Buffer.BufferEntries, target.Buffer.BufferEntries[0]);
+ }
+
+ [Fact]
+ public void Copy_WritesContent_IfTargetTextWriterIsNotARazorTextWriter()
+ {
+ // Arrange
+ var source = new RazorTextWriter(Encoding.UTF8);
+ var target = new StringWriter();
+ var expected = @"Hello world
+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_IfTargetTextWriterIsARazorTextWriter()
+ {
+ // Arrange
+ var source = new RazorTextWriter(Encoding.UTF8);
+ var target = new RazorTextWriter(Encoding.UTF8);
+
+ // Act
+ source.WriteLine("Hello world");
+ source.Write(new[] { 'x', 'a', 'b', 'c' }, 1, 3);
+ await source.CopyToAsync(target);
+
+ // Assert
+ Assert.Equal(3, source.Buffer.BufferEntries.Count);
+ Assert.Equal(1, target.Buffer.BufferEntries.Count);
+ Assert.Same(source.Buffer.BufferEntries, target.Buffer.BufferEntries[0]);
+ }
+
+ [Fact]
+ public async Task CopyAsync_WritesContent_IfTargetTextWriterIsNotARazorTextWriter()
+ {
+ // Arrange
+ var source = new RazorTextWriter(Encoding.UTF8);
+ var target = new StringWriter();
+ var expected = @"Hello world
+";
+
+ // 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
+ {
+ public override string ToString()
+ {
+ return "Hello world";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
index 686d9affee..acc9b39ed2 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
@@ -143,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.Razor
await view.RenderAsync(viewContext);
// Assert
- Assert.IsType(actual);
+ Assert.IsType(actual);
Assert.NotSame(original, actual);
}