Moved AddTagHelperHtmlAttribute from renderer to writer
This commit is contained in:
parent
cf44f103c1
commit
0b17f14d68
|
|
@ -216,6 +216,11 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
Context.TagHelperWriter.WriteCreateTagHelper(Context, node);
|
||||
}
|
||||
|
||||
public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node)
|
||||
{
|
||||
Context.TagHelperWriter.WriteAddTagHelperHtmlAttribute(Context, node);
|
||||
}
|
||||
|
||||
public override void VisitExecuteTagHelpers(ExecuteTagHelpersIRNode node)
|
||||
{
|
||||
Context.TagHelperWriter.WriteExecuteTagHelpers(Context, node);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
|
||||
public override void WriteAddTagHelperHtmlAttribute(CSharpRenderingContext context, AddTagHelperHtmlAttributeIRNode node)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
context.RenderChildren(node);
|
||||
}
|
||||
|
||||
public override void WriteCreateTagHelper(CSharpRenderingContext context, CreateTagHelperIRNode node)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
|
||||
public string WriteHtmlContentMethod { get; set; } = "WriteLiteral";
|
||||
|
||||
public string WriteAttributeValueMethod { get; set; } = "WriteAttributeValue";
|
||||
|
||||
public override void WriteCSharpExpression(CSharpRenderingContext context, CSharpExpressionIRNode node)
|
||||
{
|
||||
if (context == null)
|
||||
|
|
|
|||
|
|
@ -278,77 +278,6 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node)
|
||||
{
|
||||
var attributeValueStyleParameter = $"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}";
|
||||
var isConditionalAttributeValue = node.Children.Any(child => child is CSharpAttributeValueIRNode);
|
||||
|
||||
// All simple text and minimized attributes will be pre-allocated.
|
||||
if (isConditionalAttributeValue)
|
||||
{
|
||||
// Dynamic attribute value should be run through the conditional attribute removal system. It's
|
||||
// unbound and contains C#.
|
||||
|
||||
// TagHelper attribute rendering is buffered by default. We do not want to write to the current
|
||||
// writer.
|
||||
var valuePieceCount = node.Children.Count(
|
||||
child => child is HtmlAttributeValueIRNode || child is CSharpAttributeValueIRNode);
|
||||
|
||||
Context.Writer
|
||||
.WriteStartMethodInvocation("BeginAddHtmlAttributeValues" /* ORIGINAL: BeginAddHtmlAttributeValuesMethodName */)
|
||||
.Write("__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(node.Name)
|
||||
.WriteParameterSeparator()
|
||||
.Write(valuePieceCount.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.Write(attributeValueStyleParameter)
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
var initialRenderingConventions = Context.RenderingConventions;
|
||||
Context.RenderingConventions = new TagHelperHtmlAttributeRenderingConventions(Context.Writer);
|
||||
VisitDefault(node);
|
||||
Context.RenderingConventions = initialRenderingConventions;
|
||||
|
||||
Context.Writer
|
||||
.WriteMethodInvocation(
|
||||
"EndAddHtmlAttributeValues" /* ORIGINAL: EndAddHtmlAttributeValuesMethodName */,
|
||||
"__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or
|
||||
// other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to
|
||||
// determine its final value.
|
||||
|
||||
// Attribute value is not plain text, must be buffered to determine its final value.
|
||||
Context.Writer.WriteMethodInvocation("BeginWriteTagHelperAttribute" /* ORIGINAL: BeginWriteTagHelperAttributeMethodName */);
|
||||
|
||||
// We're building a writing scope around the provided chunks which captures everything written from the
|
||||
// page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to
|
||||
// ensure we capture all content that's written, directly or indirectly.
|
||||
var initialRenderingConventions = Context.RenderingConventions;
|
||||
Context.RenderingConventions = new CSharpRenderingConventions(Context.Writer);
|
||||
VisitDefault(node);
|
||||
Context.RenderingConventions = initialRenderingConventions;
|
||||
|
||||
Context.Writer
|
||||
.WriteStartAssignment("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */)
|
||||
.WriteMethodInvocation("EndWriteTagHelperAttribute" /* ORIGINAL: EndWriteTagHelperAttributeMethodName */)
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
"__tagHelperExecutionContext" /* ORIGINAL: ExecutionContextVariableName */,
|
||||
"AddHtmlAttribute" /* ORIGINAL: ExecutionContextAddHtmlAttributeMethodName */)
|
||||
.WriteStringLiteral(node.Name)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStartMethodInvocation("Html.Raw" /* ORIGINAL: MarkAsHtmlEncodedMethodName */)
|
||||
.Write("__tagHelperStringValueBuffer" /* ORIGINAL: StringValueBufferVariableName */)
|
||||
.WriteEndMethodInvocation(endLine: false)
|
||||
.WriteParameterSeparator()
|
||||
.Write(attributeValueStyleParameter)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitSetPreallocatedTagHelperProperty(SetPreallocatedTagHelperPropertyIRNode node)
|
||||
{
|
||||
var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
||||
|
|
@ -20,6 +22,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
|
||||
public string ExecutionContextSetOutputContentAsyncMethodName { get; set; } = "SetOutputContentAsync";
|
||||
|
||||
public string ExecutionContextAddHtmlAttributeMethodName { get; set; } = "AddHtmlAttribute";
|
||||
|
||||
public string RunnerTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner";
|
||||
|
||||
public string RunnerVariableName { get; set; } = "__tagHelperRunner";
|
||||
|
|
@ -44,6 +48,16 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
|
||||
public string TagHelperOutputIsContentModifiedPropertyName { get; set; } = "IsContentModified";
|
||||
|
||||
public string BeginAddHtmlAttributeValuesMethodName { get; set; } = "BeginAddHtmlAttributeValues";
|
||||
|
||||
public string EndAddHtmlAttributeValuesMethodName { get; set; } = "EndAddHtmlAttributeValues";
|
||||
|
||||
public string BeginWriteTagHelperAttributeMethodName { get; set; } = "BeginWriteTagHelperAttribute";
|
||||
|
||||
public string EndWriteTagHelperAttributeMethodName { get; set; } = "EndWriteTagHelperAttribute";
|
||||
|
||||
public string MarkAsHtmlEncodedMethodName { get; set; } = "Html.Raw";
|
||||
|
||||
public string WriteTagHelperOutputMethod { get; set; } = "Write";
|
||||
|
||||
public override void WriteDeclareTagHelperFields(CSharpRenderingContext context, DeclareTagHelperFieldsIRNode node)
|
||||
|
|
@ -127,7 +141,82 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
|||
|
||||
public override void WriteAddTagHelperHtmlAttribute(CSharpRenderingContext context, AddTagHelperHtmlAttributeIRNode node)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var attributeValueStyleParameter = $"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.ValueStyle}";
|
||||
var isConditionalAttributeValue = node.Children.Any(child => child is CSharpAttributeValueIRNode);
|
||||
|
||||
// All simple text and minimized attributes will be pre-allocated.
|
||||
if (isConditionalAttributeValue)
|
||||
{
|
||||
// Dynamic attribute value should be run through the conditional attribute removal system. It's
|
||||
// unbound and contains C#.
|
||||
|
||||
// TagHelper attribute rendering is buffered by default. We do not want to write to the current
|
||||
// writer.
|
||||
var valuePieceCount = node.Children.Count(
|
||||
child => child is HtmlAttributeValueIRNode || child is CSharpAttributeValueIRNode);
|
||||
|
||||
context.Writer
|
||||
.WriteStartMethodInvocation(BeginAddHtmlAttributeValuesMethodName)
|
||||
.Write(ExecutionContextVariableName)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(node.Name)
|
||||
.WriteParameterSeparator()
|
||||
.Write(valuePieceCount.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteParameterSeparator()
|
||||
.Write(attributeValueStyleParameter)
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
// This can be removed once all the tag helper nodes are moved out of the renderers.
|
||||
var initialRenderingConventions = context.RenderingConventions;
|
||||
context.RenderingConventions = new TagHelperHtmlAttributeRenderingConventions(context.Writer);
|
||||
using (context.Push(new TagHelperHtmlAttributeRuntimeBasicWriter()))
|
||||
{
|
||||
context.RenderChildren(node);
|
||||
}
|
||||
context.RenderingConventions = initialRenderingConventions;
|
||||
|
||||
context.Writer
|
||||
.WriteMethodInvocation(
|
||||
EndAddHtmlAttributeValuesMethodName,
|
||||
ExecutionContextVariableName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or
|
||||
// other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to
|
||||
// determine its final value.
|
||||
|
||||
// Attribute value is not plain text, must be buffered to determine its final value.
|
||||
context.Writer.WriteMethodInvocation(BeginWriteTagHelperAttributeMethodName);
|
||||
|
||||
// We're building a writing scope around the provided chunks which captures everything written from the
|
||||
// page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to
|
||||
// ensure we capture all content that's written, directly or indirectly.
|
||||
// This can be removed once all the tag helper nodes are moved out of the renderers.
|
||||
var initialRenderingConventions = context.RenderingConventions;
|
||||
context.RenderingConventions = new CSharpRenderingConventions(context.Writer);
|
||||
using (context.Push(new RuntimeBasicWriter()))
|
||||
using (context.Push(new RuntimeTagHelperWriter()))
|
||||
{
|
||||
context.RenderChildren(node);
|
||||
}
|
||||
context.RenderingConventions = initialRenderingConventions;
|
||||
|
||||
context.Writer
|
||||
.WriteStartAssignment(StringValueBufferVariableName)
|
||||
.WriteMethodInvocation(EndWriteTagHelperAttributeMethodName)
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
ExecutionContextVariableName,
|
||||
ExecutionContextAddHtmlAttributeMethodName)
|
||||
.WriteStringLiteral(node.Name)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStartMethodInvocation(MarkAsHtmlEncodedMethodName)
|
||||
.Write(StringValueBufferVariableName)
|
||||
.WriteEndMethodInvocation(endLine: false)
|
||||
.WriteParameterSeparator()
|
||||
.Write(attributeValueStyleParameter)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteCreateTagHelper(CSharpRenderingContext context, CreateTagHelperIRNode node)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
|
||||
{
|
||||
internal class TagHelperHtmlAttributeRuntimeBasicWriter : RuntimeBasicWriter
|
||||
{
|
||||
// This will be used when HtmlAttributeValueIRNode and CSharpAttributeValueIRNode are moved to writers.
|
||||
public new string WriteAttributeValueMethod { get; set; } = "AddHtmlAttributeValue";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -180,5 +183,203 @@ __tagHelperExecutionContext = __tagHelperScopeManager.End();
|
|||
csharp,
|
||||
ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteAddTagHelperHtmlAttribute_RendersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var writer = new RuntimeTagHelperWriter();
|
||||
var options = RazorParserOptions.CreateDefaultOptions();
|
||||
var codeWriter = new Legacy.CSharpCodeWriter();
|
||||
var context = new CSharpRenderingContext()
|
||||
{
|
||||
Writer = codeWriter,
|
||||
Options = options,
|
||||
BasicWriter = new RuntimeBasicWriter(),
|
||||
TagHelperWriter = writer,
|
||||
RenderChildren = n =>
|
||||
{
|
||||
codeWriter.WriteLine("Render Children");
|
||||
}
|
||||
};
|
||||
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "input",
|
||||
typeName: "InputTagHelper",
|
||||
assemblyName: "TestAssembly")
|
||||
};
|
||||
var engine = RazorEngine.Create(builder => builder.AddTagHelpers(descriptors));
|
||||
var content = @"
|
||||
@addTagHelper *, TestAssembly
|
||||
<input name=""value"" />";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var irDocument = Lower(codeDocument, engine);
|
||||
var node = irDocument.Children.Last().Children[2] as AddTagHelperHtmlAttributeIRNode;
|
||||
|
||||
// Act
|
||||
writer.WriteAddTagHelperHtmlAttribute(context, node);
|
||||
|
||||
// Assert
|
||||
var csharp = context.Writer.Builder.ToString();
|
||||
Assert.Equal(
|
||||
@"BeginWriteTagHelperAttribute();
|
||||
Render Children
|
||||
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(""name"", Html.Raw(__tagHelperStringValueBuffer), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
|
||||
",
|
||||
csharp,
|
||||
ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteAddTagHelperHtmlAttribute_DataAttribute_RendersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var writer = new RuntimeTagHelperWriter();
|
||||
var options = RazorParserOptions.CreateDefaultOptions();
|
||||
var codeWriter = new Legacy.CSharpCodeWriter();
|
||||
var context = new CSharpRenderingContext()
|
||||
{
|
||||
Writer = codeWriter,
|
||||
Options = options,
|
||||
BasicWriter = new RuntimeBasicWriter(),
|
||||
TagHelperWriter = writer,
|
||||
RenderChildren = n =>
|
||||
{
|
||||
codeWriter.WriteLine("Render Children");
|
||||
}
|
||||
};
|
||||
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "input",
|
||||
typeName: "InputTagHelper",
|
||||
assemblyName: "TestAssembly")
|
||||
};
|
||||
var engine = RazorEngine.Create(builder => builder.AddTagHelpers(descriptors));
|
||||
var content = @"
|
||||
@addTagHelper *, TestAssembly
|
||||
<input data-test=""Blah-@Foo"" />";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var irDocument = Lower(codeDocument, engine);
|
||||
var node = irDocument.Children.Last().Children[2] as AddTagHelperHtmlAttributeIRNode;
|
||||
|
||||
// Act
|
||||
writer.WriteAddTagHelperHtmlAttribute(context, node);
|
||||
|
||||
// Assert
|
||||
var csharp = context.Writer.Builder.ToString();
|
||||
Assert.Equal(
|
||||
@"BeginWriteTagHelperAttribute();
|
||||
Render Children
|
||||
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
|
||||
__tagHelperExecutionContext.AddHtmlAttribute(""data-test"", Html.Raw(__tagHelperStringValueBuffer), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
|
||||
",
|
||||
csharp,
|
||||
ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteAddTagHelperHtmlAttribute_DynamicAttribute_RendersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var writer = new RuntimeTagHelperWriter();
|
||||
var options = RazorParserOptions.CreateDefaultOptions();
|
||||
var codeWriter = new Legacy.CSharpCodeWriter();
|
||||
var context = new CSharpRenderingContext()
|
||||
{
|
||||
Writer = codeWriter,
|
||||
Options = options,
|
||||
BasicWriter = new RuntimeBasicWriter(),
|
||||
TagHelperWriter = writer,
|
||||
RenderChildren = n =>
|
||||
{
|
||||
codeWriter.WriteLine("Render Children");
|
||||
}
|
||||
};
|
||||
|
||||
var descriptors = new[]
|
||||
{
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "input",
|
||||
typeName: "InputTagHelper",
|
||||
assemblyName: "TestAssembly")
|
||||
};
|
||||
var engine = RazorEngine.Create(builder => builder.AddTagHelpers(descriptors));
|
||||
var content = @"
|
||||
@addTagHelper *, TestAssembly
|
||||
<input test=""Blah-@Foo"" />";
|
||||
var sourceDocument = TestRazorSourceDocument.Create(content);
|
||||
var codeDocument = RazorCodeDocument.Create(sourceDocument);
|
||||
var irDocument = Lower(codeDocument, engine);
|
||||
var node = irDocument.Children.Last().Children[2] as AddTagHelperHtmlAttributeIRNode;
|
||||
|
||||
// Act
|
||||
writer.WriteAddTagHelperHtmlAttribute(context, node);
|
||||
|
||||
// Assert
|
||||
var csharp = context.Writer.Builder.ToString();
|
||||
Assert.Equal(
|
||||
@"BeginAddHtmlAttributeValues(__tagHelperExecutionContext, ""test"", 2, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
|
||||
Render Children
|
||||
EndAddHtmlAttributeValues(__tagHelperExecutionContext);
|
||||
",
|
||||
csharp,
|
||||
ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
private static DocumentIRNode Lower(RazorCodeDocument codeDocument)
|
||||
{
|
||||
var engine = RazorEngine.Create();
|
||||
|
||||
return Lower(codeDocument, engine);
|
||||
}
|
||||
|
||||
private static DocumentIRNode Lower(RazorCodeDocument codeDocument, RazorEngine engine)
|
||||
{
|
||||
for (var i = 0; i < engine.Phases.Count; i++)
|
||||
{
|
||||
var phase = engine.Phases[i];
|
||||
phase.Execute(codeDocument);
|
||||
|
||||
if (phase is IRazorIRLoweringPhase)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var irDocument = codeDocument.GetIRDocument();
|
||||
Assert.NotNull(irDocument);
|
||||
|
||||
return irDocument;
|
||||
}
|
||||
|
||||
private static TagHelperDescriptor CreateTagHelperDescriptor(
|
||||
string tagName,
|
||||
string typeName,
|
||||
string assemblyName,
|
||||
IEnumerable<Action<ITagHelperBoundAttributeDescriptorBuilder>> attributes = null)
|
||||
{
|
||||
var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName);
|
||||
|
||||
if (attributes != null)
|
||||
{
|
||||
foreach (var attributeBuilder in attributes)
|
||||
{
|
||||
builder.BindAttribute(attributeBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
builder.TagMatchingRule(ruleBuilder => ruleBuilder.RequireTagName(tagName));
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue