aspnetcore/src/Microsoft.AspNetCore.Razor..../Extensions/PreallocatedAttributeTarget...

168 lines
7.8 KiB
C#

// 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.Diagnostics;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
internal class PreallocatedAttributeTargetExtension : IPreallocatedAttributeTargetExtension
{
public string TagHelperAttributeTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute";
public string EncodedHtmlStringTypeName { get; set; } = "Microsoft.AspNetCore.Html.HtmlString";
public string ExecutionContextVariableName { get; set; } = "__tagHelperExecutionContext";
public string ExecutionContextAddHtmlAttributeMethodName { get; set; } = "AddHtmlAttribute";
public string ExecutionContextAddTagHelperAttributeMethodName { get; set; } = "AddTagHelperAttribute";
public string FormatInvalidIndexerAssignmentMethodName { get; set; } = "InvalidTagHelperIndexerAssignment";
public void WriteTagHelperHtmlAttributeValue(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeValueIntermediateNode node)
{
context.CodeWriter
.Write("private static readonly global::")
.Write(TagHelperAttributeTypeName)
.Write(" ")
.Write(node.VariableName)
.Write(" = ")
.WriteStartNewObject("global::" + TagHelperAttributeTypeName)
.WriteStringLiteral(node.AttributeName);
if (node.AttributeStructure == AttributeStructure.Minimized)
{
context.CodeWriter.WriteEndMethodInvocation();
}
else
{
context.CodeWriter
.WriteParameterSeparator()
.WriteStartNewObject("global::" + EncodedHtmlStringTypeName)
.WriteStringLiteral(node.Value)
.WriteEndMethodInvocation(endLine: false)
.WriteParameterSeparator()
.Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}")
.WriteEndMethodInvocation();
}
}
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)
.WriteEndMethodInvocation();
}
public void WriteTagHelperPropertyValue(CodeRenderingContext context, PreallocatedTagHelperPropertyValueIntermediateNode node)
{
context.CodeWriter
.Write("private static readonly global::")
.Write(TagHelperAttributeTypeName)
.Write(" ")
.Write(node.VariableName)
.Write(" = ")
.WriteStartNewObject("global::" + TagHelperAttributeTypeName)
.WriteStringLiteral(node.AttributeName)
.WriteParameterSeparator()
.WriteStringLiteral(node.Value)
.WriteParameterSeparator()
.Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}")
.WriteEndMethodInvocation();
}
public void WriteTagHelperProperty(CodeRenderingContext context, PreallocatedTagHelperPropertyIntermediateNode node)
{
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 &&
object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node))
{
// Throw a reasonable Exception at runtime if the dictionary property is null.
context.CodeWriter
.Write("if (")
.Write(node.FieldName)
.Write(".")
.Write(node.PropertyName)
.WriteLine(" == null)");
using (context.CodeWriter.BuildScope())
{
// System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName
// of InvalidOperationException type.
context.CodeWriter
.Write("throw ")
.WriteStartNewObject(nameof(InvalidOperationException))
.WriteStartMethodInvocation(FormatInvalidIndexerAssignmentMethodName)
.WriteStringLiteral(node.AttributeName)
.WriteParameterSeparator()
.WriteStringLiteral(node.TagHelper.GetTypeName())
.WriteParameterSeparator()
.WriteStringLiteral(node.PropertyName)
.WriteEndMethodInvocation(endLine: false) // End of method call
.WriteEndMethodInvocation(); // End of new expression / throw statement
}
}
context.CodeWriter
.WriteStartAssignment(GetPropertyAccessor(node))
.Write("(string)")
.Write($"{node.VariableName}.Value")
.WriteLine(";")
.WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddTagHelperAttributeMethodName)
.Write(node.VariableName)
.WriteEndMethodInvocation();
}
private static PreallocatedTagHelperPropertyIntermediateNode FindFirstUseOfIndexer(
TagHelperIntermediateNode tagHelperNode,
PreallocatedTagHelperPropertyIntermediateNode 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 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.FieldName}.{node.PropertyName}";
if (node.IsIndexerNameMatch)
{
var dictionaryKey = node.AttributeName.Substring(node.BoundAttribute.IndexerNamePrefix.Length);
propertyAccessor += $"[\"{dictionaryKey}\"]";
}
return propertyAccessor;
}
}
}