[Perf] Avoid ViewBuffers for writing bound TagHelper attribute values. Fixes aspnet/Razor#717
This commit is contained in:
parent
f54a964815
commit
b96851ec20
|
|
@ -106,6 +106,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
|
FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment",
|
||||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
|
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
|
||||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
|
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
|
||||||
|
BeginWriteTagHelperAttributeMethodName = "BeginWriteTagHelperAttribute",
|
||||||
|
EndWriteTagHelperAttributeMethodName = "EndWriteTagHelperAttribute",
|
||||||
|
|
||||||
// Can't use nameof because IHtmlHelper is (also) not accessible here.
|
// Can't use nameof because IHtmlHelper is (also) not accessible here.
|
||||||
MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw",
|
MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw",
|
||||||
|
|
|
||||||
|
|
@ -478,6 +478,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewLocationFormatsIsRequired"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("ViewLocationFormatsIsRequired"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nesting of TagHelper attribute writing scopes is not supported.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RazorPage_NestingAttributeWritingScopesNotSupported
|
||||||
|
{
|
||||||
|
get { return GetString("RazorPage_NestingAttributeWritingScopesNotSupported"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nesting of TagHelper attribute writing scopes is not supported.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRazorPage_NestingAttributeWritingScopesNotSupported()
|
||||||
|
{
|
||||||
|
return GetString("RazorPage_NestingAttributeWritingScopesNotSupported");
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
private IViewBufferScope _bufferScope;
|
private IViewBufferScope _bufferScope;
|
||||||
private bool _ignoreBody;
|
private bool _ignoreBody;
|
||||||
private HashSet<string> _ignoredSections;
|
private HashSet<string> _ignoredSections;
|
||||||
|
private TextWriter _pageWriter;
|
||||||
|
|
||||||
public RazorPage()
|
public RazorPage()
|
||||||
{
|
{
|
||||||
|
|
@ -231,6 +232,61 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return tagHelperContent;
|
return tagHelperContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts a new scope for writing <see cref="ITagHelper"/> attribute values.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// All writes to the <see cref="Output"/> or <see cref="ViewContext.Writer"/> after calling this method will
|
||||||
|
/// be buffered until <see cref="EndWriteTagHelperAttribute"/> is called.
|
||||||
|
/// The content will be buffered using a shared <see cref="StringWriter"/> within this <see cref="RazorPage"/>
|
||||||
|
/// Nesting of <see cref="BeginWriteTagHelperAttribute"/> and <see cref="EndWriteTagHelperAttribute"/> method calls
|
||||||
|
/// is not supported.
|
||||||
|
/// </remarks>
|
||||||
|
public void BeginWriteTagHelperAttribute()
|
||||||
|
{
|
||||||
|
if (_pageWriter != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.RazorPage_NestingAttributeWritingScopesNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pageWriter = ViewContext.Writer;
|
||||||
|
|
||||||
|
if (_valueBuffer == null)
|
||||||
|
{
|
||||||
|
_valueBuffer = new StringWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to replace the ViewContext's Writer to ensure that all content (including content written
|
||||||
|
// from HTML helpers) is redirected.
|
||||||
|
ViewContext.Writer = _valueBuffer;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the current writing scope that was started by calling <see cref="BeginWriteTagHelperAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The content buffered by the shared <see cref="StringWriter"/> of this <see cref="RazorPage"/>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method assumes that there will be no nesting of <see cref="BeginWriteTagHelperAttribute"/>
|
||||||
|
/// and <see cref="EndWriteTagHelperAttribute"/> method calls.
|
||||||
|
/// </remarks>
|
||||||
|
public string EndWriteTagHelperAttribute()
|
||||||
|
{
|
||||||
|
if (_pageWriter == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.RazorPage_ThereIsNoActiveWritingScopeToEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = _valueBuffer.ToString();
|
||||||
|
_valueBuffer.GetStringBuilder().Clear();
|
||||||
|
|
||||||
|
// Restore previous writer.
|
||||||
|
ViewContext.Writer = _pageWriter;
|
||||||
|
_pageWriter = null;
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -206,4 +206,7 @@
|
||||||
<data name="ViewLocationFormatsIsRequired" xml:space="preserve">
|
<data name="ViewLocationFormatsIsRequired" xml:space="preserve">
|
||||||
<value>'{0}' cannot be empty. These locations are required to locate a view for rendering.</value>
|
<value>'{0}' cannot be empty. These locations are required to locate a view for rendering.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RazorPage_NestingAttributeWritingScopesNotSupported" xml:space="preserve">
|
||||||
|
<value>Nesting of TagHelper attribute writing scopes is not supported.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -13,7 +13,7 @@ namespace AspNetCore
|
||||||
{
|
{
|
||||||
#line hidden
|
#line hidden
|
||||||
#pragma warning disable 0414
|
#pragma warning disable 0414
|
||||||
private global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent __tagHelperStringValueBuffer = null;
|
private string __tagHelperStringValueBuffer = null;
|
||||||
#pragma warning restore 0414
|
#pragma warning restore 0414
|
||||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext = null;
|
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext = null;
|
||||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = null;
|
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = null;
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,64 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EndWriteTagHelperAttribute_RestoresPageWriter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.BeginWriteTagHelperAttribute();
|
||||||
|
v.Write("Hello World!");
|
||||||
|
v.EndWriteTagHelperAttribute();
|
||||||
|
});
|
||||||
|
var originalWriter = page.Output;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(originalWriter);
|
||||||
|
Assert.Same(originalWriter, page.Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EndWriteTagHelperAttribute_ReturnsAppropriateContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var viewContext = CreateViewContext();
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.HtmlEncoder = new HtmlTestEncoder();
|
||||||
|
v.BeginWriteTagHelperAttribute();
|
||||||
|
v.Write("Hello World!");
|
||||||
|
var returnValue = v.EndWriteTagHelperAttribute();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var content = Assert.IsType<string>(returnValue);
|
||||||
|
Assert.Equal("HtmlEncode[[Hello World!]]", content);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await page.ExecuteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BeginWriteTagHelperAttribute_NestingWritingScopesThrows()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var viewContext = CreateViewContext();
|
||||||
|
var page = CreatePage(v =>
|
||||||
|
{
|
||||||
|
v.BeginWriteTagHelperAttribute();
|
||||||
|
v.BeginWriteTagHelperAttribute();
|
||||||
|
v.Write("Hello World!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => page.ExecuteAsync());
|
||||||
|
Assert.Equal("Nesting of TagHelper attribute writing scopes is not supported.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
// This is an integration test for ensuring that ViewBuffer segments used by
|
// This is an integration test for ensuring that ViewBuffer segments used by
|
||||||
// TagHelpers can be merged back into the 'main' segment where possible.
|
// TagHelpers can be merged back into the 'main' segment where possible.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue