зеркало из https://github.com/dotnet/razor.git
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:
Родитель
0b5f0cd565
Коммит
50fa3ee3e3
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Internal.Web.Utils
|
||||
{
|
||||
|
@ -46,6 +47,11 @@ namespace Microsoft.Internal.Web.Utils
|
|||
return this;
|
||||
}
|
||||
|
||||
public HashCodeCombiner Add<TValue>(TValue value, IEqualityComparer<TValue> comparer)
|
||||
{
|
||||
return Add(comparer.GetHashCode(value));
|
||||
}
|
||||
|
||||
public static HashCodeCombiner Start()
|
||||
{
|
||||
return new HashCodeCombiner();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -56,6 +55,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
new CSharpHelperVisitor(writer, Context).Accept(Tree.Chunks);
|
||||
new CSharpTypeMemberVisitor(writer, Context).Accept(Tree.Chunks);
|
||||
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
|
||||
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);
|
||||
|
||||
BuildConstructor(writer);
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
{
|
||||
public class CSharpCodeWriter : CodeWriter
|
||||
{
|
||||
private const string InstanceMethodFormat = "{0}.{1}";
|
||||
|
||||
public CSharpCodeWriter()
|
||||
{
|
||||
LineMappingManager = new LineMappingManager();
|
||||
|
@ -208,7 +210,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
return WriteStartMethodInvocation(methodName, new string[0]);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, string[] genericArguments)
|
||||
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, params string[] genericArguments)
|
||||
{
|
||||
Write(methodName);
|
||||
|
||||
|
@ -235,6 +237,33 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
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)
|
||||
{
|
||||
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 Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
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 CSharpPaddingBuilder _paddingBuilder;
|
||||
private CSharpTagHelperCodeRenderer _tagHelperCodeRenderer;
|
||||
|
||||
public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
_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)
|
||||
|
@ -72,21 +85,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
{
|
||||
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Context.TargetWriterName))
|
||||
{
|
||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralToMethodName)
|
||||
.Write(Context.TargetWriterName)
|
||||
.WriteParameterSeparator();
|
||||
}
|
||||
else
|
||||
{
|
||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralMethodName);
|
||||
}
|
||||
RenderPreWriteStart();
|
||||
}
|
||||
|
||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.ResolveUrlMethodName)
|
||||
.WriteStringLiteral(chunk.Url)
|
||||
.WriteEndMethodInvocation(endLine: false);
|
||||
.WriteStringLiteral(chunk.Url)
|
||||
.WriteEndMethodInvocation(endLine: false);
|
||||
|
||||
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||
{
|
||||
|
@ -115,17 +119,18 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
|
||||
if (!string.IsNullOrEmpty(Context.TargetWriterName))
|
||||
{
|
||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralToMethodName)
|
||||
.Write(Context.TargetWriterName)
|
||||
.WriteParameterSeparator();
|
||||
}
|
||||
else
|
||||
{
|
||||
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralMethodName);
|
||||
}
|
||||
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||
{
|
||||
RenderPreWriteStart();
|
||||
}
|
||||
|
||||
Writer.WriteStringLiteral(chunk.Text)
|
||||
.WriteEndMethodInvocation();
|
||||
Writer.WriteStringLiteral(chunk.Text);
|
||||
|
||||
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
|
||||
{
|
||||
Writer.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
|
||||
if (Context.Host.EnableInstrumentation)
|
||||
{
|
||||
|
@ -187,10 +192,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
|
||||
Writer.WriteParameterSeparator()
|
||||
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
|
||||
.WriteEndMethodInvocation(false)
|
||||
.WriteEndMethodInvocation(endLine: false)
|
||||
.WriteParameterSeparator()
|
||||
.WriteBooleanLiteral(false)
|
||||
.WriteEndMethodInvocation(false);
|
||||
.WriteBooleanLiteral(value: false)
|
||||
.WriteEndMethodInvocation(endLine: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -479,5 +484,26 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
return Context.Host.EnableInstrumentation &&
|
||||
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>
|
||||
{
|
||||
private const string TagHelpersRuntimeNamespace = "Microsoft.AspNet.Razor.Runtime.TagHelpers";
|
||||
|
||||
private bool _foundTagHelpers;
|
||||
|
||||
public CSharpUsingVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
|
@ -46,5 +50,20 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
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);
|
||||
}
|
||||
else if(chunk is TagHelperChunk)
|
||||
{
|
||||
Visit((TagHelperChunk)chunk);
|
||||
}
|
||||
else if(chunk is SetLayoutChunk)
|
||||
{
|
||||
Visit((SetLayoutChunk)chunk);
|
||||
|
@ -110,6 +114,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
|
|||
protected abstract void Visit(LiteralChunk chunk);
|
||||
protected abstract void Visit(ExpressionChunk chunk);
|
||||
protected abstract void Visit(StatementChunk chunk);
|
||||
protected abstract void Visit(TagHelperChunk chunk);
|
||||
protected abstract void Visit(UsingChunk chunk);
|
||||
protected abstract void Visit(ChunkBlock 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(TagHelperChunk 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()
|
||||
{
|
||||
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()
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Microsoft.Internal.Web.Utils;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Generator
|
||||
{
|
||||
|
@ -17,35 +15,39 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
public static readonly string DefaultWriteAttributeMethodName = "WriteAttribute";
|
||||
public static readonly string DefaultWriteAttributeToMethodName = "WriteAttributeTo";
|
||||
|
||||
public static readonly GeneratedClassContext Default = new GeneratedClassContext(DefaultExecuteMethodName,
|
||||
DefaultWriteMethodName,
|
||||
DefaultWriteLiteralMethodName);
|
||||
public static readonly GeneratedClassContext Default =
|
||||
new GeneratedClassContext(DefaultExecuteMethodName,
|
||||
DefaultWriteMethodName,
|
||||
DefaultWriteLiteralMethodName,
|
||||
new GeneratedTagHelperContext());
|
||||
|
||||
public GeneratedClassContext(string executeMethodName, string writeMethodName, string writeLiteralMethodName)
|
||||
public GeneratedClassContext(string executeMethodName,
|
||||
string writeMethodName,
|
||||
string writeLiteralMethodName,
|
||||
[NotNull] GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this()
|
||||
{
|
||||
if (String.IsNullOrEmpty(executeMethodName))
|
||||
if (string.IsNullOrEmpty(executeMethodName))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
"executeMethodName"),
|
||||
"executeMethodName");
|
||||
throw new ArgumentException(
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
nameof(executeMethodName));
|
||||
}
|
||||
if (String.IsNullOrEmpty(writeMethodName))
|
||||
if (string.IsNullOrEmpty(writeMethodName))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
"writeMethodName"),
|
||||
"writeMethodName");
|
||||
throw new ArgumentException(
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
nameof(writeMethodName));
|
||||
}
|
||||
if (String.IsNullOrEmpty(writeLiteralMethodName))
|
||||
if (string.IsNullOrEmpty(writeLiteralMethodName))
|
||||
{
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
"writeLiteralMethodName"),
|
||||
"writeLiteralMethodName");
|
||||
throw new ArgumentException(
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
nameof(writeLiteralMethodName));
|
||||
}
|
||||
|
||||
GeneratedTagHelperContext = generatedTagHelperContext;
|
||||
|
||||
WriteMethodName = writeMethodName;
|
||||
WriteLiteralMethodName = writeLiteralMethodName;
|
||||
ExecuteMethodName = executeMethodName;
|
||||
|
@ -65,8 +67,12 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
string writeLiteralMethodName,
|
||||
string writeToMethodName,
|
||||
string writeLiteralToMethodName,
|
||||
string templateTypeName)
|
||||
: this(executeMethodName, writeMethodName, writeLiteralMethodName)
|
||||
string templateTypeName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this(executeMethodName,
|
||||
writeMethodName,
|
||||
writeLiteralMethodName,
|
||||
generatedTagHelperContext)
|
||||
{
|
||||
WriteToMethodName = writeToMethodName;
|
||||
WriteLiteralToMethodName = writeLiteralToMethodName;
|
||||
|
@ -79,8 +85,15 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
string writeToMethodName,
|
||||
string writeLiteralToMethodName,
|
||||
string templateTypeName,
|
||||
string defineSectionMethodName)
|
||||
: this(executeMethodName, writeMethodName, writeLiteralMethodName, writeToMethodName, writeLiteralToMethodName, templateTypeName)
|
||||
string defineSectionMethodName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this(executeMethodName,
|
||||
writeMethodName,
|
||||
writeLiteralMethodName,
|
||||
writeToMethodName,
|
||||
writeLiteralToMethodName,
|
||||
templateTypeName,
|
||||
generatedTagHelperContext)
|
||||
{
|
||||
DefineSectionMethodName = defineSectionMethodName;
|
||||
}
|
||||
|
@ -93,18 +106,28 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
string templateTypeName,
|
||||
string defineSectionMethodName,
|
||||
string beginContextMethodName,
|
||||
string endContextMethodName)
|
||||
: this(executeMethodName, writeMethodName, writeLiteralMethodName, writeToMethodName, writeLiteralToMethodName, templateTypeName, defineSectionMethodName)
|
||||
string endContextMethodName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this(executeMethodName,
|
||||
writeMethodName,
|
||||
writeLiteralMethodName,
|
||||
writeToMethodName,
|
||||
writeLiteralToMethodName,
|
||||
templateTypeName,
|
||||
defineSectionMethodName,
|
||||
generatedTagHelperContext)
|
||||
{
|
||||
BeginContextMethodName = beginContextMethodName;
|
||||
EndContextMethodName = endContextMethodName;
|
||||
}
|
||||
|
||||
// Required Items
|
||||
public string WriteMethodName { get; private set; }
|
||||
public string WriteLiteralMethodName { get; private set; }
|
||||
public string WriteToMethodName { get; private set; }
|
||||
public string WriteLiteralToMethodName { get; private set; }
|
||||
public string ExecuteMethodName { get; private set; }
|
||||
public GeneratedTagHelperContext GeneratedTagHelperContext { get; private set; }
|
||||
|
||||
// Optional Items
|
||||
public string BeginContextMethodName { get; set; }
|
||||
|
@ -120,17 +143,17 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
|
||||
public bool AllowSections
|
||||
{
|
||||
get { return !String.IsNullOrEmpty(DefineSectionMethodName); }
|
||||
get { return !string.IsNullOrEmpty(DefineSectionMethodName); }
|
||||
}
|
||||
|
||||
public bool AllowTemplates
|
||||
{
|
||||
get { return !String.IsNullOrEmpty(TemplateTypeName); }
|
||||
get { return !string.IsNullOrEmpty(TemplateTypeName); }
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -139,16 +162,16 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
{
|
||||
return false;
|
||||
}
|
||||
GeneratedClassContext other = (GeneratedClassContext)obj;
|
||||
return String.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
|
||||
String.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
|
||||
String.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
|
||||
var other = (GeneratedClassContext)obj;
|
||||
return string.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
|
||||
string.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
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.
|
||||
// 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.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Generator
|
||||
{
|
||||
|
@ -10,6 +15,19 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
/// </summary>
|
||||
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>
|
||||
/// Starts the generation of a <see cref="TagHelperChunk"/>.
|
||||
/// </summary>
|
||||
|
@ -20,11 +38,52 @@ namespace Microsoft.AspNet.Razor.Generator
|
|||
/// the current code generation process.</param>
|
||||
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>
|
||||
/// 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>
|
||||
/// <param name="target">
|
||||
/// 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>
|
||||
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
|
||||
{
|
||||
context.CodeTreeBuilder.EndChunkBlock();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Generator;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
|
||||
|
||||
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"/>.
|
||||
/// </summary>
|
||||
/// <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
|
||||
/// of the HTML element.</param>
|
||||
public TagHelperBlockBuilder(string tagName, Block startTag)
|
||||
public TagHelperBlockBuilder(string tagName, IEnumerable<TagHelperDescriptor> descriptors, Block startTag)
|
||||
{
|
||||
TagName = tagName;
|
||||
CodeGenerator = new TagHelperCodeGenerator();
|
||||
CodeGenerator = new TagHelperCodeGenerator(descriptors);
|
||||
Type = startTag.Type;
|
||||
Attributes = GetTagAttributes(startTag);
|
||||
}
|
||||
|
@ -51,7 +54,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
TagName = tagName;
|
||||
Attributes = attributes;
|
||||
Type = BlockType.Tag;
|
||||
CodeGenerator = new TagHelperCodeGenerator();
|
||||
CodeGenerator = new TagHelperCodeGenerator(tagHelperDescriptors: null);
|
||||
|
||||
// Children is IList, no AddRange
|
||||
foreach (var child in children)
|
||||
|
|
|
@ -65,19 +65,25 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
// We're in a begin tag block
|
||||
|
||||
if (IsPotentialTagHelper(tagName, childBlock) && IsRegisteredTagHelper(tagName))
|
||||
if (IsPotentialTagHelper(tagName, childBlock))
|
||||
{
|
||||
// Found a new tag helper block
|
||||
TrackTagHelperBlock(new TagHelperBlockBuilder(tagName, childBlock));
|
||||
var descriptors = _provider.GetTagHelpers(tagName);
|
||||
|
||||
// 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))
|
||||
// We could be a tag helper, but only if we have descriptors registered
|
||||
if (descriptors.Any())
|
||||
{
|
||||
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
|
||||
|
|
|
@ -1462,6 +1462,38 @@ namespace Microsoft.AspNet.Razor
|
|||
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)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
@ -215,5 +215,13 @@ namespace Microsoft.AspNet.Razor
|
|||
}
|
||||
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">
|
||||
<value>Tag Helpers cannot have C# in an HTML tag element's attribute declaration area.</value>
|
||||
</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>
|
|
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Internal.Web.Utils;
|
||||
|
||||
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="contentBehavior">The <see cref="TagHelpers.ContentBehavior"/>
|
||||
/// of the tag helper.</param>
|
||||
public TagHelperDescriptor(string tagName,
|
||||
string tagHelperName,
|
||||
public TagHelperDescriptor([NotNull] string tagName,
|
||||
[NotNull] string tagHelperName,
|
||||
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;
|
||||
TagHelperName = tagHelperName;
|
||||
ContentBehavior = contentBehavior;
|
||||
Attributes = new List<TagHelperAttributeDescriptor>();
|
||||
Attributes = new List<TagHelperAttributeDescriptor>(attributes);
|
||||
}
|
||||
|
||||
/// <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 static readonly TagHelperDescriptorComparer DefaultTagHelperDescriptorComparer =
|
||||
new TagHelperDescriptorComparer();
|
||||
|
||||
private IDictionary<string, HashSet<TagHelperDescriptor>> _registrations;
|
||||
|
||||
/// <summary>
|
||||
|
@ -79,30 +76,11 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
// Ensure there's a List to add the descriptor to.
|
||||
if (!_registrations.TryGetValue(descriptor.TagName, out descriptorSet))
|
||||
{
|
||||
descriptorSet = new HashSet<TagHelperDescriptor>(DefaultTagHelperDescriptorComparer);
|
||||
descriptorSet = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
|
||||
_registrations[descriptor.TagName] = descriptorSet;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче