Make `TagHelperOutput` an `IHtmlContent`.
- This allows users to write `TagHelperOutput` directly to an `IHtmlContent` accepting `TextWriter`. - This also enables us to inspect backing fields for all of the various contents to lazily initialize them. #358
This commit is contained in:
parent
36c744ff29
commit
bdf869c3d5
|
|
@ -223,15 +223,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
AllAttributes.Add(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the child content asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> which on completion executes all child content.</returns>
|
||||
public Task ExecuteChildContentAsync()
|
||||
{
|
||||
return _executeChildContentAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute and retrieve the rendered child content asynchronously.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Html.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used to represent the output of an <see cref="ITagHelper"/>.
|
||||
/// </summary>
|
||||
public class TagHelperOutput
|
||||
public class TagHelperOutput : IHtmlContent
|
||||
{
|
||||
private readonly Func<bool, Task<TagHelperContent>> _getChildContentAsync;
|
||||
|
||||
|
|
@ -151,5 +154,85 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
{
|
||||
return _getChildContentAsync(useCachedResult);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
PreElement.WriteTo(writer, encoder);
|
||||
|
||||
var isTagNameNullOrWhitespace = string.IsNullOrWhiteSpace(TagName);
|
||||
|
||||
if (!isTagNameNullOrWhitespace)
|
||||
{
|
||||
writer.Write('<');
|
||||
writer.Write(TagName);
|
||||
|
||||
foreach (var attribute in Attributes)
|
||||
{
|
||||
writer.Write(' ');
|
||||
writer.Write(attribute.Name);
|
||||
|
||||
if (attribute.Minimized)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.Write("=\"");
|
||||
var value = attribute.Value;
|
||||
var htmlContent = value as IHtmlContent;
|
||||
if (htmlContent != null)
|
||||
{
|
||||
// There's no way of tracking the attribute value quotations in the Razor source. Therefore, we
|
||||
// must escape any IHtmlContent double quote values in the case that a user wrote:
|
||||
// <p name='A " is valid in single quotes'></p>
|
||||
using (var stringWriter = new StringWriter())
|
||||
{
|
||||
htmlContent.WriteTo(stringWriter, encoder);
|
||||
|
||||
var stringValue = stringWriter.ToString();
|
||||
stringValue = stringValue.Replace("\"", """);
|
||||
|
||||
writer.Write(stringValue);
|
||||
}
|
||||
}
|
||||
else if (value != null)
|
||||
{
|
||||
encoder.Encode(writer, value.ToString());
|
||||
}
|
||||
|
||||
writer.Write('"');
|
||||
}
|
||||
|
||||
if (TagMode == TagMode.SelfClosing)
|
||||
{
|
||||
writer.Write(" /");
|
||||
}
|
||||
|
||||
writer.Write('>');
|
||||
}
|
||||
|
||||
if (isTagNameNullOrWhitespace || TagMode == TagMode.StartTagAndEndTag)
|
||||
{
|
||||
PreContent.WriteTo(writer, encoder);
|
||||
|
||||
Content.WriteTo(writer, encoder);
|
||||
|
||||
PostContent.WriteTo(writer, encoder);
|
||||
}
|
||||
|
||||
if (!isTagNameNullOrWhitespace && TagMode == TagMode.StartTagAndEndTag)
|
||||
{
|
||||
writer.Write("</");
|
||||
writer.Write(TagName);
|
||||
writer.Write(">");
|
||||
}
|
||||
|
||||
PostElement.WriteTo(writer, encoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
if (!_designTimeMode)
|
||||
{
|
||||
RenderRunTagHelpers();
|
||||
RenderWriteTagHelperMethodCall(chunk);
|
||||
RenderTagHelperOutput(chunk);
|
||||
RenderEndTagHelpersScope();
|
||||
}
|
||||
}
|
||||
|
|
@ -533,26 +533,50 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
_tagHelperContext.ScopeManagerEndMethodName);
|
||||
}
|
||||
|
||||
private void RenderWriteTagHelperMethodCall(TagHelperChunk chunk)
|
||||
private void RenderTagHelperOutput(TagHelperChunk chunk)
|
||||
{
|
||||
var tagHelperOutputAccessor =
|
||||
$"{ExecutionContextVariableName}.{_tagHelperContext.ExecutionContextOutputPropertyName}";
|
||||
|
||||
if (ContainsChildContent(chunk.Children))
|
||||
{
|
||||
_writer
|
||||
.Write("if (!")
|
||||
.Write(tagHelperOutputAccessor)
|
||||
.Write(".")
|
||||
.Write(_tagHelperContext.TagHelperOutputIsContentModifiedPropertyName)
|
||||
.WriteLine(")");
|
||||
|
||||
using (_writer.BuildScope())
|
||||
{
|
||||
_writer
|
||||
.Write(tagHelperOutputAccessor)
|
||||
.Write(".")
|
||||
.WriteStartAssignment(_tagHelperContext.TagHelperOutputContentPropertyName)
|
||||
.Write("await ")
|
||||
.WriteInstanceMethodInvocation(
|
||||
tagHelperOutputAccessor,
|
||||
_tagHelperContext.TagHelperOutputGetChildContentAsyncMethodName);
|
||||
}
|
||||
}
|
||||
|
||||
_writer
|
||||
.WriteStartInstrumentationContext(_context, chunk.Association, isLiteral: false)
|
||||
.Write("await ");
|
||||
.WriteStartInstrumentationContext(_context, chunk.Association, isLiteral: false);
|
||||
|
||||
if (!string.IsNullOrEmpty(_context.TargetWriterName))
|
||||
{
|
||||
_writer
|
||||
.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperToAsyncMethodName)
|
||||
.WriteStartMethodInvocation(_context.Host.GeneratedClassContext.WriteToMethodName)
|
||||
.Write(_context.TargetWriterName)
|
||||
.WriteParameterSeparator();
|
||||
}
|
||||
else
|
||||
{
|
||||
_writer.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperAsyncMethodName);
|
||||
_writer.WriteStartMethodInvocation(_context.Host.GeneratedClassContext.WriteMethodName);
|
||||
}
|
||||
|
||||
_writer
|
||||
.Write(ExecutionContextVariableName)
|
||||
.Write(tagHelperOutputAccessor)
|
||||
.WriteEndMethodInvocation()
|
||||
.WriteEndInstrumentationContext(_context);
|
||||
}
|
||||
|
|
@ -688,6 +712,24 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
}
|
||||
}
|
||||
|
||||
private static bool ContainsChildContent(IList<Chunk> children)
|
||||
{
|
||||
// False will be returned if there are no children or if there are only non-TagHelper ParentChunk leaf
|
||||
// nodes.
|
||||
foreach (var child in children)
|
||||
{
|
||||
var parentChunk = child as ParentChunk;
|
||||
if (parentChunk == null ||
|
||||
parentChunk is TagHelperChunk ||
|
||||
ContainsChildContent(parentChunk.Children))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsDynamicAttributeValue(Chunk attributeValueChunk)
|
||||
{
|
||||
var parentChunk = attributeValueChunk as ParentChunk;
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
ScopeManagerTypeName = "Microsoft.AspNet.Razor.Runtime.TagHelperScopeManager";
|
||||
ExecutionContextTypeName = "Microsoft.AspNet.Razor.Runtime.TagHelperExecutionContext";
|
||||
TagHelperContentTypeName = "Microsoft.AspNet.Razor.TagHelperContent";
|
||||
WriteTagHelperAsyncMethodName = "WriteTagHelperAsync";
|
||||
WriteTagHelperToAsyncMethodName = "WriteTagHelperToAsync";
|
||||
TagHelperContentGetContentMethodName = "GetContent";
|
||||
HtmlEncoderPropertyName = "HtmlEncoder";
|
||||
TagHelperOutputIsContentModifiedPropertyName = "IsContentModified";
|
||||
TagHelperOutputContentPropertyName = "Content";
|
||||
TagHelperOutputGetChildContentAsyncMethodName = "GetChildContentAsync";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -124,7 +125,7 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
public string ExecutionContextAddMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The property accessor for the tag helper's output.
|
||||
/// The property name for the tag helper's output.
|
||||
/// </summary>
|
||||
public string ExecutionContextOutputPropertyName { get; set; }
|
||||
|
||||
|
|
@ -185,17 +186,6 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
/// </remarks>
|
||||
public string TagHelperContentTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to write <see cref="ExecutionContextTypeName"/>.
|
||||
/// </summary>
|
||||
public string WriteTagHelperAsyncMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to write <see cref="ExecutionContextTypeName"/> to a specified
|
||||
/// <see cref="System.IO.TextWriter"/>.
|
||||
/// </summary>
|
||||
public string WriteTagHelperToAsyncMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the property containing the <c>HtmlEncoder</c>.
|
||||
/// </summary>
|
||||
|
|
@ -205,5 +195,21 @@ namespace Microsoft.AspNet.Razor.CodeGenerators
|
|||
/// The name of the method used to convert a <c>TagHelperContent</c> into a <see cref="string"/>.
|
||||
/// </summary>
|
||||
public string TagHelperContentGetContentMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the property used to indicate the tag helper's content has been modified.
|
||||
/// </summary>
|
||||
public string TagHelperOutputIsContentModifiedPropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the property for the tag helper's output content.
|
||||
/// </summary>
|
||||
public string TagHelperOutputContentPropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method on the property <see cref="ExecutionContextOutputPropertyName"/> used to retrieve
|
||||
/// tag helper child content.
|
||||
/// </summary>
|
||||
public string TagHelperOutputGetChildContentAsyncMethodName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -143,34 +143,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
Assert.Empty(content3.GetContent(new HtmlTestEncoder()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteChildContentAsync_IsNotMemoized()
|
||||
{
|
||||
// Arrange
|
||||
var childContentExecutionCount = 0;
|
||||
var executionContext = new TagHelperExecutionContext(
|
||||
"p",
|
||||
tagMode: TagMode.StartTagAndEndTag,
|
||||
items: new Dictionary<object, object>(),
|
||||
uniqueId: string.Empty,
|
||||
executeChildContentAsync: () =>
|
||||
{
|
||||
childContentExecutionCount++;
|
||||
|
||||
return Task.FromResult(result: true);
|
||||
},
|
||||
startTagHelperWritingScope: () => { },
|
||||
endTagHelperWritingScope: () => new DefaultTagHelperContent());
|
||||
|
||||
// Act
|
||||
await executionContext.ExecuteChildContentAsync();
|
||||
await executionContext.ExecuteChildContentAsync();
|
||||
await executionContext.ExecuteChildContentAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, childContentExecutionCount);
|
||||
}
|
||||
|
||||
public static TheoryData<string, string> DictionaryCaseTestingData
|
||||
{
|
||||
get
|
||||
|
|
|
|||
Loading…
Reference in New Issue