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:
N. Taylor Mullen 2014-09-13 14:41:31 -07:00
Родитель 0b5f0cd565
Коммит 50fa3ee3e3
23 изменённых файлов: 1164 добавлений и 110 удалений

Просмотреть файл

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