Added PushWriter and PopWriter to RazorPageBase

- Removed WriteXYZTo methods
This commit is contained in:
Ajay Bhargav Baaskaran 2017-05-08 16:40:19 -07:00
parent 42a4e9a143
commit 5eabae55cb
3 changed files with 142 additions and 153 deletions

View File

@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// </summary> /// </summary>
public abstract class RazorPageBase : IRazorPage public abstract class RazorPageBase : IRazorPage
{ {
private readonly Stack<TextWriter> _textWriterStack = new Stack<TextWriter>();
private StringWriter _valueBuffer; private StringWriter _valueBuffer;
private ITagHelperFactory _tagHelperFactory; private ITagHelperFactory _tagHelperFactory;
private IViewBufferScope _bufferScope; private IViewBufferScope _bufferScope;
@ -275,6 +276,25 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return content; return content;
} }
// Internal for unit testing.
protected internal virtual void PushWriter(TextWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
_textWriterStack.Push(ViewContext.Writer);
ViewContext.Writer = writer;
}
// Internal for unit testing.
protected internal virtual TextWriter PopWriter()
{
ViewContext.Writer = _textWriterStack.Pop();
return ViewContext.Writer;
}
public virtual string Href(string contentPath) public virtual string Href(string contentPath)
{ {
if (contentPath == null) if (contentPath == null)
@ -335,63 +355,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// <param name="value">The <see cref="object"/> to write.</param> /// <param name="value">The <see cref="object"/> to write.</param>
public virtual void Write(object value) public virtual void Write(object value)
{ {
WriteTo(Output, value);
}
/// <summary>
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
/// <param name="value">The <see cref="object"/> to write.</param>
/// <remarks>
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
/// <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
/// <paramref name="writer"/>.
/// </remarks>
public virtual void WriteTo(TextWriter writer, object value)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
WriteTo(writer, HtmlEncoder, value);
}
/// <summary>
/// Writes the specified <paramref name="value"/> with HTML encoding to given <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
/// <param name="encoder">
/// The <see cref="System.Text.Encodings.Web.HtmlEncoder"/> to use when encoding <paramref name="value"/>.
/// </param>
/// <param name="value">The <see cref="object"/> to write.</param>
/// <remarks>
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
/// <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>.
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
/// <paramref name="writer"/>.
/// </remarks>
public static void WriteTo(TextWriter writer, HtmlEncoder encoder, object value)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (encoder == null)
{
throw new ArgumentNullException(nameof(encoder));
}
if (value == null || value == HtmlString.Empty) if (value == null || value == HtmlString.Empty)
{ {
return; return;
} }
var htmlContent = value as IHtmlContent; var writer = Output;
if (htmlContent != null) var encoder = HtmlEncoder;
if (value is IHtmlContent htmlContent)
{ {
var bufferedWriter = writer as ViewBufferTextWriter; var bufferedWriter = writer as ViewBufferTextWriter;
if (bufferedWriter == null || !bufferedWriter.IsBuffering) if (bufferedWriter == null || !bufferedWriter.IsBuffering)
@ -400,8 +371,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
} }
else else
{ {
var htmlContentContainer = value as IHtmlContentContainer; if (value is IHtmlContentContainer htmlContentContainer)
if (htmlContentContainer != null)
{ {
// This is likely another ViewBuffer. // This is likely another ViewBuffer.
htmlContentContainer.MoveTo(bufferedWriter.Buffer); htmlContentContainer.MoveTo(bufferedWriter.Buffer);
@ -417,26 +387,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return; return;
} }
WriteTo(writer, encoder, value.ToString()); Write(value.ToString());
} }
/// <summary> /// <summary>
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>. /// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
/// </summary> /// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
/// <param name="value">The <see cref="string"/> to write.</param> /// <param name="value">The <see cref="string"/> to write.</param>
public virtual void WriteTo(TextWriter writer, string value) public virtual void Write(string value)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
WriteTo(writer, HtmlEncoder, value);
}
private static void WriteTo(TextWriter writer, HtmlEncoder encoder, string value)
{ {
var writer = Output;
var encoder = HtmlEncoder;
if (!string.IsNullOrEmpty(value)) if (!string.IsNullOrEmpty(value))
{ {
// Perf: Encode right away instead of writing it character-by-character. // Perf: Encode right away instead of writing it character-by-character.
@ -452,42 +413,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// <param name="value">The <see cref="object"/> to write.</param> /// <param name="value">The <see cref="object"/> to write.</param>
public virtual void WriteLiteral(object value) public virtual void WriteLiteral(object value)
{ {
WriteLiteralTo(Output, value); WriteLiteral(value.ToString());
}
/// <summary>
/// Writes the specified <paramref name="value"/> without HTML encoding to the <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
/// <param name="value">The <see cref="object"/> to write.</param>
public virtual void WriteLiteralTo(TextWriter writer, object value)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (value != null)
{
WriteLiteralTo(writer, value.ToString());
}
} }
/// <summary> /// <summary>
/// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Output"/>. /// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Output"/>.
/// </summary> /// </summary>
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
/// <param name="value">The <see cref="string"/> to write.</param> /// <param name="value">The <see cref="string"/> to write.</param>
public virtual void WriteLiteralTo(TextWriter writer, string value) public virtual void WriteLiteral(string value)
{ {
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (!string.IsNullOrEmpty(value)) if (!string.IsNullOrEmpty(value))
{ {
writer.Write(value); Output.Write(value);
} }
} }
@ -499,23 +436,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
int suffixOffset, int suffixOffset,
int attributeValuesCount) int attributeValuesCount)
{ {
BeginWriteAttributeTo(Output, name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount);
}
public virtual void BeginWriteAttributeTo(
TextWriter writer,
string name,
string prefix,
int prefixOffset,
string suffix,
int suffixOffset,
int attributeValuesCount)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (prefix == null) if (prefix == null)
{ {
throw new ArgumentNullException(nameof(prefix)); throw new ArgumentNullException(nameof(prefix));
@ -532,7 +452,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// null or false. Consequently defer the prefix generation until we encounter the attribute value. // null or false. Consequently defer the prefix generation until we encounter the attribute value.
if (attributeValuesCount != 1) if (attributeValuesCount != 1)
{ {
WritePositionTaggedLiteral(writer, prefix, prefixOffset); WritePositionTaggedLiteral(prefix, prefixOffset);
} }
} }
@ -543,18 +463,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
int valueOffset, int valueOffset,
int valueLength, int valueLength,
bool isLiteral) bool isLiteral)
{
WriteAttributeValueTo(Output, prefix, prefixOffset, value, valueOffset, valueLength, isLiteral);
}
public void WriteAttributeValueTo(
TextWriter writer,
string prefix,
int prefixOffset,
object value,
int valueOffset,
int valueLength,
bool isLiteral)
{ {
if (_attributeInfo.AttributeValuesCount == 1) if (_attributeInfo.AttributeValuesCount == 1)
{ {
@ -566,7 +474,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
} }
// We are not omitting the attribute. Write the prefix. // We are not omitting the attribute. Write the prefix.
WritePositionTaggedLiteral(writer, _attributeInfo.Prefix, _attributeInfo.PrefixOffset); WritePositionTaggedLiteral(_attributeInfo.Prefix, _attributeInfo.PrefixOffset);
if (IsBoolTrueWithEmptyPrefixValue(prefix, value)) if (IsBoolTrueWithEmptyPrefixValue(prefix, value))
{ {
@ -582,12 +490,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor
{ {
if (!string.IsNullOrEmpty(prefix)) if (!string.IsNullOrEmpty(prefix))
{ {
WritePositionTaggedLiteral(writer, prefix, prefixOffset); WritePositionTaggedLiteral(prefix, prefixOffset);
} }
BeginContext(valueOffset, valueLength, isLiteral); BeginContext(valueOffset, valueLength, isLiteral);
WriteUnprefixedAttributeValueTo(writer, value, isLiteral); WriteUnprefixedAttributeValue(value, isLiteral);
EndContext(); EndContext();
} }
@ -595,19 +503,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public virtual void EndWriteAttribute() public virtual void EndWriteAttribute()
{ {
EndWriteAttributeTo(Output);
}
public virtual void EndWriteAttributeTo(TextWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (!_attributeInfo.Suppressed) if (!_attributeInfo.Suppressed)
{ {
WritePositionTaggedLiteral(writer, _attributeInfo.Suffix, _attributeInfo.SuffixOffset); WritePositionTaggedLiteral(_attributeInfo.Suffix, _attributeInfo.SuffixOffset);
} }
} }
@ -668,12 +566,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor
_valueBuffer = new StringWriter(); _valueBuffer = new StringWriter();
} }
PushWriter(_valueBuffer);
if (!string.IsNullOrEmpty(prefix)) if (!string.IsNullOrEmpty(prefix))
{ {
WriteLiteralTo(_valueBuffer, prefix); WriteLiteral(prefix);
} }
WriteUnprefixedAttributeValueTo(_valueBuffer, value, isLiteral); WriteUnprefixedAttributeValue(value, isLiteral);
PopWriter();
} }
} }
@ -738,33 +638,33 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return HtmlString.Empty; return HtmlString.Empty;
} }
private void WriteUnprefixedAttributeValueTo(TextWriter writer, object value, bool isLiteral) private void WriteUnprefixedAttributeValue(object value, bool isLiteral)
{ {
var stringValue = value as string; var stringValue = value as string;
// The extra branching here is to ensure that we call the Write*To(string) overload where possible. // The extra branching here is to ensure that we call the Write*To(string) overload where possible.
if (isLiteral && stringValue != null) if (isLiteral && stringValue != null)
{ {
WriteLiteralTo(writer, stringValue); WriteLiteral(stringValue);
} }
else if (isLiteral) else if (isLiteral)
{ {
WriteLiteralTo(writer, value); WriteLiteral(value);
} }
else if (stringValue != null) else if (stringValue != null)
{ {
WriteTo(writer, stringValue); Write(stringValue);
} }
else else
{ {
WriteTo(writer, value); Write(value);
} }
} }
private void WritePositionTaggedLiteral(TextWriter writer, string value, int position) private void WritePositionTaggedLiteral(string value, int position)
{ {
BeginContext(position, value.Length, isLiteral: true); BeginContext(position, value.Length, isLiteral: true);
WriteLiteralTo(writer, value); WriteLiteral(value);
EndContext(); EndContext();
} }

View File

@ -1394,7 +1394,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
[Theory] [Theory]
[MemberData(nameof(WriteAttributeData))] [MemberData(nameof(WriteAttributeData))]
public void WriteAttributeTo_WritesAsExpected( public void WriteAttribute_UsesSpecifiedWriter_WritesAsExpected(
Tuple<string, int, object, int, bool>[] attributeValues, Tuple<string, int, object, int, bool>[] attributeValues,
string expectedOutput) string expectedOutput)
{ {
@ -1406,11 +1406,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var suffix = string.Empty; var suffix = string.Empty;
// Act // Act
page.BeginWriteAttributeTo(writer, "someattr", prefix, 0, suffix, 0, attributeValues.Length); page.PushWriter(writer);
page.BeginWriteAttribute("someattr", prefix, 0, suffix, 0, attributeValues.Length);
foreach (var value in attributeValues) foreach (var value in attributeValues)
{ {
page.WriteAttributeValueTo( page.WriteAttributeValue(
writer,
value.Item1, value.Item1,
value.Item2, value.Item2,
value.Item3, value.Item3,
@ -1418,12 +1418,89 @@ namespace Microsoft.AspNetCore.Mvc.Razor
value.Item3?.ToString().Length ?? 0, value.Item3?.ToString().Length ?? 0,
value.Item5); value.Item5);
} }
page.EndWriteAttributeTo(writer); page.EndWriteAttribute();
page.PopWriter();
// Assert // Assert
Assert.Equal(expectedOutput, writer.ToString()); Assert.Equal(expectedOutput, writer.ToString());
} }
[Fact]
public void PushWriter_SetsUnderlyingWriter()
{
// Arrange
var page = CreatePage(p => { });
var writer = new StringWriter();
// Act
page.PushWriter(writer);
// Assert
Assert.Same(writer, page.ViewContext.Writer);
}
[Fact]
public void PopWriter_ResetsUnderlyingWriter()
{
// Arrange
var page = CreatePage(p => { });
var defaultWriter = new StringWriter();
page.ViewContext.Writer = defaultWriter;
var writer = new StringWriter();
// Act 1
page.PushWriter(writer);
// Assert 1
Assert.Same(writer, page.ViewContext.Writer);
// Act 2
var poppedWriter = page.PopWriter();
// Assert 2
Assert.Same(defaultWriter, poppedWriter);
Assert.Same(defaultWriter, page.ViewContext.Writer);
}
[Fact]
public void WriteLiteral_BuffersResultToPushedWriter()
{
// Arrange
var page = CreatePage(p => { });
var defaultWriter = new StringWriter();
page.ViewContext.Writer = defaultWriter;
var bufferWriter = new StringWriter();
// Act
page.WriteLiteral("Not");
page.PushWriter(bufferWriter);
page.WriteLiteral("This should be buffered");
page.PopWriter();
page.WriteLiteral(" buffered");
// Assert
Assert.Equal("Not buffered", defaultWriter.ToString());
Assert.Equal("This should be buffered", bufferWriter.ToString());
}
[Fact]
public void Write_StringValue_UsesSpecifiedWriter_EncodesValue()
{
// Arrange
var page = CreatePage(p => { });
var bufferWriter = new StringWriter();
// Act
page.PushWriter(bufferWriter);
page.Write("This should be encoded");
page.PopWriter();
// Assert
Assert.Equal("HtmlEncode[[This should be encoded]]", bufferWriter.ToString());
}
[Fact] [Fact]
public async Task Write_WithHtmlString_WritesValueWithoutEncoding() public async Task Write_WithHtmlString_WritesValueWithoutEncoding()
{ {

View File

@ -13,10 +13,22 @@ namespace RazorWebSite
base.WriteLiteral(value); base.WriteLiteral(value);
} }
public override void WriteLiteral(string value)
{
base.WriteLiteral("WriteLiteral says:");
base.WriteLiteral(value);
}
public override void Write(object value) public override void Write(object value)
{ {
base.WriteLiteral("Write says:"); base.WriteLiteral("Write says:");
base.Write(value); base.Write(value);
} }
public override void Write(string value)
{
base.WriteLiteral("Write says:");
base.Write(value);
}
} }
} }