Rewrite of code rendering context

This commit is contained in:
Ryan Nowak 2017-07-03 15:25:34 -07:00
parent 8dfba25d59
commit 7408bcd025
10 changed files with 489 additions and 108 deletions

View File

@ -2,6 +2,7 @@
// 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 Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
@ -18,11 +19,15 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public abstract string DocumentKind { get; }
public abstract ItemCollection Items { get; }
public abstract IEnumerable<IntermediateNode> Ancestors { get; }
public abstract IntermediateNodeWriter NodeWriter { get; }
public abstract RazorCodeGenerationOptions Options { get; }
public abstract IntermediateNode Parent { get; }
public abstract RazorSourceDocument SourceDocument { get; }
public abstract Scope CreateScope();
@ -56,48 +61,5 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
_context.EndScope();
}
}
// All bits below here are temporary
#region Temporary TagHelper bits
internal TagHelperRenderingContext TagHelperRenderingContext { get; set; }
internal TagHelperRenderingContextScope Push(TagHelperRenderingContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var scope = new TagHelperRenderingContextScope(this, TagHelperRenderingContext);
TagHelperRenderingContext = context;
return scope;
}
internal struct TagHelperRenderingContextScope : IDisposable
{
private readonly CodeRenderingContext _context;
private readonly TagHelperRenderingContext _renderingContext;
public TagHelperRenderingContextScope(CodeRenderingContext context, TagHelperRenderingContext renderingContext)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
_context = context;
_renderingContext = renderingContext;
}
public void Dispose()
{
_context.TagHelperRenderingContext = _renderingContext;
}
}
#endregion
}
}

View File

@ -10,9 +10,9 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
internal class DefaultCodeRenderingContext : CodeRenderingContext
{
private readonly Stack<IntermediateNode> _ancestors;
private readonly RazorCodeDocument _codeDocument;
private readonly DocumentIntermediateNode _documentNode;
private readonly List<ScopeInternal> _scopes;
public DefaultCodeRenderingContext(
@ -52,12 +52,11 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
_documentNode = documentNode;
Options = options;
_ancestors = new Stack<IntermediateNode>();
Diagnostics = new RazorDiagnosticCollection();
Items = new ItemCollection();
LineMappings = new List<LineMapping>();
TagHelperRenderingContext = new TagHelperRenderingContext();
var diagnostics = _documentNode.GetAllDiagnostics();
for (var i = 0; i < diagnostics.Count; i++)
{
@ -81,6 +80,10 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
// This will be initialized by the document writer when the context is 'live'.
public IntermediateNodeVisitor Visitor { get; set; }
public override IEnumerable<IntermediateNode> Ancestors => _ancestors;
internal Stack<IntermediateNode> AncestorsInternal => _ancestors;
public override CodeWriter CodeWriter { get; }
public override RazorDiagnosticCollection Diagnostics { get; }
@ -95,6 +98,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public override RazorCodeGenerationOptions Options { get; }
public override IntermediateNode Parent => _ancestors.Count == 0 ? null : _ancestors.Peek();
public override RazorSourceDocument SourceDocument => _codeDocument.Source;
private ScopeInternal Current => _scopes[_scopes.Count - 1];
@ -154,10 +159,14 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
throw new ArgumentNullException(nameof(node));
}
_ancestors.Push(node);
for (var i = 0; i < node.Children.Count; i++)
{
Visitor.Visit(node.Children[i]);
}
_ancestors.Pop();
}
public override void RenderNode(IntermediateNode node)

View File

@ -200,12 +200,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public override void VisitTagHelper(TagHelperIntermediateNode node)
{
var tagHelperRenderingContext = new TagHelperRenderingContext();
using (Context.Push(tagHelperRenderingContext))
{
VisitDefault(node);
}
VisitDefault(node);
}
public override void VisitDefault(IntermediateNode node)

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
@ -75,6 +76,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperBody(CodeRenderingContext context, DefaultTagHelperBodyIntermediateNode node)
{
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (DesignTime)
{
context.RenderChildren(node);
@ -121,6 +128,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperCreate(CodeRenderingContext context, DefaultTagHelperCreateIntermediateNode node)
{
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
context.CodeWriter
.WriteStartAssignment(node.Field)
.Write(CreateTagHelperMethodName)
@ -137,6 +150,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperExecute(CodeRenderingContext context, DefaultTagHelperExecuteIntermediateNode node)
{
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (!DesignTime)
{
context.CodeWriter
@ -178,6 +197,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, DefaultTagHelperHtmlAttributeIntermediateNode node)
{
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (DesignTime)
{
context.RenderChildren(node);
@ -261,22 +286,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node)
{
var tagHelperRenderingContext = context.TagHelperRenderingContext;
var propertyName = node.BoundAttribute.GetPropertyName();
var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, node.Field, node.AttributeName, node.BoundAttribute);
var tagHelperNode = context.Parent as TagHelperIntermediateNode;
if (context.Parent == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (!DesignTime)
{
// Ensure that the property we're trying to set has initialized its dictionary bound properties.
if (node.IsIndexerNameMatch &&
tagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelper.GetTypeName()}.{propertyName}"))
object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node))
{
// Throw a reasonable Exception at runtime if the dictionary property is null.
context.CodeWriter
.Write("if (")
.Write(node.Field)
.Write(".")
.Write(propertyName)
.Write(node.Property)
.WriteLine(" == null)");
using (context.CodeWriter.BuildScope())
{
@ -290,34 +318,40 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
.WriteParameterSeparator()
.WriteStringLiteral(node.TagHelper.GetTypeName())
.WriteParameterSeparator()
.WriteStringLiteral(propertyName)
.WriteStringLiteral(node.Property)
.WriteEndMethodInvocation(endLine: false) // End of method call
.WriteEndMethodInvocation(); // End of new expression / throw statement
}
}
}
if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out var previousValueAccessor))
// If this is not the first use of the attribute value, we need to evaluate the expression and assign it to
// the tag helper property.
//
// Otherwise, the value has already been computed and assigned to another tag helper. We just need to
// copy from that tag helper to this one.
//
// This is important because we can't evaluate the expression twice due to side-effects.
var firstUseOfAttribute = FindFirstUseOfAttribute(tagHelperNode, node);
if (!object.ReferenceEquals(firstUseOfAttribute, node))
{
// If we get here, this value has already been used. We just need to copy the value.
context.CodeWriter
.WriteStartAssignment(propertyValueAccessor)
.Write(previousValueAccessor)
.WriteStartAssignment(GetPropertyAccessor(node))
.Write(GetPropertyAccessor(firstUseOfAttribute))
.WriteLine(";");
return;
}
else
{
tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor;
}
// If we get there, this is the first time seeing this property so we need to evaluate the expression.
if (node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty))
{
if (DesignTime)
{
context.RenderChildren(node);
context.CodeWriter.WriteStartAssignment(propertyValueAccessor);
context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
if (node.Children.Count == 1 && node.Children.First() is HtmlContentIntermediateNode htmlNode)
{
var content = GetContent(htmlNode);
@ -341,7 +375,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
context.CodeWriter
.WriteStartAssignment(StringValueBufferVariableName)
.WriteMethodInvocation(EndWriteTagHelperAttributeMethodName)
.WriteStartAssignment(propertyValueAccessor)
.WriteStartAssignment(GetPropertyAccessor(node))
.Write(StringValueBufferVariableName)
.WriteLine(";");
}
@ -355,7 +389,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
using (context.CodeWriter.BuildLinePragma(node.Source))
{
var assignmentPrefixLength = propertyValueAccessor.Length + " = ".Length;
var accessor = GetPropertyAccessor(node);
var assignmentPrefixLength = accessor.Length + " = ".Length;
if (node.BoundAttribute.IsEnum &&
node.Children.Count == 1 &&
node.Children.First() is IntermediateToken token &&
@ -369,7 +404,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
context.CodeWriter
.WriteStartAssignment(propertyValueAccessor)
.WriteStartAssignment(accessor)
.Write("global::")
.Write(node.BoundAttribute.TypeName)
.Write(".");
@ -381,7 +416,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context);
}
context.CodeWriter.WriteStartAssignment(propertyValueAccessor);
context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
}
RenderTagHelperAttributeInline(context, node, node.Source);
@ -393,7 +428,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
using (context.CodeWriter.BuildLinePragma(node.Source))
{
context.CodeWriter.WriteStartAssignment(propertyValueAccessor);
context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
if (node.BoundAttribute.IsEnum &&
node.Children.Count == 1 &&
@ -422,7 +457,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
ExecutionContextAddTagHelperAttributeMethodName)
.WriteStringLiteral(node.AttributeName)
.WriteParameterSeparator()
.Write(propertyValueAccessor)
.Write(GetPropertyAccessor(node))
.WriteParameterSeparator()
.Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}")
.WriteEndMethodInvocation();
@ -548,6 +583,45 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
}
private static DefaultTagHelperPropertyIntermediateNode FindFirstUseOfIndexer(
TagHelperIntermediateNode tagHelperNode,
DefaultTagHelperPropertyIntermediateNode propertyNode)
{
Debug.Assert(tagHelperNode.Children.Contains(propertyNode));
Debug.Assert(propertyNode.IsIndexerNameMatch);
for (var i = 0; i < tagHelperNode.Children.Count; i++)
{
if (tagHelperNode.Children[i] is DefaultTagHelperPropertyIntermediateNode otherPropertyNode &&
otherPropertyNode.TagHelper.Equals(propertyNode.TagHelper) &&
otherPropertyNode.BoundAttribute.Equals(propertyNode.BoundAttribute) &&
otherPropertyNode.IsIndexerNameMatch)
{
return otherPropertyNode;
}
}
// This is unreachable, we should find 'propertyNode' in the list of children.
throw new InvalidOperationException();
}
private static DefaultTagHelperPropertyIntermediateNode FindFirstUseOfAttribute(
TagHelperIntermediateNode tagHelperNode,
DefaultTagHelperPropertyIntermediateNode propertyNode)
{
for (var i = 0; i < tagHelperNode.Children.Count; i++)
{
if (tagHelperNode.Children[i] is DefaultTagHelperPropertyIntermediateNode otherPropertyNode &&
string.Equals(otherPropertyNode.AttributeName, propertyNode.AttributeName, StringComparison.Ordinal))
{
return otherPropertyNode;
}
}
// This is unreachable, we should find 'propertyNode' in the list of children.
throw new InvalidOperationException();
}
private string GetContent(HtmlContentIntermediateNode node)
{
var builder = new StringBuilder();
@ -562,17 +636,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
return builder.ToString();
}
private static string GetTagHelperPropertyAccessor(
bool isIndexerNameMatch,
string tagHelperVariableName,
string attributeName,
BoundAttributeDescriptor descriptor)
private static string GetPropertyAccessor(DefaultTagHelperPropertyIntermediateNode node)
{
var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}";
var propertyAccessor = $"{node.Field}.{node.Property}";
if (isIndexerNameMatch)
if (node.IsIndexerNameMatch)
{
var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length);
var dictionaryKey = node.AttributeName.Substring(node.BoundAttribute.IndexerNamePrefix.Length);
propertyAccessor += $"[\"{dictionaryKey}\"]";
}

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
@ -50,6 +52,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeIntermediateNode node)
{
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
context.CodeWriter
.WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddHtmlAttributeMethodName)
.Write(node.VariableName)
@ -75,20 +83,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperProperty(CodeRenderingContext context, PreallocatedTagHelperPropertyIntermediateNode node)
{
var propertyName = node.BoundAttribute.GetPropertyName();
var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, node.Field, node.AttributeName, node.BoundAttribute);
var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */;
var tagHelperNode = context.Parent as TagHelperIntermediateNode;
if (tagHelperNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
// Ensure that the property we're trying to set has initialized its dictionary bound properties.
if (node.IsIndexerNameMatch &&
context.TagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelper.GetTypeName()}.{propertyName}"))
object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node))
{
// Throw a reasonable Exception at runtime if the dictionary property is null.
context.CodeWriter
.Write("if (")
.Write(node.Field)
.Write(".")
.Write(propertyName)
.Write(node.Property)
.WriteLine(" == null)");
using (context.CodeWriter.BuildScope())
{
@ -102,33 +113,51 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
.WriteParameterSeparator()
.WriteStringLiteral(node.TagHelper.GetTypeName())
.WriteParameterSeparator()
.WriteStringLiteral(propertyName)
.WriteStringLiteral(node.Property)
.WriteEndMethodInvocation(endLine: false) // End of method call
.WriteEndMethodInvocation(); // End of new expression / throw statement
}
}
context.CodeWriter
.WriteStartAssignment(propertyValueAccessor)
.WriteStartAssignment(GetPropertyAccessor(node))
.Write("(string)")
.Write(attributeValueAccessor)
.Write($"{node.VariableName}.Value")
.WriteLine(";")
.WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddTagHelperAttributeMethodName)
.Write(node.VariableName)
.WriteEndMethodInvocation();
}
private static string GetTagHelperPropertyAccessor(
bool isIndexerNameMatch,
string tagHelperVariableName,
string attributeName,
BoundAttributeDescriptor descriptor)
private static PreallocatedTagHelperPropertyIntermediateNode FindFirstUseOfIndexer(
TagHelperIntermediateNode tagHelperNode,
PreallocatedTagHelperPropertyIntermediateNode propertyNode)
{
var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}";
Debug.Assert(tagHelperNode.Children.Contains(propertyNode));
Debug.Assert(propertyNode.IsIndexerNameMatch);
if (isIndexerNameMatch)
for (var i = 0; i < tagHelperNode.Children.Count; i++)
{
var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length);
if (tagHelperNode.Children[i] is PreallocatedTagHelperPropertyIntermediateNode otherPropertyNode &&
otherPropertyNode.TagHelper.Equals(propertyNode.TagHelper) &&
otherPropertyNode.BoundAttribute.Equals(propertyNode.BoundAttribute) &&
otherPropertyNode.IsIndexerNameMatch)
{
return otherPropertyNode;
}
}
// This is unreachable, we should find 'propertyNode' in the list of children.
throw new InvalidOperationException();
}
private static string GetPropertyAccessor(PreallocatedTagHelperPropertyIntermediateNode node)
{
var propertyAccessor = $"{node.Field}.{node.Property}";
if (node.IsIndexerNameMatch)
{
var dictionaryKey = node.AttributeName.Substring(node.BoundAttribute.IndexerNamePrefix.Length);
propertyAccessor += $"[\"{dictionaryKey}\"]";
}

View File

@ -584,6 +584,20 @@ namespace Microsoft.AspNetCore.Razor.Language
internal static string FormatRenderingContextRequiresDelegate(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("RenderingContextRequiresDelegate"), p0, p1);
/// <summary>
/// The '{0}' node type can only be used as a direct child of a '{1}' node.
/// </summary>
internal static string IntermediateNodes_InvalidParentNode
{
get => GetString("IntermediateNodes_InvalidParentNode");
}
/// <summary>
/// The '{0}' node type can only be used as a direct child of a '{1}' node.
/// </summary>
internal static string FormatIntermediateNodes_InvalidParentNode(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("IntermediateNodes_InvalidParentNode"), p0, p1);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -240,4 +240,7 @@
<data name="RenderingContextRequiresDelegate" xml:space="preserve">
<value>The '{0}' requires a '{1}' delegate to be set.</value>
</data>
<data name="IntermediateNodes_InvalidParentNode" xml:space="preserve">
<value>The '{0}' node type can only be used as a direct child of a '{1}' node.</value>
</data>
</root>

View File

@ -71,6 +71,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperBodyIntermediateNode()
{
Children =
@ -78,6 +79,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
new CSharpExpressionIntermediateNode(),
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperBody(context, node);
@ -98,6 +101,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperBodyIntermediateNode()
{
Children =
@ -107,6 +111,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
TagMode = TagMode.SelfClosing,
TagName = "p",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperBody(context, node);
@ -130,11 +136,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperCreateIntermediateNode()
{
Field = "__TestNamespace_MyTagHelper",
Type = "TestNamespace.MyTagHelper",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperCreate(context, node);
@ -155,11 +164,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperCreateIntermediateNode()
{
Field = "__TestNamespace_MyTagHelper",
Type = "TestNamespace.MyTagHelper",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperCreate(context, node);
@ -181,7 +193,10 @@ __tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper);
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperExecuteIntermediateNode();
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperExecute(context, node);
@ -201,7 +216,10 @@ __tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper);
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperExecuteIntermediateNode();
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperExecute(context, node);
@ -228,6 +246,7 @@ __tagHelperExecutionContext = __tagHelperScopeManager.End();
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{
AttributeName = "name",
@ -244,6 +263,8 @@ __tagHelperExecutionContext = __tagHelperScopeManager.End();
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
@ -265,6 +286,7 @@ Render Children
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{
AttributeName = "name",
@ -277,6 +299,8 @@ Render Children
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
@ -300,6 +324,7 @@ __tagHelperExecutionContext.AddHtmlAttribute(""name"", Html.Raw(__tagHelperStrin
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{
AttributeName = "name",
@ -316,6 +341,8 @@ __tagHelperExecutionContext.AddHtmlAttribute(""name"", Html.Raw(__tagHelperStrin
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
@ -339,6 +366,7 @@ EndAddHtmlAttributeValues(__tagHelperExecutionContext);
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -356,6 +384,8 @@ EndAddHtmlAttributeValues(__tagHelperExecutionContext);
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -377,6 +407,7 @@ __InputTagHelper.StringProp = ""value"";
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -394,6 +425,8 @@ __InputTagHelper.StringProp = ""value"";
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -415,6 +448,7 @@ __InputTagHelper.StringProp = string.Empty;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -433,6 +467,8 @@ __InputTagHelper.StringProp = string.Empty;
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -450,6 +486,50 @@ __InputTagHelper.IntProp = 32;
ignoreLineEndingDifferences: true);
}
// If a value is bound to multiple tag helpers, we want to make sure to only render the first
// occurrence of the expression due to side-effects.
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringProperty_SecondUseOfAttribute()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
// We only look at the attribute name here.
AttributeName = "bound",
Field = "__OtherTagHelper",
Property = "IntProp",
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = false,
Property = "IntProp",
TagHelper = IntPropertyTagHelper,
Source = Span,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.Builder.ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly_WithoutLocation()
{
@ -457,6 +537,7 @@ __InputTagHelper.IntProp = 32;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -474,6 +555,8 @@ __InputTagHelper.IntProp = 32;
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -494,6 +577,7 @@ __InputTagHelper.IntProp = 32;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
@ -512,6 +596,8 @@ __InputTagHelper.IntProp = 32;
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -536,6 +622,7 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
@ -553,6 +640,8 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -573,6 +662,7 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -590,6 +680,8 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -616,6 +708,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.St
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -634,6 +727,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.St
}
},
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -652,6 +747,50 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
ignoreLineEndingDifferences: true);
}
// If a value is bound to multiple tag helpers, we want to make sure to only render the first
// occurrence of the expression due to side-effects.
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringProperty_SecondUseOfAttribute()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
// We only look at the attribute name here.
AttributeName = "bound",
Field = "__OtherTagHelper",
Property = "IntProp",
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = false,
Property = "IntProp",
TagHelper = IntPropertyTagHelper,
Source = Span,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.Builder.ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_WithoutLocation()
{
@ -659,6 +798,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
@ -676,6 +816,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -697,6 +839,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
@ -715,6 +858,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -729,6 +874,71 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
#line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32;
#line default
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact] // We should only emit the validation code for the first use of an indexer property.
public void WriteTagHelperProperty_Runtime_NonStringIndexer_MultipleValues()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-first",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = true,
Property = "IntIndexer",
TagHelper = IntIndexerTagHelper,
Source = Span,
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { new IntermediateToken { Kind = IntermediateToken.TokenKind.CSharp, Content = "17", } },
}
}
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = true,
Property = "IntIndexer",
TagHelper = IntIndexerTagHelper,
Source = Span,
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { new IntermediateToken { Kind = IntermediateToken.TokenKind.CSharp, Content = "32", } },
}
}
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.Builder.ToString();
Assert.Equal(
@"#line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32;
#line default
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
@ -744,6 +954,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
@ -761,6 +972,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
}
}
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -785,7 +998,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
// Arrange
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DefaultTagHelperRuntimeIntermediateNode();
// Act
@ -805,7 +1018,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
// Arrange
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new DefaultTagHelperRuntimeIntermediateNode();
// Act
@ -837,6 +1050,11 @@ private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeMana
ignoreLineEndingDifferences: true);
}
private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node)
{
((DefaultCodeRenderingContext)context).AncestorsInternal.Push(node);
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
var engine = RazorEngine.Create();

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public class PreallocatedAttributeTargetExtensionTest
{
[Fact]
public void WriteDeclarePreallocatedTagHelperHtmlAttribute_RendersCorrectly()
public void WriteTagHelperHtmlAttributeValue_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
[Fact]
public void WriteDeclarePreallocatedTagHelperHtmlAttribute_Minimized_RendersCorrectly()
public void WriteTagHelperHtmlAttributeValue_Minimized_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
@ -63,16 +64,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
[Fact]
public void WriteAddPreallocatedTagHelperHtmlAttribute_RendersCorrectly()
public void WriteTagHelperHtmlAttribute_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperHtmlAttributeIntermediateNode()
{
VariableName = "_tagHelper1"
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
@ -87,12 +91,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
[Fact]
public void WriteDeclarePreallocatedTagHelperAttribute_RendersCorrectly()
public void WriteTagHelperPropertyValue_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new PreallocatedTagHelperPropertyValueIntermediateNode()
{
AttributeName = "Foo",
@ -114,7 +118,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
}
[Fact]
public void WriteSetPreallocatedTagHelperProperty_RendersCorrectly()
public void WriteTagHelperProperty_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
@ -124,7 +128,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.String")
@ -132,13 +135,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var descriptor = builder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = descriptor.Name,
Field = "__FooTagHelper",
VariableName = "_tagHelper1",
BoundAttribute = descriptor,
Field = "__FooTagHelper",
Property = "FooProp",
VariableName = "_tagHelper1",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -164,7 +171,6 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.String>")
@ -173,6 +179,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
var descriptor = builder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Foo",
@ -180,8 +187,11 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
VariableName = "_tagHelper1",
BoundAttribute = descriptor,
IsIndexerNameMatch = true,
Property = "FooProp",
TagHelper = tagHelperBuilder.Build(),
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
@ -199,5 +209,68 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteSetPreallocatedTagHelperProperty_IndexerAttribute_MultipleValues()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "FooTagHelper", "Test");
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.String>")
.AsDictionaryAttribute("pre-", "System.String")
.PropertyName("FooProp");
var boundAttribute = builder.Build();
var tagHelper = tagHelperBuilder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Bar",
Field = "__FooTagHelper",
VariableName = "_tagHelper0s",
BoundAttribute = boundAttribute,
IsIndexerNameMatch = true,
Property = "FooProp",
TagHelper = tagHelper,
};
var node2 = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Foo",
Field = "__FooTagHelper",
VariableName = "_tagHelper1",
BoundAttribute = boundAttribute,
IsIndexerNameMatch = true,
Property = "FooProp",
TagHelper = tagHelper,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.Builder.ToString();
Assert.Equal(
@"__FooTagHelper.FooProp[""Foo""] = (string)_tagHelper1.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
",
csharp,
ignoreLineEndingDifferences: true);
}
private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node)
{
((DefaultCodeRenderingContext)context).AncestorsInternal.Push(node);
}
}
}

View File

@ -181,6 +181,10 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["pepper"] = 98;
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5);
__TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = (string)__tagHelperAttribute_5.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5);
if (__TestNamespace_InputTagHelper1.StringDictionaryProperty == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-cumin", "TestNamespace.InputTagHelper1", "StringDictionaryProperty"));
}
BeginWriteTagHelperAttribute();
WriteLiteral("literate ");
#line 21 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers.cshtml"
@ -192,6 +196,10 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["pepper"] = 98;
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
__TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"] = __tagHelperStringValueBuffer;
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-cumin", __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
if (__TestNamespace_InputTagHelper2.StringDictionaryProperty == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-cumin", "TestNamespace.InputTagHelper2", "StringDictionaryProperty"));
}
__TestNamespace_InputTagHelper2.StringDictionaryProperty["cumin"] = __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"];
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
if (!__tagHelperExecutionContext.Output.IsContentModified)