[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",
|
||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope",
|
||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope",
|
||||
BeginWriteTagHelperAttributeMethodName = "BeginWriteTagHelperAttribute",
|
||||
EndWriteTagHelperAttributeMethodName = "EndWriteTagHelperAttribute",
|
||||
|
||||
// Can't use nameof because IHtmlHelper is (also) not accessible here.
|
||||
MarkAsHtmlEncodedMethodName = HtmlHelperPropertyName + ".Raw",
|
||||
|
|
|
|||
|
|
@ -478,6 +478,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
private IViewBufferScope _bufferScope;
|
||||
private bool _ignoreBody;
|
||||
private HashSet<string> _ignoredSections;
|
||||
private TextWriter _pageWriter;
|
||||
|
||||
public RazorPage()
|
||||
{
|
||||
|
|
@ -231,6 +232,61 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
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>
|
||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -206,4 +206,7 @@
|
|||
<data name="ViewLocationFormatsIsRequired" xml:space="preserve">
|
||||
<value>'{0}' cannot be empty. These locations are required to locate a view for rendering.</value>
|
||||
</data>
|
||||
<data name="RazorPage_NestingAttributeWritingScopesNotSupported" xml:space="preserve">
|
||||
<value>Nesting of TagHelper attribute writing scopes is not supported.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -13,7 +13,7 @@ namespace AspNetCore
|
|||
{
|
||||
#line hidden
|
||||
#pragma warning disable 0414
|
||||
private global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent __tagHelperStringValueBuffer = null;
|
||||
private string __tagHelperStringValueBuffer = null;
|
||||
#pragma warning restore 0414
|
||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext = null;
|
||||
private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = null;
|
||||
|
|
|
|||
|
|
@ -225,6 +225,64 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
|||
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
|
||||
// TagHelpers can be merged back into the 'main' segment where possible.
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in New Issue