Flatten TagHelperOutput when writing

This change 'flattens' a TagHelperOutput when writing it out to an
HtmlTextWriter. This is a beneficial perf change because more often than
not the thing being written to is a ViewBuffer in Razor, which uses pooled
memory. This allows us to 'join' islands of pooled ViewBuffer memory back
into the main buffer instead of keeping them wrapped up in a
TagHelperOutput or TagHelperContent.
This commit is contained in:
Ryan Nowak 2016-01-21 17:29:35 -08:00
parent 2e7e1f9c85
commit 18909506e1
2 changed files with 59 additions and 6 deletions

View File

@ -283,14 +283,14 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
if (!isTagNameNullOrWhitespace)
{
writer.Write('<');
writer.Write("<");
writer.Write(TagName);
// Perf: Avoid allocating enumerator
for (var i = 0; i < Attributes.Count; i++)
{
var attribute = Attributes[i];
writer.Write(' ');
writer.Write(" ");
writer.Write(attribute.Name);
if (attribute.Minimized)
@ -309,10 +309,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
using (var stringWriter = new StringWriter())
{
htmlContent.WriteTo(stringWriter, encoder);
stringWriter.GetStringBuilder().Replace("\"", "&quot;");
var stringValue = stringWriter.ToString();
stringValue = stringValue.Replace("\"", "&quot;");
writer.Write(stringValue);
}
}
@ -321,7 +320,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
encoder.Encode(writer, value.ToString());
}
writer.Write('"');
writer.Write("\"");
}
if (TagMode == TagMode.SelfClosing)
@ -329,7 +328,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
writer.Write(" /");
}
writer.Write('>');
writer.Write(">");
}
if (isTagNameNullOrWhitespace || TagMode == TagMode.StartTagAndEndTag)

View File

@ -4,8 +4,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Microsoft.Extensions.WebEncoders.Testing;
using Xunit;
@ -990,6 +992,34 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
Assert.Equal(expected, writer.ToString(), StringComparer.Ordinal);
}
// This tests a separate code path that's used by THO when the writer is an HtmlTextWriter.
// The output should be the same, but we do some specific perf optimizations on this path.
[Theory]
[MemberData(nameof(WriteTagHelper_InputData))]
public void WriteTo_WritesFormattedTagHelper_HtmlTextWriter(TagHelperOutput output, string expected)
{
// Arrange
var inner = new StringWriter();
var testEncoder = new HtmlTestEncoder();
var writer = new MockHtmlTextWriter(inner, testEncoder);
var tagHelperExecutionContext = new TagHelperExecutionContext(
tagName: output.TagName,
tagMode: output.TagMode,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () => Task.FromResult(result: true),
startTagHelperWritingScope: _ => { },
endTagHelperWritingScope: () => new DefaultTagHelperContent());
tagHelperExecutionContext.Output = output;
// Act
output.WriteTo(writer, testEncoder);
// Assert
Assert.Equal(expected, inner.ToString(), StringComparer.Ordinal);
}
private static TagHelperOutput GetTagHelperOutput(
string tagName,
TagHelperAttributeList attributes,
@ -1036,5 +1066,29 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
return output;
}
private class MockHtmlTextWriter : HtmlTextWriter
{
private readonly HtmlEncoder _encoder;
private readonly TextWriter _inner;
public MockHtmlTextWriter(TextWriter inner, HtmlEncoder encoder)
{
_inner = inner;
_encoder = encoder;
}
public override Encoding Encoding => _inner.Encoding;
public override void Write(IHtmlContent value)
{
value.WriteTo(_inner, _encoder);
}
public override void Write(char value)
{
_inner.Write(value);
}
}
}
}