Introducing RazorTextWriter
RazorTextWriter represents the result of rendering a page as a sequence of strings rather than a concatenated string. This avoids building up large strings in memory.
This commit is contained in:
parent
dc58eb297d
commit
5168808906
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a hierarchy of strings and provides an enumerator that iterates over it as a sequence.
|
||||||
|
/// </summary>
|
||||||
|
public class BufferEntryCollection : IEnumerable<string>
|
||||||
|
{
|
||||||
|
// Specifies the maximum size we'll allow for direct conversion from
|
||||||
|
// char arrays to string.
|
||||||
|
private const int MaxCharToStringLength = 1024;
|
||||||
|
private readonly List<object> _buffer = new List<object>();
|
||||||
|
|
||||||
|
public IReadOnlyList<object> BufferEntries
|
||||||
|
{
|
||||||
|
get { return _buffer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a string value to the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to add.</param>
|
||||||
|
public void Add(string value)
|
||||||
|
{
|
||||||
|
_buffer.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a subarray of characters to the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The array to add.</param>
|
||||||
|
/// <param name="index">The character position in the array at which to start copying data.</param>
|
||||||
|
/// <param name="count">The number of characters to copy.</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an instance of <see cref="BufferEntryCollection"/> to the buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer collection to add.</param>
|
||||||
|
public void Add([NotNull] BufferEntryCollection buffer)
|
||||||
|
{
|
||||||
|
_buffer.Add(buffer.BufferEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<string> GetEnumerator()
|
||||||
|
{
|
||||||
|
return new BufferEntryEnumerator(_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class BufferEntryEnumerator : IEnumerator<string>
|
||||||
|
{
|
||||||
|
private readonly Stack<IEnumerator<object>> _enumerators = new Stack<IEnumerator<object>>();
|
||||||
|
private readonly List<object> _initialBuffer;
|
||||||
|
|
||||||
|
public BufferEntryEnumerator(List<object> buffer)
|
||||||
|
{
|
||||||
|
_initialBuffer = buffer;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<object> 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<object>;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// 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.
|
// 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;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Razor
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
@ -16,13 +18,17 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ViewContext ViewContext { get; set; }
|
ViewContext ViewContext { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the action invoked to render the body.
|
||||||
|
/// </summary>
|
||||||
|
// TODO: https://github.com/aspnet/Mvc/issues/845 tracks making this async
|
||||||
|
Action<TextWriter> RenderBodyDelegate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the page.
|
/// Gets the path to the page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Path { get; set; }
|
string Path { get; set; }
|
||||||
|
|
||||||
string BodyContent { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the path of a layout page.
|
/// Gets or sets the path of a layout page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AttributeValue.cs" />
|
<Compile Include="AttributeValue.cs" />
|
||||||
|
<Compile Include="BufferEntryCollection.cs" />
|
||||||
<Compile Include="Compilation\CompilationFailedException.cs" />
|
<Compile Include="Compilation\CompilationFailedException.cs" />
|
||||||
<Compile Include="Compilation\CompilationMessage.cs" />
|
<Compile Include="Compilation\CompilationMessage.cs" />
|
||||||
<Compile Include="Compilation\CompilationResult.cs" />
|
<Compile Include="Compilation\CompilationResult.cs" />
|
||||||
|
|
@ -30,6 +31,8 @@
|
||||||
<Compile Include="Compilation\RoslynCompilationService.cs" />
|
<Compile Include="Compilation\RoslynCompilationService.cs" />
|
||||||
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
<Compile Include="Extensions\DictionaryExtensions.cs" />
|
||||||
<Compile Include="IRazorPage.cs" />
|
<Compile Include="IRazorPage.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="RazorTextWriter.cs" />
|
||||||
<Compile Include="IViewStartProvider.cs" />
|
<Compile Include="IViewStartProvider.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ViewStartProvider.cs" />
|
<Compile Include="ViewStartProvider.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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Test")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Test")]
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string BodyContent { get; set; }
|
/// <inheritdoc />
|
||||||
|
public Action<TextWriter> RenderBodyDelegate { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
public Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
||||||
|
|
@ -243,14 +244,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
WritePositionTaggedLiteral(writer, value.Value, value.Position);
|
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"));
|
throw new InvalidOperationException(Resources.FormatRenderBodyCannotBeCalled("RenderBody"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderedBody = true;
|
_renderedBody = true;
|
||||||
return new HtmlString(BodyContent);
|
return new HelperResult(RenderBodyDelegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DefineSection(string name, HelperResult action)
|
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 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.
|
// If a body was defined, then RenderBody should have been called.
|
||||||
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
|
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="TextWriter"/> that represents individual write operations as a sequence of strings.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is primarily designed to avoid creating large in-memory strings.
|
||||||
|
/// Refer to https://aspnetwebstack.codeplex.com/workitem/585 for more details.
|
||||||
|
/// </remarks>
|
||||||
|
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; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Write(char value)
|
||||||
|
{
|
||||||
|
Buffer.Add(value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Write(string value)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
Buffer.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteAsync(char value)
|
||||||
|
{
|
||||||
|
Write(value);
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteAsync([NotNull] char[] buffer, int index, int count)
|
||||||
|
{
|
||||||
|
Write(buffer, index, count);
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteAsync(string value)
|
||||||
|
{
|
||||||
|
Write(value);
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void WriteLine()
|
||||||
|
{
|
||||||
|
Buffer.Add(Environment.NewLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void WriteLine(string value)
|
||||||
|
{
|
||||||
|
Write(value);
|
||||||
|
WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteLineAsync(char value)
|
||||||
|
{
|
||||||
|
WriteLine(value);
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteLineAsync(char[] value, int start, int offset)
|
||||||
|
{
|
||||||
|
WriteLine(value, start, offset);
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteLineAsync(string value)
|
||||||
|
{
|
||||||
|
WriteLine(value);
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task WriteLineAsync()
|
||||||
|
{
|
||||||
|
WriteLine();
|
||||||
|
return _completedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the content of the <see cref="RazorTextWriter"/> to the <see cref="TextWriter"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The writer to copy contents to.</param>
|
||||||
|
public void CopyTo(TextWriter writer)
|
||||||
|
{
|
||||||
|
var targetRazorTextWriter = writer as RazorTextWriter;
|
||||||
|
if (targetRazorTextWriter != null)
|
||||||
|
{
|
||||||
|
targetRazorTextWriter.Buffer.Add(Buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteList(writer, Buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the content of the <see cref="RazorTextWriter"/> to the specified <see cref="TextWriter"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The writer to copy contents to.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous copy operation.</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,6 @@
|
||||||
// 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;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
|
|
||||||
|
|
@ -48,8 +45,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
if (_executeViewHierarchy)
|
if (_executeViewHierarchy)
|
||||||
{
|
{
|
||||||
var bodyContent = await RenderPageAsync(_page, context, executeViewStart: true);
|
var bodyWriter = await RenderPageAsync(_page, context, executeViewStart: true);
|
||||||
await RenderLayoutAsync(context, bodyContent);
|
await RenderLayoutAsync(context, bodyWriter);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -57,17 +54,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> RenderPageAsync(IRazorPage page,
|
private async Task<RazorTextWriter> RenderPageAsync(IRazorPage page,
|
||||||
ViewContext context,
|
ViewContext context,
|
||||||
bool executeViewStart)
|
bool executeViewStart)
|
||||||
{
|
{
|
||||||
var contentBuilder = new StringBuilder(1024);
|
using (var bufferedWriter = new RazorTextWriter(context.Writer.Encoding))
|
||||||
using (var bodyWriter = new StringWriter(contentBuilder))
|
|
||||||
{
|
{
|
||||||
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
|
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
|
||||||
// and ViewComponents to reference it.
|
// and ViewComponents to reference it.
|
||||||
var oldWriter = context.Writer;
|
var oldWriter = context.Writer;
|
||||||
context.Writer = bodyWriter;
|
context.Writer = bufferedWriter;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (executeViewStart)
|
if (executeViewStart)
|
||||||
|
|
@ -75,15 +71,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
// Execute view starts using the same context + writer as the page to render.
|
// Execute view starts using the same context + writer as the page to render.
|
||||||
await RenderViewStartAsync(context);
|
await RenderViewStartAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
await RenderPageCoreAsync(page, context);
|
await RenderPageCoreAsync(page, context);
|
||||||
|
return bufferedWriter;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
context.Writer = oldWriter;
|
context.Writer = oldWriter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentBuilder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
|
private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
|
||||||
|
|
@ -107,7 +103,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderLayoutAsync(ViewContext context,
|
private async Task RenderLayoutAsync(ViewContext context,
|
||||||
string bodyContent)
|
RazorTextWriter bodyWriter)
|
||||||
{
|
{
|
||||||
// A layout page can specify another layout page. We'll need to continue
|
// A layout page can specify another layout page. We'll need to continue
|
||||||
// looking for layout pages until they're no longer specified.
|
// looking for layout pages until they're no longer specified.
|
||||||
|
|
@ -122,9 +118,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
|
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
|
||||||
layoutPage.BodyContent = bodyContent;
|
layoutPage.RenderBodyDelegate = bodyWriter.CopyTo;
|
||||||
|
bodyWriter = await RenderPageAsync(layoutPage, context, executeViewStart: false);
|
||||||
bodyContent = await RenderPageAsync(layoutPage, context, executeViewStart: false);
|
|
||||||
|
|
||||||
// Verify that RenderBody is called, or that RenderSection is called for all sections
|
// Verify that RenderBody is called, or that RenderSection is called for all sections
|
||||||
layoutPage.EnsureBodyAndSectionsWereRendered();
|
layoutPage.EnsureBodyAndSectionsWereRendered();
|
||||||
|
|
@ -132,7 +127,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
previousPage = layoutPage;
|
previousPage = layoutPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
await context.Writer.WriteAsync(bodyContent);
|
await bodyWriter.CopyToAsync(context.Writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,6 +104,7 @@
|
||||||
<Compile Include="Routing\AttributeRoutePrecedenceTests.cs" />
|
<Compile Include="Routing\AttributeRoutePrecedenceTests.cs" />
|
||||||
<Compile Include="Routing\AttributeRouteTemplateTests.cs" />
|
<Compile Include="Routing\AttributeRouteTemplateTests.cs" />
|
||||||
<Compile Include="Routing\AttributeRoutingTest.cs" />
|
<Compile Include="Routing\AttributeRoutingTest.cs" />
|
||||||
|
<Compile Include="StaticActionDiscoveryConventions.cs" />
|
||||||
<Compile Include="StaticControllerAssemblyProvider.cs" />
|
<Compile Include="StaticControllerAssemblyProvider.cs" />
|
||||||
<Compile Include="Routing\AttributeRouteTests.cs" />
|
<Compile Include="Routing\AttributeRouteTests.cs" />
|
||||||
<Compile Include="TestController.cs" />
|
<Compile Include="TestController.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<ArgumentOutOfRangeException>(
|
||||||
|
() => 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<ArgumentOutOfRangeException>(
|
||||||
|
() => collection.Add(new[] { 'a', 'b', 'c' }, 1, 3));
|
||||||
|
Assert.Equal("count", ex.ParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> 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<object> expected)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var collection = new BufferEntryCollection();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
collection.Add(value, index, count);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, collection.BufferEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,9 +21,11 @@
|
||||||
<Content Include="project.json" />
|
<Content Include="project.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="BufferEntryCollectionTest.cs" />
|
||||||
<Compile Include="MvcRazorCodeParserTest.cs" />
|
<Compile Include="MvcRazorCodeParserTest.cs" />
|
||||||
<Compile Include="RazorCompilationServiceTest.cs" />
|
<Compile Include="RazorCompilationServiceTest.cs" />
|
||||||
<Compile Include="RazorPageActivatorTest.cs" />
|
<Compile Include="RazorPageActivatorTest.cs" />
|
||||||
|
<Compile Include="RazorTextWriterTest.cs" />
|
||||||
<Compile Include="RazorViewEngineTest.cs" />
|
<Compile Include="RazorViewEngineTest.cs" />
|
||||||
<Compile Include="RazorPageTest.cs" />
|
<Compile Include="RazorPageTest.cs" />
|
||||||
<Compile Include="RazorViewTest.cs" />
|
<Compile Include="RazorViewTest.cs" />
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
{ "baz", new HelperResult(writer => { }) }
|
{ "baz", new HelperResult(writer => { }) }
|
||||||
};
|
};
|
||||||
page.BodyContent = "body-content";
|
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
{ "baz", new HelperResult(writer => { }) }
|
{ "baz", new HelperResult(writer => { }) }
|
||||||
};
|
};
|
||||||
page.BodyContent = "body-content";
|
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
|
|
@ -211,7 +211,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
var page = CreatePage(v =>
|
var page = CreatePage(v =>
|
||||||
{
|
{
|
||||||
});
|
});
|
||||||
page.BodyContent = "some content";
|
page.RenderBodyDelegate = CreateBodyAction("some content");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
|
|
@ -240,7 +240,7 @@ Layout end
|
||||||
v.WriteLiteral("Layout end" + Environment.NewLine);
|
v.WriteLiteral("Layout end" + Environment.NewLine);
|
||||||
|
|
||||||
});
|
});
|
||||||
page.BodyContent = "body content" + Environment.NewLine;
|
page.RenderBodyDelegate = CreateBodyAction("body content" + Environment.NewLine);
|
||||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|
@ -289,9 +289,14 @@ Layout end
|
||||||
new StringWriter());
|
new StringWriter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Action<TextWriter> CreateBodyAction(string value)
|
||||||
|
{
|
||||||
|
return writer => writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class TestableRazorPage : RazorPage
|
public abstract class TestableRazorPage : RazorPage
|
||||||
{
|
{
|
||||||
public HtmlString RenderBodyPublic()
|
public HelperResult RenderBodyPublic()
|
||||||
{
|
{
|
||||||
return base.RenderBody();
|
return base.RenderBody();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<object>(expected, writer.Buffer.BufferEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[ReplaceCulture]
|
||||||
|
public void WriteLine_WritesDataTypes_ToBuffer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var newLine = Environment.NewLine;
|
||||||
|
var expected = new List<object> { "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<char>(new char[] { 'a', 'b', 'c', 'd' }, 1, 3);
|
||||||
|
var input2 = new ArraySegment<char>(new char[] { 'e', 'f' }, 0, 2);
|
||||||
|
var input3 = new ArraySegment<char>(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<object>(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<object>(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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -143,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
await view.RenderAsync(viewContext);
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsType<StringWriter>(actual);
|
Assert.IsType<RazorTextWriter>(actual);
|
||||||
Assert.NotSame(original, actual);
|
Assert.NotSame(original, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue