Create TagHelper specific C# code Generation.
- Added TagHelperChunk generation. - Added CSharp visitors to understand TagHelperChunks and render corresponding C# code. - Refactored some code in the CSharpCodeVisitor so it could be utilized in other classes. - Added a CSharpFieldDeclarationVisitor to render declaration pieces for TagHelper's - Added metadata to represent specific TagHelper code generation constructs. #72
This commit is contained in:
parent
0b5f0cd565
commit
50fa3ee3e3
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Microsoft.Internal.Web.Utils
|
namespace Microsoft.Internal.Web.Utils
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +47,11 @@ namespace Microsoft.Internal.Web.Utils
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashCodeCombiner Add<TValue>(TValue value, IEqualityComparer<TValue> comparer)
|
||||||
|
{
|
||||||
|
return Add(comparer.GetHashCode(value));
|
||||||
|
}
|
||||||
|
|
||||||
public static HashCodeCombiner Start()
|
public static HashCodeCombiner Start()
|
||||||
{
|
{
|
||||||
return new HashCodeCombiner();
|
return new HashCodeCombiner();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -56,6 +55,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
new CSharpHelperVisitor(writer, Context).Accept(Tree.Chunks);
|
new CSharpHelperVisitor(writer, Context).Accept(Tree.Chunks);
|
||||||
new CSharpTypeMemberVisitor(writer, Context).Accept(Tree.Chunks);
|
new CSharpTypeMemberVisitor(writer, Context).Accept(Tree.Chunks);
|
||||||
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
|
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
|
||||||
|
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);
|
||||||
|
|
||||||
BuildConstructor(writer);
|
BuildConstructor(writer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
{
|
{
|
||||||
public class CSharpCodeWriter : CodeWriter
|
public class CSharpCodeWriter : CodeWriter
|
||||||
{
|
{
|
||||||
|
private const string InstanceMethodFormat = "{0}.{1}";
|
||||||
|
|
||||||
public CSharpCodeWriter()
|
public CSharpCodeWriter()
|
||||||
{
|
{
|
||||||
LineMappingManager = new LineMappingManager();
|
LineMappingManager = new LineMappingManager();
|
||||||
|
|
@ -208,7 +210,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
return WriteStartMethodInvocation(methodName, new string[0]);
|
return WriteStartMethodInvocation(methodName, new string[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, string[] genericArguments)
|
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, params string[] genericArguments)
|
||||||
{
|
{
|
||||||
Write(methodName);
|
Write(methodName);
|
||||||
|
|
||||||
|
|
@ -235,6 +237,33 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Writes a method invocation for the given instance name.
|
||||||
|
public CSharpCodeWriter WriteInstanceMethodInvocation([NotNull] string instanceName,
|
||||||
|
[NotNull] string methodName,
|
||||||
|
params string[] parameters)
|
||||||
|
{
|
||||||
|
return WriteInstanceMethodInvocation(instanceName, methodName, endLine: true, parameters: parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a method invocation for the given instance name.
|
||||||
|
public CSharpCodeWriter WriteInstanceMethodInvocation([NotNull] string instanceName,
|
||||||
|
[NotNull] string methodName,
|
||||||
|
bool endLine,
|
||||||
|
params string[] parameters)
|
||||||
|
{
|
||||||
|
return WriteMethodInvocation(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName),
|
||||||
|
endLine,
|
||||||
|
parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CSharpCodeWriter WriteStartInstanceMethodInvocation([NotNull] string instanceName,
|
||||||
|
[NotNull] string methodName)
|
||||||
|
{
|
||||||
|
return WriteStartMethodInvocation(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName));
|
||||||
|
}
|
||||||
|
|
||||||
public CSharpCodeWriter WriteMethodInvocation(string methodName, params string[] parameters)
|
public CSharpCodeWriter WriteMethodInvocation(string methodName, params string[] parameters)
|
||||||
{
|
{
|
||||||
return WriteMethodInvocation(methodName, endLine: true, parameters: parameters);
|
return WriteMethodInvocation(methodName, endLine: true, parameters: parameters);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders tag helper rendering code.
|
||||||
|
/// </summary>
|
||||||
|
public class CSharpTagHelperCodeRenderer
|
||||||
|
{
|
||||||
|
internal static readonly string ExecutionContextVariableName = "__tagHelperExecutionContext";
|
||||||
|
internal static readonly string StringValueBufferVariableName = "__tagHelperStringValueBuffer";
|
||||||
|
internal static readonly string ScopeManagerVariableName = "__tagHelperScopeManager";
|
||||||
|
internal static readonly string RunnerVariableName = "__tagHelperRunner";
|
||||||
|
|
||||||
|
private static readonly TagHelperAttributeDescriptorComparer AttributeDescriptorComparer =
|
||||||
|
new TagHelperAttributeDescriptorComparer();
|
||||||
|
|
||||||
|
// TODO: The work to properly implement this will be done in: https://github.com/aspnet/Razor/issues/74
|
||||||
|
private readonly TagHelperAttributeValueCodeRenderer _attributeValueCodeRenderer =
|
||||||
|
new TagHelperAttributeValueCodeRenderer();
|
||||||
|
private readonly CSharpCodeWriter _writer;
|
||||||
|
private readonly CodeBuilderContext _context;
|
||||||
|
private readonly IChunkVisitor _bodyVisitor;
|
||||||
|
private readonly GeneratedTagHelperContext _tagHelperContext;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="CSharpTagHelperCodeRenderer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bodyVisitor">The <see cref="IChunkVisitor"/> used to render chunks found in the body.</param>
|
||||||
|
/// <param name="writer">The <see cref="CSharpCodeWriter"/> used to write code.</param>
|
||||||
|
/// <param name="context">A <see cref="CodeBuilderContext"/> instance that contains information about
|
||||||
|
/// the current code generation process.</param>
|
||||||
|
public CSharpTagHelperCodeRenderer([NotNull] IChunkVisitor bodyVisitor,
|
||||||
|
[NotNull] CSharpCodeWriter writer,
|
||||||
|
[NotNull] CodeBuilderContext context)
|
||||||
|
{
|
||||||
|
_writer = writer;
|
||||||
|
_context = context;
|
||||||
|
_bodyVisitor = bodyVisitor;
|
||||||
|
_tagHelperContext = context.Host.GeneratedClassContext.GeneratedTagHelperContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the code for the given <paramref name="chunk"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param>
|
||||||
|
public void RenderTagHelper(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
// TODO: Implement design time support for tag helpers in https://github.com/aspnet/Razor/issues/83
|
||||||
|
if (_context.Host.DesignTimeMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagHelperDescriptors = chunk.Descriptors;
|
||||||
|
|
||||||
|
// Find the first content behavior that doesn't have a content behavior of None.
|
||||||
|
// The resolver restricts content behavior collisions so the first one that's not None will be
|
||||||
|
// the content behavior we need to abide by. None can work in unison with other ContentBehaviors.
|
||||||
|
var contentBehavior = tagHelperDescriptors.Select(descriptor => descriptor.ContentBehavior)
|
||||||
|
.FirstOrDefault(
|
||||||
|
behavior => behavior != ContentBehavior.None);
|
||||||
|
|
||||||
|
RenderBeginTagHelperScope(chunk.TagName);
|
||||||
|
|
||||||
|
RenderTagHelpersCreation(chunk);
|
||||||
|
|
||||||
|
var attributeDescriptors = tagHelperDescriptors.SelectMany(descriptor => descriptor.Attributes);
|
||||||
|
var boundHTMLAttributes = attributeDescriptors.Select(descriptor => descriptor.AttributeName);
|
||||||
|
var htmlAttributes = chunk.Attributes;
|
||||||
|
var unboundHTMLAttributes =
|
||||||
|
htmlAttributes.Where(htmlAttribute => !boundHTMLAttributes.Contains(htmlAttribute.Key,
|
||||||
|
StringComparer.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
RenderUnboundHTMLAttributes(unboundHTMLAttributes);
|
||||||
|
|
||||||
|
switch (contentBehavior)
|
||||||
|
{
|
||||||
|
case ContentBehavior.None:
|
||||||
|
RenderRunTagHelpers(bufferedBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateStartTagMethodName);
|
||||||
|
RenderTagHelperBody(chunk.Children, bufferBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateEndTagMethodName);
|
||||||
|
break;
|
||||||
|
case ContentBehavior.Append:
|
||||||
|
RenderRunTagHelpers(bufferedBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateStartTagMethodName);
|
||||||
|
RenderTagHelperBody(chunk.Children, bufferBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateContentMethodName);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateEndTagMethodName);
|
||||||
|
break;
|
||||||
|
case ContentBehavior.Prepend:
|
||||||
|
RenderRunTagHelpers(bufferedBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateStartTagMethodName);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateContentMethodName);
|
||||||
|
RenderTagHelperBody(chunk.Children, bufferBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateEndTagMethodName);
|
||||||
|
break;
|
||||||
|
case ContentBehavior.Replace:
|
||||||
|
RenderRunTagHelpers(bufferedBody: false);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateStartTagMethodName);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateContentMethodName);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateEndTagMethodName);
|
||||||
|
break;
|
||||||
|
case ContentBehavior.Modify:
|
||||||
|
RenderTagHelperBody(chunk.Children, bufferBody: true);
|
||||||
|
RenderRunTagHelpers(bufferedBody: true);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateStartTagMethodName);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateContentMethodName);
|
||||||
|
RenderTagOutput(_tagHelperContext.OutputGenerateEndTagMethodName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderEndTagHelpersScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetVariableName(TagHelperDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return "__" + descriptor.TagHelperName.Replace('.', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBeginTagHelperScope(string tagName)
|
||||||
|
{
|
||||||
|
// Call into the tag helper scope manager to start a new tag helper scope.
|
||||||
|
// Also capture the value as the current execution context.
|
||||||
|
_writer.WriteStartAssignment(ExecutionContextVariableName)
|
||||||
|
.WriteStartInstanceMethodInvocation(ScopeManagerVariableName,
|
||||||
|
_tagHelperContext.ScopeManagerBeginMethodName);
|
||||||
|
_writer.WriteStringLiteral(tagName)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTagHelpersCreation(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
var tagHelperDescriptors = chunk.Descriptors;
|
||||||
|
|
||||||
|
// This is to maintain value accessors for attributes when creating the TagHelpers.
|
||||||
|
// Ultimately it enables us to do scenarios like this:
|
||||||
|
// myTagHelper1.Foo = DateTime.Now;
|
||||||
|
// myTagHelper2.Foo = myTagHelper1.Foo;
|
||||||
|
var htmlAttributeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var tagHelperDescriptor in tagHelperDescriptors)
|
||||||
|
{
|
||||||
|
var tagHelperVariableName = GetVariableName(tagHelperDescriptor);
|
||||||
|
|
||||||
|
// Create the tag helper
|
||||||
|
_writer.WriteStartAssignment(tagHelperVariableName)
|
||||||
|
.WriteStartMethodInvocation(_tagHelperContext.CreateTagHelperMethodName,
|
||||||
|
tagHelperDescriptor.TagHelperName)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
|
||||||
|
_writer.WriteInstanceMethodInvocation(ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddMethodName,
|
||||||
|
tagHelperVariableName);
|
||||||
|
|
||||||
|
// Render all of the bound attribute values for the tag helper.
|
||||||
|
RenderBoundHTMLAttributes(chunk.Attributes,
|
||||||
|
tagHelperVariableName,
|
||||||
|
tagHelperDescriptor.Attributes,
|
||||||
|
htmlAttributeValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBoundHTMLAttributes(IDictionary<string, Chunk> chunkAttributes,
|
||||||
|
string tagHelperVariableName,
|
||||||
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
|
Dictionary<string, string> htmlAttributeValues)
|
||||||
|
{
|
||||||
|
foreach (var attributeDescriptor in attributeDescriptors)
|
||||||
|
{
|
||||||
|
Chunk attributeValueChunk;
|
||||||
|
|
||||||
|
var providedAttribute = chunkAttributes.TryGetValue(attributeDescriptor.AttributeName,
|
||||||
|
out attributeValueChunk);
|
||||||
|
|
||||||
|
if (providedAttribute)
|
||||||
|
{
|
||||||
|
var attributeValueRecorded = htmlAttributeValues.ContainsKey(attributeDescriptor.AttributeName);
|
||||||
|
|
||||||
|
// Bufferable attributes are attributes that can have Razor code inside of them.
|
||||||
|
var bufferableAttribute = IsStringAttribute(attributeDescriptor);
|
||||||
|
|
||||||
|
// Plain text values are non Razor code (@DateTime.Now) values. If an attribute is bufferable it
|
||||||
|
// may be more than just a plain text value, it may also contain Razor code which is why we attempt
|
||||||
|
// to retrieve a plain text value here.
|
||||||
|
string textValue;
|
||||||
|
var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);
|
||||||
|
|
||||||
|
// If we haven't recorded a value and we need to buffer an attribute value and the value is not
|
||||||
|
// plain text then we need to prepare the value prior to setting it below.
|
||||||
|
if (!attributeValueRecorded && bufferableAttribute && !isPlainTextValue)
|
||||||
|
{
|
||||||
|
BuildBufferedWritingScope(attributeValueChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We capture the tag helpers property value accessor so we can retrieve it later (if we need to).
|
||||||
|
var valueAccessor = string.Format(CultureInfo.InvariantCulture,
|
||||||
|
"{0}.{1}",
|
||||||
|
tagHelperVariableName,
|
||||||
|
attributeDescriptor.AttributePropertyName);
|
||||||
|
|
||||||
|
_writer.WriteStartAssignment(valueAccessor);
|
||||||
|
|
||||||
|
// If we haven't recorded this attribute value before then we need to record its value.
|
||||||
|
if (!attributeValueRecorded)
|
||||||
|
{
|
||||||
|
// We only need to create attribute values once per HTML element (not once per tag helper).
|
||||||
|
// We're saving the value accessor so we can retrieve it later if there are more tag helpers that
|
||||||
|
// need the value.
|
||||||
|
htmlAttributeValues.Add(attributeDescriptor.AttributeName, valueAccessor);
|
||||||
|
|
||||||
|
if (bufferableAttribute)
|
||||||
|
{
|
||||||
|
// If the attribute is bufferable but has a plain text value that means the value
|
||||||
|
// is a string which needs to be surrounded in quotes.
|
||||||
|
if (isPlainTextValue)
|
||||||
|
{
|
||||||
|
RenderQuotedAttributeValue(textValue, attributeDescriptor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The value contains more than plain text. e.g. someAttribute="Time: @DateTime.Now"
|
||||||
|
RenderBufferedAttributeValue(attributeDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Make complex types in non-bufferable attributes work in
|
||||||
|
// https://github.com/aspnet/Razor/issues/129
|
||||||
|
if (!isPlainTextValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
RazorResources.FormatTagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols(
|
||||||
|
attributeDescriptor.AttributePropertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We aren't a bufferable attribute which means we have no Razor code in our value.
|
||||||
|
// Therefore we can just use the "textValue" as the attribute value.
|
||||||
|
RenderRawAttributeValue(textValue, attributeDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the assignment to the attribute.
|
||||||
|
_writer.WriteLine(";");
|
||||||
|
|
||||||
|
// We need to inform the context of the attribute value.
|
||||||
|
_writer.WriteStartInstanceMethodInvocation(
|
||||||
|
ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName);
|
||||||
|
|
||||||
|
_writer.WriteStringLiteral(attributeDescriptor.AttributeName)
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.Write(valueAccessor)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The attribute value has already been recorded, lets retrieve it from the stored value accessors.
|
||||||
|
_writer.Write(htmlAttributeValues[attributeDescriptor.AttributeName])
|
||||||
|
.WriteLine(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderUnboundHTMLAttributes(IEnumerable<KeyValuePair<string, Chunk>> unboundHTMLAttributes)
|
||||||
|
{
|
||||||
|
// Build out the unbound HTML attributes for the tag builder
|
||||||
|
foreach (var htmlAttribute in unboundHTMLAttributes)
|
||||||
|
{
|
||||||
|
string textValue;
|
||||||
|
var attributeValue = htmlAttribute.Value;
|
||||||
|
var isPlainTextValue = TryGetPlainTextValue(attributeValue, out textValue);
|
||||||
|
|
||||||
|
// HTML attributes are always strings. So if this value is not plain text i.e. if the value contains
|
||||||
|
// C# code, then we need to buffer it.
|
||||||
|
if (!isPlainTextValue)
|
||||||
|
{
|
||||||
|
BuildBufferedWritingScope(attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteStartInstanceMethodInvocation(ExecutionContextVariableName,
|
||||||
|
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName);
|
||||||
|
_writer.WriteStringLiteral(htmlAttribute.Key)
|
||||||
|
.WriteParameterSeparator();
|
||||||
|
|
||||||
|
// If it's a plain text value then we need to surround the value with quotes.
|
||||||
|
if (isPlainTextValue)
|
||||||
|
{
|
||||||
|
_writer.WriteStringLiteral(textValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RenderBufferedAttributeValueAccessor(_writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTagHelperBody(IList<Chunk> children, bool bufferBody)
|
||||||
|
{
|
||||||
|
// If we want to buffer the body we need to create a writing scope to capture the body content.
|
||||||
|
if (bufferBody)
|
||||||
|
{
|
||||||
|
// Render all of the tag helper children in a buffered writing scope.
|
||||||
|
BuildBufferedWritingScope(children);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Render all of the tag helper children.
|
||||||
|
_bodyVisitor.Accept(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderEndTagHelpersScope()
|
||||||
|
{
|
||||||
|
_writer.WriteStartAssignment(ExecutionContextVariableName)
|
||||||
|
.WriteInstanceMethodInvocation(ScopeManagerVariableName,
|
||||||
|
_tagHelperContext.ScopeManagerEndMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTagOutput(string tagOutputMethodName)
|
||||||
|
{
|
||||||
|
CSharpCodeVisitor.RenderPreWriteStart(_writer, _context);
|
||||||
|
|
||||||
|
_writer.Write(ExecutionContextVariableName)
|
||||||
|
.Write(".")
|
||||||
|
.Write(_tagHelperContext.ExecutionContextOutputPropertyName)
|
||||||
|
.Write(".")
|
||||||
|
.WriteMethodInvocation(tagOutputMethodName, endLine: false)
|
||||||
|
.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderRunTagHelpers(bool bufferedBody)
|
||||||
|
{
|
||||||
|
_writer.Write(ExecutionContextVariableName)
|
||||||
|
.Write(".")
|
||||||
|
.Write(_tagHelperContext.ExecutionContextOutputPropertyName)
|
||||||
|
.Write(" = ")
|
||||||
|
.WriteStartInstanceMethodInvocation(RunnerVariableName,
|
||||||
|
_tagHelperContext.RunnerRunAsyncMethodName);
|
||||||
|
_writer.Write(ExecutionContextVariableName);
|
||||||
|
|
||||||
|
if (bufferedBody)
|
||||||
|
{
|
||||||
|
_writer.WriteParameterSeparator()
|
||||||
|
.Write(StringValueBufferVariableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteEndMethodInvocation(endLine: false)
|
||||||
|
.WriteLine(".Result;");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
valueRenderer: (writer) =>
|
||||||
|
{
|
||||||
|
RenderBufferedAttributeValueAccessor(writer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderRawAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
valueRenderer: (writer) =>
|
||||||
|
{
|
||||||
|
writer.Write(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderQuotedAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
RenderAttributeValue(
|
||||||
|
attributeDescriptor,
|
||||||
|
valueRenderer: (writer) =>
|
||||||
|
{
|
||||||
|
writer.WriteStringLiteral(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildBufferedWritingScope(Chunk htmlAttributeChunk)
|
||||||
|
{
|
||||||
|
// Render a buffered writing scope for the html attribute value.
|
||||||
|
BuildBufferedWritingScope(new[] { htmlAttributeChunk });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildBufferedWritingScope(IList<Chunk> chunks)
|
||||||
|
{
|
||||||
|
// 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 oldWriter = _context.TargetWriterName;
|
||||||
|
_context.TargetWriterName = null;
|
||||||
|
|
||||||
|
// Need to disable instrumentation inside of writing scopes, the instrumentation will not detect
|
||||||
|
// content written inside writing scopes.
|
||||||
|
var oldInstrumentation = _context.Host.EnableInstrumentation;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_context.Host.EnableInstrumentation = false;
|
||||||
|
|
||||||
|
_writer.WriteMethodInvocation(_tagHelperContext.StartWritingScopeMethodName);
|
||||||
|
|
||||||
|
_bodyVisitor.Accept(chunks);
|
||||||
|
|
||||||
|
_writer.WriteStartAssignment(StringValueBufferVariableName)
|
||||||
|
.WriteMethodInvocation(_tagHelperContext.EndWritingScopeMethodName);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Reset instrumentation back to what it was, leaving the writing scope.
|
||||||
|
_context.Host.EnableInstrumentation = oldInstrumentation;
|
||||||
|
|
||||||
|
// Reset the writer/buffer back to what it was, leaving the writing scope.
|
||||||
|
_context.TargetWriterName = oldWriter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderAttributeValue(TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
Action<CSharpCodeWriter> valueRenderer)
|
||||||
|
{
|
||||||
|
_attributeValueCodeRenderer.RenderAttributeValue(attributeDescriptor, _writer, _context, valueRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderBufferedAttributeValueAccessor(CSharpCodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteInstanceMethodInvocation(StringValueBufferVariableName,
|
||||||
|
"ToString",
|
||||||
|
endLine: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsStringAttribute(TagHelperAttributeDescriptor attributeDescriptor)
|
||||||
|
{
|
||||||
|
return attributeDescriptor.PropertyInfo.PropertyType == typeof(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetPlainTextValue(Chunk chunk, out string plainText)
|
||||||
|
{
|
||||||
|
var chunkBlock = chunk as ChunkBlock;
|
||||||
|
|
||||||
|
plainText = null;
|
||||||
|
|
||||||
|
if (chunkBlock == null || chunkBlock.Children.Count != 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var literalChildChunk = chunkBlock.Children[0] as LiteralChunk;
|
||||||
|
|
||||||
|
if (literalChildChunk == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText = literalChildChunk.Text;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This class is used to compare tag helper attributes by comparing only the HTML attribute name.
|
||||||
|
private class TagHelperAttributeDescriptorComparer : IEqualityComparer<TagHelperAttributeDescriptor>
|
||||||
|
{
|
||||||
|
public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
|
||||||
|
{
|
||||||
|
return descriptorX.AttributeName.Equals(descriptorY.AttributeName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(TagHelperAttributeDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return StringComparer.OrdinalIgnoreCase.GetHashCode(descriptor.AttributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNet.Razor.Text;
|
using Microsoft.AspNet.Razor.Text;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
{
|
{
|
||||||
|
|
@ -16,11 +17,23 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
private const string TemplateWriterName = "__razor_template_writer";
|
private const string TemplateWriterName = "__razor_template_writer";
|
||||||
|
|
||||||
private CSharpPaddingBuilder _paddingBuilder;
|
private CSharpPaddingBuilder _paddingBuilder;
|
||||||
|
private CSharpTagHelperCodeRenderer _tagHelperCodeRenderer;
|
||||||
|
|
||||||
public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
||||||
: base(writer, context)
|
: base(writer, context)
|
||||||
{
|
{
|
||||||
_paddingBuilder = new CSharpPaddingBuilder(context.Host);
|
_paddingBuilder = new CSharpPaddingBuilder(context.Host);
|
||||||
|
_tagHelperCodeRenderer = new CSharpTagHelperCodeRenderer(this, writer, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Visit(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
_tagHelperCodeRenderer.RenderTagHelper(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Visit(ChunkBlock chunk)
|
||||||
|
{
|
||||||
|
Accept(chunk.Children);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Visit(SetLayoutChunk chunk)
|
protected override void Visit(SetLayoutChunk chunk)
|
||||||
|
|
@ -72,21 +85,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
{
|
{
|
||||||
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrEmpty(Context.TargetWriterName))
|
RenderPreWriteStart();
|
||||||
{
|
|
||||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralToMethodName)
|
|
||||||
.Write(Context.TargetWriterName)
|
|
||||||
.WriteParameterSeparator();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralMethodName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.ResolveUrlMethodName)
|
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.ResolveUrlMethodName)
|
||||||
.WriteStringLiteral(chunk.Url)
|
.WriteStringLiteral(chunk.Url)
|
||||||
.WriteEndMethodInvocation(endLine: false);
|
.WriteEndMethodInvocation(endLine: false);
|
||||||
|
|
||||||
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||||
{
|
{
|
||||||
|
|
@ -115,17 +119,18 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Context.TargetWriterName))
|
if (!string.IsNullOrEmpty(Context.TargetWriterName))
|
||||||
{
|
{
|
||||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralToMethodName)
|
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||||
.Write(Context.TargetWriterName)
|
{
|
||||||
.WriteParameterSeparator();
|
RenderPreWriteStart();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralMethodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
Writer.WriteStringLiteral(chunk.Text)
|
Writer.WriteStringLiteral(chunk.Text);
|
||||||
.WriteEndMethodInvocation();
|
|
||||||
|
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||||
|
{
|
||||||
|
Writer.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Context.Host.EnableInstrumentation)
|
if (Context.Host.EnableInstrumentation)
|
||||||
{
|
{
|
||||||
|
|
@ -187,10 +192,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
|
|
||||||
Writer.WriteParameterSeparator()
|
Writer.WriteParameterSeparator()
|
||||||
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
|
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
|
||||||
.WriteEndMethodInvocation(false)
|
.WriteEndMethodInvocation(endLine: false)
|
||||||
.WriteParameterSeparator()
|
.WriteParameterSeparator()
|
||||||
.WriteBooleanLiteral(false)
|
.WriteBooleanLiteral(value: false)
|
||||||
.WriteEndMethodInvocation(false);
|
.WriteEndMethodInvocation(endLine: false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -479,5 +484,26 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
return Context.Host.EnableInstrumentation &&
|
return Context.Host.EnableInstrumentation &&
|
||||||
Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput;
|
Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CSharpCodeWriter RenderPreWriteStart()
|
||||||
|
{
|
||||||
|
return RenderPreWriteStart(Writer, Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CSharpCodeWriter RenderPreWriteStart(CSharpCodeWriter writer, CodeBuilderContext context)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(context.TargetWriterName))
|
||||||
|
{
|
||||||
|
writer.WriteStartMethodInvocation(context.Host.GeneratedClassContext.WriteLiteralToMethodName)
|
||||||
|
.Write(context.TargetWriterName)
|
||||||
|
.WriteParameterSeparator();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStartMethodInvocation(context.Host.GeneratedClassContext.WriteLiteralMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
|
{
|
||||||
|
public class CSharpTagHelperFieldDeclarationVisitor : CodeVisitor<CSharpCodeWriter>
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _declaredTagHelpers;
|
||||||
|
private readonly GeneratedTagHelperContext _tagHelperContext;
|
||||||
|
private bool _foundTagHelpers;
|
||||||
|
|
||||||
|
public CSharpTagHelperFieldDeclarationVisitor([NotNull] CSharpCodeWriter writer,
|
||||||
|
[NotNull] CodeBuilderContext context)
|
||||||
|
: base(writer, context)
|
||||||
|
{
|
||||||
|
_declaredTagHelpers = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
_tagHelperContext = Context.Host.GeneratedClassContext.GeneratedTagHelperContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Visit(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
// We only want to setup tag helper manager fields if there are tag helpers, and only once
|
||||||
|
if (!_foundTagHelpers)
|
||||||
|
{
|
||||||
|
_foundTagHelpers = true;
|
||||||
|
|
||||||
|
Writer.WriteLineHiddenDirective();
|
||||||
|
|
||||||
|
WritePrivateField(typeof(TextWriter).FullName,
|
||||||
|
CSharpTagHelperCodeRenderer.StringValueBufferVariableName,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
WritePrivateField(_tagHelperContext.ExecutionContextTypeName,
|
||||||
|
CSharpTagHelperCodeRenderer.ExecutionContextVariableName,
|
||||||
|
value: null);
|
||||||
|
|
||||||
|
WritePrivateField(_tagHelperContext.RunnerTypeName,
|
||||||
|
CSharpTagHelperCodeRenderer.RunnerVariableName,
|
||||||
|
"new " + _tagHelperContext.RunnerTypeName + "()");
|
||||||
|
|
||||||
|
WritePrivateField(_tagHelperContext.ScopeManagerTypeName,
|
||||||
|
CSharpTagHelperCodeRenderer.ScopeManagerVariableName,
|
||||||
|
"new " + _tagHelperContext.ScopeManagerTypeName + "()");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var descriptor in chunk.Descriptors)
|
||||||
|
{
|
||||||
|
if (!_declaredTagHelpers.Contains(descriptor.TagHelperName))
|
||||||
|
{
|
||||||
|
_declaredTagHelpers.Add(descriptor.TagHelperName);
|
||||||
|
|
||||||
|
WritePrivateField(descriptor.TagHelperName,
|
||||||
|
CSharpTagHelperCodeRenderer.GetVariableName(descriptor),
|
||||||
|
value: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to dive deeper to ensure we pick up any nested tag helpers.
|
||||||
|
Accept(chunk.Children);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Accept(Chunk chunk)
|
||||||
|
{
|
||||||
|
var chunkBlock = chunk as ChunkBlock;
|
||||||
|
|
||||||
|
// If we're any ChunkBlock other than TagHelperChunk then we want to dive into its Children
|
||||||
|
// to search for more TagHelperChunk chunks. This if-statement enables us to not override
|
||||||
|
// each of the special ChunkBlock types and then dive into their children.
|
||||||
|
if (chunkBlock != null && !(chunkBlock is TagHelperChunk))
|
||||||
|
{
|
||||||
|
Accept(chunkBlock.Children);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we're a TagHelperChunk or any other non ChunkBlock we ".Accept" it. This ensures
|
||||||
|
// that our overriden Visit(TagHelperChunk) method gets called and is not skipped over.
|
||||||
|
// If we're a non ChunkBlock or a TagHelperChunk then we want to just invoke the Visit
|
||||||
|
// method for that given chunk (base.Accept indirectly calls the Visit method).
|
||||||
|
base.Accept(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WritePrivateField(string type, string name, string value)
|
||||||
|
{
|
||||||
|
Writer.Write("private ")
|
||||||
|
.WriteVariableDeclaration(type, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
{
|
{
|
||||||
public class CSharpUsingVisitor : CodeVisitor<CSharpCodeWriter>
|
public class CSharpUsingVisitor : CodeVisitor<CSharpCodeWriter>
|
||||||
{
|
{
|
||||||
|
private const string TagHelpersRuntimeNamespace = "Microsoft.AspNet.Razor.Runtime.TagHelpers";
|
||||||
|
|
||||||
|
private bool _foundTagHelpers;
|
||||||
|
|
||||||
public CSharpUsingVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
public CSharpUsingVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
||||||
: base(writer, context)
|
: base(writer, context)
|
||||||
{
|
{
|
||||||
|
|
@ -46,5 +50,20 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||||
Writer.WriteLine(";");
|
Writer.WriteLine(";");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Visit(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
if (!_foundTagHelpers)
|
||||||
|
{
|
||||||
|
_foundTagHelpers = true;
|
||||||
|
|
||||||
|
if (!ImportedUsings.Contains(TagHelpersRuntimeNamespace))
|
||||||
|
{
|
||||||
|
// If we find TagHelpers then we need to add the TagHelper runtime namespace to our list of usings.
|
||||||
|
Writer.WriteUsing(TagHelpersRuntimeNamespace);
|
||||||
|
ImportedUsings.Add(TagHelpersRuntimeNamespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
|
||||||
{
|
{
|
||||||
Visit((StatementChunk)chunk);
|
Visit((StatementChunk)chunk);
|
||||||
}
|
}
|
||||||
|
else if(chunk is TagHelperChunk)
|
||||||
|
{
|
||||||
|
Visit((TagHelperChunk)chunk);
|
||||||
|
}
|
||||||
else if(chunk is SetLayoutChunk)
|
else if(chunk is SetLayoutChunk)
|
||||||
{
|
{
|
||||||
Visit((SetLayoutChunk)chunk);
|
Visit((SetLayoutChunk)chunk);
|
||||||
|
|
@ -110,6 +114,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
|
||||||
protected abstract void Visit(LiteralChunk chunk);
|
protected abstract void Visit(LiteralChunk chunk);
|
||||||
protected abstract void Visit(ExpressionChunk chunk);
|
protected abstract void Visit(ExpressionChunk chunk);
|
||||||
protected abstract void Visit(StatementChunk chunk);
|
protected abstract void Visit(StatementChunk chunk);
|
||||||
|
protected abstract void Visit(TagHelperChunk chunk);
|
||||||
protected abstract void Visit(UsingChunk chunk);
|
protected abstract void Visit(UsingChunk chunk);
|
||||||
protected abstract void Visit(ChunkBlock chunk);
|
protected abstract void Visit(ChunkBlock chunk);
|
||||||
protected abstract void Visit(DynamicCodeAttributeChunk chunk);
|
protected abstract void Visit(DynamicCodeAttributeChunk chunk);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
|
||||||
protected override void Visit(DynamicCodeAttributeChunk chunk)
|
protected override void Visit(DynamicCodeAttributeChunk chunk)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
protected override void Visit(TagHelperChunk chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
protected override void Visit(LiteralCodeAttributeChunk chunk)
|
protected override void Visit(LiteralCodeAttributeChunk chunk)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Generator.Compiler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ChunkBlock"/> that represents a special HTML tag.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperChunk : ChunkBlock
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These attributes are <see cref="string"/> => <see cref="Chunk"/> so attribute values can consist
|
||||||
|
/// of all sorts of Razor specific pieces.
|
||||||
|
/// </remarks>
|
||||||
|
public IDictionary<string, Chunk> Attributes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="TagHelperDescriptor"/>s that are associated with the tag helpers HTML element.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<TagHelperDescriptor> Descriptors { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HTML tag name.
|
||||||
|
/// </summary>
|
||||||
|
public string TagName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -146,13 +146,18 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
|
||||||
|
|
||||||
public T StartChunkBlock<T>(SyntaxTreeNode association, bool topLevel) where T : ChunkBlock, new()
|
public T StartChunkBlock<T>(SyntaxTreeNode association, bool topLevel) where T : ChunkBlock, new()
|
||||||
{
|
{
|
||||||
var chunk = new T();
|
var chunkBlock = new T();
|
||||||
|
|
||||||
AddChunk(chunk, association, topLevel);
|
return StartChunkBlock<T>(chunkBlock, association, topLevel);
|
||||||
|
}
|
||||||
|
|
||||||
_blockChain.Push(chunk);
|
public T StartChunkBlock<T>(T chunkBlock, SyntaxTreeNode association, bool topLevel) where T : ChunkBlock
|
||||||
|
{
|
||||||
|
AddChunk(chunkBlock, association, topLevel);
|
||||||
|
|
||||||
return chunk;
|
_blockChain.Push(chunkBlock);
|
||||||
|
|
||||||
|
return chunkBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndChunkBlock()
|
public void EndChunkBlock()
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.Internal.Web.Utils;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Generator
|
namespace Microsoft.AspNet.Razor.Generator
|
||||||
{
|
{
|
||||||
|
|
@ -17,35 +15,39 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
public static readonly string DefaultWriteAttributeMethodName = "WriteAttribute";
|
public static readonly string DefaultWriteAttributeMethodName = "WriteAttribute";
|
||||||
public static readonly string DefaultWriteAttributeToMethodName = "WriteAttributeTo";
|
public static readonly string DefaultWriteAttributeToMethodName = "WriteAttributeTo";
|
||||||
|
|
||||||
public static readonly GeneratedClassContext Default = new GeneratedClassContext(DefaultExecuteMethodName,
|
public static readonly GeneratedClassContext Default =
|
||||||
DefaultWriteMethodName,
|
new GeneratedClassContext(DefaultExecuteMethodName,
|
||||||
DefaultWriteLiteralMethodName);
|
DefaultWriteMethodName,
|
||||||
|
DefaultWriteLiteralMethodName,
|
||||||
|
new GeneratedTagHelperContext());
|
||||||
|
|
||||||
public GeneratedClassContext(string executeMethodName, string writeMethodName, string writeLiteralMethodName)
|
public GeneratedClassContext(string executeMethodName,
|
||||||
|
string writeMethodName,
|
||||||
|
string writeLiteralMethodName,
|
||||||
|
[NotNull] GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
: this()
|
: this()
|
||||||
{
|
{
|
||||||
if (String.IsNullOrEmpty(executeMethodName))
|
if (string.IsNullOrEmpty(executeMethodName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
throw new ArgumentException(
|
||||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||||
"executeMethodName"),
|
nameof(executeMethodName));
|
||||||
"executeMethodName");
|
|
||||||
}
|
}
|
||||||
if (String.IsNullOrEmpty(writeMethodName))
|
if (string.IsNullOrEmpty(writeMethodName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
throw new ArgumentException(
|
||||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||||
"writeMethodName"),
|
nameof(writeMethodName));
|
||||||
"writeMethodName");
|
|
||||||
}
|
}
|
||||||
if (String.IsNullOrEmpty(writeLiteralMethodName))
|
if (string.IsNullOrEmpty(writeLiteralMethodName))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
throw new ArgumentException(
|
||||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||||
"writeLiteralMethodName"),
|
nameof(writeLiteralMethodName));
|
||||||
"writeLiteralMethodName");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GeneratedTagHelperContext = generatedTagHelperContext;
|
||||||
|
|
||||||
WriteMethodName = writeMethodName;
|
WriteMethodName = writeMethodName;
|
||||||
WriteLiteralMethodName = writeLiteralMethodName;
|
WriteLiteralMethodName = writeLiteralMethodName;
|
||||||
ExecuteMethodName = executeMethodName;
|
ExecuteMethodName = executeMethodName;
|
||||||
|
|
@ -65,8 +67,12 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
string writeLiteralMethodName,
|
string writeLiteralMethodName,
|
||||||
string writeToMethodName,
|
string writeToMethodName,
|
||||||
string writeLiteralToMethodName,
|
string writeLiteralToMethodName,
|
||||||
string templateTypeName)
|
string templateTypeName,
|
||||||
: this(executeMethodName, writeMethodName, writeLiteralMethodName)
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this(executeMethodName,
|
||||||
|
writeMethodName,
|
||||||
|
writeLiteralMethodName,
|
||||||
|
generatedTagHelperContext)
|
||||||
{
|
{
|
||||||
WriteToMethodName = writeToMethodName;
|
WriteToMethodName = writeToMethodName;
|
||||||
WriteLiteralToMethodName = writeLiteralToMethodName;
|
WriteLiteralToMethodName = writeLiteralToMethodName;
|
||||||
|
|
@ -79,8 +85,15 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
string writeToMethodName,
|
string writeToMethodName,
|
||||||
string writeLiteralToMethodName,
|
string writeLiteralToMethodName,
|
||||||
string templateTypeName,
|
string templateTypeName,
|
||||||
string defineSectionMethodName)
|
string defineSectionMethodName,
|
||||||
: this(executeMethodName, writeMethodName, writeLiteralMethodName, writeToMethodName, writeLiteralToMethodName, templateTypeName)
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this(executeMethodName,
|
||||||
|
writeMethodName,
|
||||||
|
writeLiteralMethodName,
|
||||||
|
writeToMethodName,
|
||||||
|
writeLiteralToMethodName,
|
||||||
|
templateTypeName,
|
||||||
|
generatedTagHelperContext)
|
||||||
{
|
{
|
||||||
DefineSectionMethodName = defineSectionMethodName;
|
DefineSectionMethodName = defineSectionMethodName;
|
||||||
}
|
}
|
||||||
|
|
@ -93,18 +106,28 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
string templateTypeName,
|
string templateTypeName,
|
||||||
string defineSectionMethodName,
|
string defineSectionMethodName,
|
||||||
string beginContextMethodName,
|
string beginContextMethodName,
|
||||||
string endContextMethodName)
|
string endContextMethodName,
|
||||||
: this(executeMethodName, writeMethodName, writeLiteralMethodName, writeToMethodName, writeLiteralToMethodName, templateTypeName, defineSectionMethodName)
|
GeneratedTagHelperContext generatedTagHelperContext)
|
||||||
|
: this(executeMethodName,
|
||||||
|
writeMethodName,
|
||||||
|
writeLiteralMethodName,
|
||||||
|
writeToMethodName,
|
||||||
|
writeLiteralToMethodName,
|
||||||
|
templateTypeName,
|
||||||
|
defineSectionMethodName,
|
||||||
|
generatedTagHelperContext)
|
||||||
{
|
{
|
||||||
BeginContextMethodName = beginContextMethodName;
|
BeginContextMethodName = beginContextMethodName;
|
||||||
EndContextMethodName = endContextMethodName;
|
EndContextMethodName = endContextMethodName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Required Items
|
||||||
public string WriteMethodName { get; private set; }
|
public string WriteMethodName { get; private set; }
|
||||||
public string WriteLiteralMethodName { get; private set; }
|
public string WriteLiteralMethodName { get; private set; }
|
||||||
public string WriteToMethodName { get; private set; }
|
public string WriteToMethodName { get; private set; }
|
||||||
public string WriteLiteralToMethodName { get; private set; }
|
public string WriteLiteralToMethodName { get; private set; }
|
||||||
public string ExecuteMethodName { get; private set; }
|
public string ExecuteMethodName { get; private set; }
|
||||||
|
public GeneratedTagHelperContext GeneratedTagHelperContext { get; private set; }
|
||||||
|
|
||||||
// Optional Items
|
// Optional Items
|
||||||
public string BeginContextMethodName { get; set; }
|
public string BeginContextMethodName { get; set; }
|
||||||
|
|
@ -120,17 +143,17 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
|
|
||||||
public bool AllowSections
|
public bool AllowSections
|
||||||
{
|
{
|
||||||
get { return !String.IsNullOrEmpty(DefineSectionMethodName); }
|
get { return !string.IsNullOrEmpty(DefineSectionMethodName); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AllowTemplates
|
public bool AllowTemplates
|
||||||
{
|
{
|
||||||
get { return !String.IsNullOrEmpty(TemplateTypeName); }
|
get { return !string.IsNullOrEmpty(TemplateTypeName); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SupportsInstrumentation
|
public bool SupportsInstrumentation
|
||||||
{
|
{
|
||||||
get { return !String.IsNullOrEmpty(BeginContextMethodName) && !String.IsNullOrEmpty(EndContextMethodName); }
|
get { return !string.IsNullOrEmpty(BeginContextMethodName) && !string.IsNullOrEmpty(EndContextMethodName); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
|
|
@ -139,16 +162,16 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
GeneratedClassContext other = (GeneratedClassContext)obj;
|
var other = (GeneratedClassContext)obj;
|
||||||
return String.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
|
return string.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
|
string.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
|
string.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
|
string.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
|
string.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
|
string.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
|
string.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
|
||||||
String.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
|
string.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
|
||||||
String.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
|
string.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.Generator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains necessary information for the tag helper code generation process.
|
||||||
|
/// </summary>
|
||||||
|
public class GeneratedTagHelperContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="GeneratedTagHelperContext"/> with default values.
|
||||||
|
/// </summary>
|
||||||
|
public GeneratedTagHelperContext()
|
||||||
|
{
|
||||||
|
CreateTagHelperMethodName = "CreateTagHelper";
|
||||||
|
RunnerRunAsyncMethodName = "RunAsync";
|
||||||
|
ScopeManagerBeginMethodName = "Begin";
|
||||||
|
ScopeManagerEndMethodName = "End";
|
||||||
|
OutputGenerateStartTagMethodName = "GenerateStartTag";
|
||||||
|
OutputGenerateContentMethodName = "GenerateContent";
|
||||||
|
OutputGenerateEndTagMethodName = "GenerateEndTag";
|
||||||
|
ExecutionContextAddMethodName = "Add";
|
||||||
|
ExecutionContextAddTagHelperAttributeMethodName = "AddTagHelperAttribute";
|
||||||
|
ExecutionContextAddHtmlAttributeMethodName = "AddHtmlAttribute";
|
||||||
|
ExecutionContextOutputPropertyName = "Output";
|
||||||
|
StartWritingScopeMethodName = "StartWritingScope";
|
||||||
|
EndWritingScopeMethodName = "EndWritingScope";
|
||||||
|
RunnerTypeName = "TagHelperRunner";
|
||||||
|
ScopeManagerTypeName = "TagHelperScopeManager";
|
||||||
|
ExecutionContextTypeName = "TagHelperExecutionContext";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to create a tag helper.
|
||||||
|
/// </summary>
|
||||||
|
public string CreateTagHelperMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="RunnerTypeName"/> method used to run tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string RunnerRunAsyncMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to start a scope.
|
||||||
|
/// </summary>
|
||||||
|
public string ScopeManagerBeginMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to end a scope.
|
||||||
|
/// </summary>
|
||||||
|
public string ScopeManagerEndMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to generate a tag helper output's start tag.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputGenerateStartTagMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to generate a tag helper output's content.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputGenerateContentMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to generate a tag helper output's end tag.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputGenerateEndTagMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add tag helper attributes.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddTagHelperAttributeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add HTML attributes.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddHtmlAttributeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextAddMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The property accessor for the tag helper's output.
|
||||||
|
/// </summary>
|
||||||
|
public string ExecutionContextOutputPropertyName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to start a new writing scope.
|
||||||
|
/// </summary>
|
||||||
|
public string StartWritingScopeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the method used to end a writing scope.
|
||||||
|
/// </summary>
|
||||||
|
public string EndWritingScopeMethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type used to run tag helpers.
|
||||||
|
/// </summary>
|
||||||
|
public string RunnerTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type used to create scoped <see cref="ExecutionContextTypeName"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
public string ScopeManagerTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the type describing a specific tag helper scope.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Contains information about in-scope tag helpers, HTML attributes, and the tag helpers' output.
|
||||||
|
/// </remarks>
|
||||||
|
public string ExecutionContextTypeName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.AspNet.Razor.Generator.Compiler;
|
||||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.Parser.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Generator
|
namespace Microsoft.AspNet.Razor.Generator
|
||||||
{
|
{
|
||||||
|
|
@ -10,6 +15,19 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TagHelperCodeGenerator : BlockCodeGenerator
|
public class TagHelperCodeGenerator : BlockCodeGenerator
|
||||||
{
|
{
|
||||||
|
private IEnumerable<TagHelperDescriptor> _tagHelperDescriptors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new <see cref="TagHelperCodeGenerator"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagHelperDescriptors">
|
||||||
|
/// <see cref="TagHelperDescriptor"/>s associated with the current HTML tag.
|
||||||
|
/// </param>
|
||||||
|
public TagHelperCodeGenerator(IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||||
|
{
|
||||||
|
_tagHelperDescriptors = tagHelperDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the generation of a <see cref="TagHelperChunk"/>.
|
/// Starts the generation of a <see cref="TagHelperChunk"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -20,11 +38,52 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
/// the current code generation process.</param>
|
/// the current code generation process.</param>
|
||||||
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
|
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
|
||||||
{
|
{
|
||||||
|
var tagHelperBlock = target as TagHelperBlock;
|
||||||
|
|
||||||
|
if (tagHelperBlock == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
RazorResources.TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = new Dictionary<string, Chunk>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// We need to create a code generator to create chunks for each of the attributes.
|
||||||
|
var codeGenerator = context.Host.CreateCodeGenerator(
|
||||||
|
context.ClassName,
|
||||||
|
context.RootNamespace,
|
||||||
|
context.SourceFile);
|
||||||
|
|
||||||
|
foreach (var attribute in tagHelperBlock.Attributes)
|
||||||
|
{
|
||||||
|
// Populates the code tree with chunks associated with attributes
|
||||||
|
attribute.Value.Accept(codeGenerator);
|
||||||
|
|
||||||
|
var chunks = codeGenerator.Context.CodeTreeBuilder.CodeTree.Chunks;
|
||||||
|
|
||||||
|
attributes[attribute.Key] = new ChunkBlock
|
||||||
|
{
|
||||||
|
Children = chunks
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset the code tree builder so we can build a new one for the next attribute
|
||||||
|
codeGenerator.Context.CodeTreeBuilder = new CodeTreeBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeTreeBuilder.StartChunkBlock(
|
||||||
|
new TagHelperChunk
|
||||||
|
{
|
||||||
|
TagName = tagHelperBlock.TagName,
|
||||||
|
Attributes = attributes,
|
||||||
|
Descriptors = _tagHelperDescriptors
|
||||||
|
},
|
||||||
|
target,
|
||||||
|
topLevel: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ends the generation of a <see cref="TagHelperChunk"/> capturing all previously visited children
|
/// Ends the generation of a <see cref="TagHelperChunk"/> capturing all previously visited children
|
||||||
/// since <see cref="GenerateStartBlockCode"/> method was called.
|
/// since the <see cref="GenerateStartBlockCode"/> method was called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">
|
/// <param name="target">
|
||||||
/// The <see cref="Block"/> responsible for this <see cref="TagHelperCodeGenerator"/>.
|
/// The <see cref="Block"/> responsible for this <see cref="TagHelperCodeGenerator"/>.
|
||||||
|
|
@ -33,6 +92,7 @@ namespace Microsoft.AspNet.Razor.Generator
|
||||||
/// the current code generation process.</param>
|
/// the current code generation process.</param>
|
||||||
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
|
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
|
||||||
{
|
{
|
||||||
|
context.CodeTreeBuilder.EndChunkBlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Razor.Generator;
|
using Microsoft.AspNet.Razor.Generator;
|
||||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
||||||
|
|
@ -33,12 +34,14 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
||||||
/// and <see cref="BlockBuilder.Type"/> from the <paramref name="startTag"/>.
|
/// and <see cref="BlockBuilder.Type"/> from the <paramref name="startTag"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tagName">An HTML tag name.</param>
|
/// <param name="tagName">An HTML tag name.</param>
|
||||||
|
/// <param name="descriptors">The <see cref="TagHelperDescriptor"/>s associated with the current HTML
|
||||||
|
/// tag.</param>
|
||||||
/// <param name="startTag">The <see cref="Block"/> that contains all information about the start
|
/// <param name="startTag">The <see cref="Block"/> that contains all information about the start
|
||||||
/// of the HTML element.</param>
|
/// of the HTML element.</param>
|
||||||
public TagHelperBlockBuilder(string tagName, Block startTag)
|
public TagHelperBlockBuilder(string tagName, IEnumerable<TagHelperDescriptor> descriptors, Block startTag)
|
||||||
{
|
{
|
||||||
TagName = tagName;
|
TagName = tagName;
|
||||||
CodeGenerator = new TagHelperCodeGenerator();
|
CodeGenerator = new TagHelperCodeGenerator(descriptors);
|
||||||
Type = startTag.Type;
|
Type = startTag.Type;
|
||||||
Attributes = GetTagAttributes(startTag);
|
Attributes = GetTagAttributes(startTag);
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +54,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
||||||
TagName = tagName;
|
TagName = tagName;
|
||||||
Attributes = attributes;
|
Attributes = attributes;
|
||||||
Type = BlockType.Tag;
|
Type = BlockType.Tag;
|
||||||
CodeGenerator = new TagHelperCodeGenerator();
|
CodeGenerator = new TagHelperCodeGenerator(tagHelperDescriptors: null);
|
||||||
|
|
||||||
// Children is IList, no AddRange
|
// Children is IList, no AddRange
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
|
|
|
||||||
|
|
@ -65,19 +65,25 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
// We're in a begin tag block
|
// We're in a begin tag block
|
||||||
|
|
||||||
if (IsPotentialTagHelper(tagName, childBlock) && IsRegisteredTagHelper(tagName))
|
if (IsPotentialTagHelper(tagName, childBlock))
|
||||||
{
|
{
|
||||||
// Found a new tag helper block
|
var descriptors = _provider.GetTagHelpers(tagName);
|
||||||
TrackTagHelperBlock(new TagHelperBlockBuilder(tagName, childBlock));
|
|
||||||
|
|
||||||
// If it's a self closing block then we don't have to worry about nested children
|
// We could be a tag helper, but only if we have descriptors registered
|
||||||
// within the tag... complete it.
|
if (descriptors.Any())
|
||||||
if (IsSelfClosing(childBlock))
|
|
||||||
{
|
{
|
||||||
BuildCurrentlyTrackedTagHelperBlock();
|
// Found a new tag helper block
|
||||||
}
|
TrackTagHelperBlock(new TagHelperBlockBuilder(tagName, descriptors, childBlock));
|
||||||
|
|
||||||
continue;
|
// If it's a self closing block then we don't have to worry about nested children
|
||||||
|
// within the tag... complete it.
|
||||||
|
if (IsSelfClosing(childBlock))
|
||||||
|
{
|
||||||
|
BuildCurrentlyTrackedTagHelperBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -1462,6 +1462,38 @@ namespace Microsoft.AspNet.Razor
|
||||||
return GetString("TagHelpers_CannotHaveCSharpInTagDeclaration");
|
return GetString("TagHelpers_CannotHaveCSharpInTagDeclaration");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A TagHelperCodeGenerator must only be used with TagHelperBlocks.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A TagHelperCodeGenerator must only be used with TagHelperBlocks.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock()
|
||||||
|
{
|
||||||
|
return GetString("TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TagHelper attributes that do not expect strings must not have @ symbols within them. Found attribute '{0}' with an invalid value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string TagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols
|
||||||
|
{
|
||||||
|
get { return GetString("TagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TagHelper attributes that do not expect strings must not have @ symbols within them. Found attribute '{0}' with an invalid value.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatTagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -215,5 +215,13 @@ namespace Microsoft.AspNet.Razor
|
||||||
}
|
}
|
||||||
return incomingBuilder;
|
return incomingBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a user wants to modify the code generation process they do it via the DecorateCodeGenerator method which
|
||||||
|
// is why this is internal.
|
||||||
|
internal RazorCodeGenerator CreateCodeGenerator(string className, string rootNamespace, string sourceFileName)
|
||||||
|
{
|
||||||
|
return DecorateCodeGenerator(
|
||||||
|
CodeLanguage.CreateCodeGenerator(className, rootNamespace, sourceFileName, host: this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -409,4 +409,10 @@ Instead, wrap the contents of the block in "{{}}":
|
||||||
<data name="TagHelpers_CannotHaveCSharpInTagDeclaration" xml:space="preserve">
|
<data name="TagHelpers_CannotHaveCSharpInTagDeclaration" xml:space="preserve">
|
||||||
<value>Tag Helpers cannot have C# in an HTML tag element's attribute declaration area.</value>
|
<value>Tag Helpers cannot have C# in an HTML tag element's attribute declaration area.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TagHelpers_TagHelperCodeGeneartorMustBeAssociatedWithATagHelperBlock" xml:space="preserve">
|
||||||
|
<value>A TagHelperCodeGenerator must only be used with TagHelperBlocks.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TagHelpers_AttributesThatAreNotStringsMustNotContainAtSymbols" xml:space="preserve">
|
||||||
|
<value>TagHelper attributes that do not expect strings must not have @ symbols within them. Found attribute '{0}' with an invalid value.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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 System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNet.Razor.Generator;
|
||||||
|
using Microsoft.AspNet.Razor.Generator.Compiler.CSharp;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders code for tag helper property initialization.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperAttributeValueCodeRenderer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called during Razor's code generation process to generate code that instantiates the value of the tag
|
||||||
|
/// helper's property. Last value written should not be or end with a semicolon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributeDescriptor">The <see cref="TagHelperAttributeDescriptor"/> to generate code for.</param>
|
||||||
|
/// <param name="writer">The <see cref="CSharpCodeWriter"/> that's used to write code.</param>
|
||||||
|
/// <param name="context">A <see cref="CodeGeneratorContext"/> instance that contains information about
|
||||||
|
/// the current code generation process.</param>
|
||||||
|
/// <param name="renderAttributeValue"><see cref="Action"/> that renders the raw value of the HTML attribute.</param>
|
||||||
|
public void RenderAttributeValue([NotNull] TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
|
[NotNull] CSharpCodeWriter writer,
|
||||||
|
[NotNull] CodeBuilderContext context,
|
||||||
|
[NotNull] Action<CSharpCodeWriter> renderAttributeValue)
|
||||||
|
{
|
||||||
|
renderAttributeValue(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.Internal.Web.Utils;
|
using Microsoft.Internal.Web.Utils;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
|
@ -20,14 +21,35 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
/// <param name="tagHelperName">The full name of the tag helper class.</param>
|
/// <param name="tagHelperName">The full name of the tag helper class.</param>
|
||||||
/// <param name="contentBehavior">The <see cref="TagHelpers.ContentBehavior"/>
|
/// <param name="contentBehavior">The <see cref="TagHelpers.ContentBehavior"/>
|
||||||
/// of the tag helper.</param>
|
/// of the tag helper.</param>
|
||||||
public TagHelperDescriptor(string tagName,
|
public TagHelperDescriptor([NotNull] string tagName,
|
||||||
string tagHelperName,
|
[NotNull] string tagHelperName,
|
||||||
ContentBehavior contentBehavior)
|
ContentBehavior contentBehavior)
|
||||||
|
: this(tagName, tagHelperName, contentBehavior, Enumerable.Empty<TagHelperAttributeDescriptor>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantiates a new instance of the <see cref="TagHelperDescriptor"/> class with the given
|
||||||
|
/// <paramref name="attributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">The tag name that the tag helper targets. '*' indicates a catch-all
|
||||||
|
/// <see cref="TagHelperDescriptor"/> which applies to every HTML tag.</param>
|
||||||
|
/// <param name="tagHelperName">The code class used to render the tag helper. Corresponds to
|
||||||
|
/// the tag helper's <see cref="Type.FullName"/>.</param>
|
||||||
|
/// <param name="contentBehavior">The <see cref="TagHelpers.ContentBehavior"/>
|
||||||
|
/// of the tag helper.</param>
|
||||||
|
/// <param name="attributes">
|
||||||
|
/// The <see cref="TagHelperAttributeDescriptor"/>s to request from the HTML tag.
|
||||||
|
/// </param>
|
||||||
|
public TagHelperDescriptor([NotNull] string tagName,
|
||||||
|
[NotNull] string tagHelperName,
|
||||||
|
ContentBehavior contentBehavior,
|
||||||
|
[NotNull] IEnumerable<TagHelperAttributeDescriptor> attributes)
|
||||||
{
|
{
|
||||||
TagName = tagName;
|
TagName = tagName;
|
||||||
TagHelperName = tagHelperName;
|
TagHelperName = tagHelperName;
|
||||||
ContentBehavior = contentBehavior;
|
ContentBehavior = contentBehavior;
|
||||||
Attributes = new List<TagHelperAttributeDescriptor>();
|
Attributes = new List<TagHelperAttributeDescriptor>(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.Internal.Web.Utils;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a an <see cref="IEqualityComparer{TagHelperDescriptor}"/> that is used to check equality between
|
||||||
|
/// two <see cref="TagHelperDescriptor"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class TagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A default instance of the <see cref="TagHelperDescriptorComparer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly TagHelperDescriptorComparer Default = new TagHelperDescriptorComparer();
|
||||||
|
|
||||||
|
private TagHelperDescriptorComparer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the two given tag helpers are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptorX">A <see cref="TagHelperDescriptor"/> to compare with the given
|
||||||
|
/// <paramref name="descriptorY"/>.</param>
|
||||||
|
/// <param name="descriptorY">A <see cref="TagHelperDescriptor"/> to compare with the given
|
||||||
|
/// <paramref name="descriptorX"/>.</param>
|
||||||
|
/// <returns><c>true</c> if <paramref name="descriptorX"/> and <paramref name="descriptorY"/> are equal,
|
||||||
|
/// <c>false</c> otherwise.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Determines equality based on <see cref="TagHelperDescriptor.TagHelperName"/>,
|
||||||
|
/// <see cref="TagHelperDescriptor.TagName"/> and <see cref="TagHelperDescriptor.ContentBehavior"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
|
||||||
|
{
|
||||||
|
return string.Equals(descriptorX.TagHelperName, descriptorY.TagHelperName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
descriptorX.ContentBehavior == descriptorY.ContentBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an <see cref="int"/> value that uniquely identifies the given <see cref="TagHelperDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The <see cref="TagHelperDescriptor"/> to create a hash code for.</param>
|
||||||
|
/// <returns>An <see cref="int"/> that uniquely identifies the given <paramref name="descriptor"/>.</returns>
|
||||||
|
public int GetHashCode(TagHelperDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return HashCodeCombiner.Start()
|
||||||
|
.Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Add(descriptor.TagHelperName, StringComparer.Ordinal)
|
||||||
|
.Add(descriptor.ContentBehavior)
|
||||||
|
.CombinedHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,9 +15,6 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
{
|
{
|
||||||
private const string CatchAllDescriptorTarget = "*";
|
private const string CatchAllDescriptorTarget = "*";
|
||||||
|
|
||||||
private static readonly TagHelperDescriptorComparer DefaultTagHelperDescriptorComparer =
|
|
||||||
new TagHelperDescriptorComparer();
|
|
||||||
|
|
||||||
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
|
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -79,30 +76,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
// Ensure there's a List to add the descriptor to.
|
// Ensure there's a List to add the descriptor to.
|
||||||
if (!_registrations.TryGetValue(descriptor.TagName, out descriptorSet))
|
if (!_registrations.TryGetValue(descriptor.TagName, out descriptorSet))
|
||||||
{
|
{
|
||||||
descriptorSet = new HashSet<TagHelperDescriptor>(DefaultTagHelperDescriptorComparer);
|
descriptorSet = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
|
||||||
_registrations[descriptor.TagName] = descriptorSet;
|
_registrations[descriptor.TagName] = descriptorSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptorSet.Add(descriptor);
|
descriptorSet.Add(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
|
|
||||||
{
|
|
||||||
public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
|
|
||||||
{
|
|
||||||
return descriptorX.TagHelperName == descriptorY.TagHelperName &&
|
|
||||||
descriptorX.TagName == descriptorY.TagName &&
|
|
||||||
descriptorX.ContentBehavior == descriptorY.ContentBehavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(TagHelperDescriptor descriptor)
|
|
||||||
{
|
|
||||||
return HashCodeCombiner.Start()
|
|
||||||
.Add(descriptor.TagName)
|
|
||||||
.Add(descriptor.TagHelperName)
|
|
||||||
.Add(descriptor.ContentBehavior)
|
|
||||||
.CombinedHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue