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:
parent
2e7e1f9c85
commit
18909506e1
|
|
@ -283,14 +283,14 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||||
|
|
||||||
if (!isTagNameNullOrWhitespace)
|
if (!isTagNameNullOrWhitespace)
|
||||||
{
|
{
|
||||||
writer.Write('<');
|
writer.Write("<");
|
||||||
writer.Write(TagName);
|
writer.Write(TagName);
|
||||||
|
|
||||||
// Perf: Avoid allocating enumerator
|
// Perf: Avoid allocating enumerator
|
||||||
for (var i = 0; i < Attributes.Count; i++)
|
for (var i = 0; i < Attributes.Count; i++)
|
||||||
{
|
{
|
||||||
var attribute = Attributes[i];
|
var attribute = Attributes[i];
|
||||||
writer.Write(' ');
|
writer.Write(" ");
|
||||||
writer.Write(attribute.Name);
|
writer.Write(attribute.Name);
|
||||||
|
|
||||||
if (attribute.Minimized)
|
if (attribute.Minimized)
|
||||||
|
|
@ -309,10 +309,9 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||||
using (var stringWriter = new StringWriter())
|
using (var stringWriter = new StringWriter())
|
||||||
{
|
{
|
||||||
htmlContent.WriteTo(stringWriter, encoder);
|
htmlContent.WriteTo(stringWriter, encoder);
|
||||||
|
stringWriter.GetStringBuilder().Replace("\"", """);
|
||||||
|
|
||||||
var stringValue = stringWriter.ToString();
|
var stringValue = stringWriter.ToString();
|
||||||
stringValue = stringValue.Replace("\"", """);
|
|
||||||
|
|
||||||
writer.Write(stringValue);
|
writer.Write(stringValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -321,7 +320,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||||
encoder.Encode(writer, value.ToString());
|
encoder.Encode(writer, value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Write('"');
|
writer.Write("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TagMode == TagMode.SelfClosing)
|
if (TagMode == TagMode.SelfClosing)
|
||||||
|
|
@ -329,7 +328,7 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||||
writer.Write(" /");
|
writer.Write(" /");
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Write('>');
|
writer.Write(">");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTagNameNullOrWhitespace || TagMode == TagMode.StartTagAndEndTag)
|
if (isTagNameNullOrWhitespace || TagMode == TagMode.StartTagAndEndTag)
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
||||||
using Microsoft.Extensions.WebEncoders.Testing;
|
using Microsoft.Extensions.WebEncoders.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -990,6 +992,34 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||||
Assert.Equal(expected, writer.ToString(), StringComparer.Ordinal);
|
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(
|
private static TagHelperOutput GetTagHelperOutput(
|
||||||
string tagName,
|
string tagName,
|
||||||
TagHelperAttributeList attributes,
|
TagHelperAttributeList attributes,
|
||||||
|
|
@ -1036,5 +1066,29 @@ namespace Microsoft.AspNetCore.Razor.TagHelpers
|
||||||
|
|
||||||
return output;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue