Moved AddTagHelperHtmlAttribute from renderer to writer

This commit is contained in:
Ajay Bhargav Baaskaran 2017-04-07 18:42:24 -07:00
parent cf44f103c1
commit 0b17f14d68
7 changed files with 310 additions and 73 deletions

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -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";
}
}

View File

@ -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;
}
}
}