De-dupe TagHelperDescriptors based on Type for rendering.
- This can occur if you have multiple [TargetElement] attributes that overlap. Ultimately the descriptor is the same because its the same type, just the required attributes differ. - Added tests to validate. #326
This commit is contained in:
parent
fa40fe6e46
commit
842549bba9
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
||||
|
|
@ -56,11 +55,15 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
/// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param>
|
||||
public void RenderTagHelper(TagHelperChunk chunk)
|
||||
{
|
||||
var tagHelperDescriptors = chunk.Descriptors;
|
||||
// Remove any duplicate TagHelperDescriptors that referrence the same type name. Duplicates can occur when
|
||||
// multiple TargetElement attributes are on a TagHelper type and matchs overlap for an HTML element.
|
||||
// Having more than one descriptor with the same TagHelper type results in generated code that runs
|
||||
// the same TagHelper X many times (instead of once) over a single HTML element.
|
||||
var tagHelperDescriptors = chunk.Descriptors.Distinct(TypeNameTagHelperDescriptorComparer.Default);
|
||||
|
||||
RenderBeginTagHelperScope(chunk.TagName, chunk.SelfClosing, chunk.Children);
|
||||
|
||||
RenderTagHelpersCreation(chunk);
|
||||
RenderTagHelpersCreation(chunk, tagHelperDescriptors);
|
||||
|
||||
var attributeDescriptors = tagHelperDescriptors.SelectMany(descriptor => descriptor.Attributes);
|
||||
var boundHTMLAttributes = attributeDescriptors.Select(descriptor => descriptor.Name);
|
||||
|
|
@ -146,10 +149,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
private void RenderTagHelpersCreation(TagHelperChunk chunk)
|
||||
private void RenderTagHelpersCreation(
|
||||
TagHelperChunk chunk,
|
||||
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||
{
|
||||
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;
|
||||
|
|
@ -551,5 +554,25 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
|
|||
return StringComparer.OrdinalIgnoreCase.GetHashCode(descriptor.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeNameTagHelperDescriptorComparer : IEqualityComparer<TagHelperDescriptor>
|
||||
{
|
||||
public static readonly TypeNameTagHelperDescriptorComparer Default =
|
||||
new TypeNameTagHelperDescriptorComparer();
|
||||
|
||||
private TypeNameTagHelperDescriptorComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Equals(TagHelperDescriptor descriptorX, TagHelperDescriptor descriptorY)
|
||||
{
|
||||
return string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public int GetHashCode(TagHelperDescriptor descriptor)
|
||||
{
|
||||
return StringComparer.Ordinal.GetHashCode(descriptor.TypeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,56 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
|||
private static IEnumerable<TagHelperDescriptor> PrefixedPAndInputTagHelperDescriptors
|
||||
=> BuildPAndInputTagHelperDescriptors("THS");
|
||||
|
||||
private static IEnumerable<TagHelperDescriptor> DuplicateTargetTagHelperDescriptors
|
||||
{
|
||||
get
|
||||
{
|
||||
var inputTypePropertyInfo = typeof(TestType).GetProperty("Type");
|
||||
var inputCheckedPropertyInfo = typeof(TestType).GetProperty("Checked");
|
||||
return new[]
|
||||
{
|
||||
new TagHelperDescriptor(
|
||||
tagName: "*",
|
||||
typeName: "CatchAllTagHelper",
|
||||
assemblyName: "SomeAssembly",
|
||||
attributes: new TagHelperAttributeDescriptor[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
|
||||
},
|
||||
requiredAttributes: new[] { "type" }),
|
||||
new TagHelperDescriptor(
|
||||
tagName: "*",
|
||||
typeName: "CatchAllTagHelper",
|
||||
assemblyName: "SomeAssembly",
|
||||
attributes: new TagHelperAttributeDescriptor[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
requiredAttributes: new[] { "type", "checked" }),
|
||||
new TagHelperDescriptor(
|
||||
tagName: "input",
|
||||
typeName: "InputTagHelper",
|
||||
assemblyName: "SomeAssembly",
|
||||
attributes: new TagHelperAttributeDescriptor[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo)
|
||||
},
|
||||
requiredAttributes: new[] { "type" }),
|
||||
new TagHelperDescriptor(
|
||||
tagName: "input",
|
||||
typeName: "InputTagHelper",
|
||||
assemblyName: "SomeAssembly",
|
||||
attributes: new TagHelperAttributeDescriptor[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("type", inputTypePropertyInfo),
|
||||
new TagHelperAttributeDescriptor("checked", inputCheckedPropertyInfo)
|
||||
},
|
||||
requiredAttributes: new[] { "type", "checked" })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDescriptor> AttributeTargetingTagHelperDescriptors
|
||||
{
|
||||
get
|
||||
|
|
@ -93,6 +143,13 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
|||
DefaultPAndInputTagHelperDescriptors,
|
||||
false
|
||||
},
|
||||
{
|
||||
"DuplicateTargetTagHelper",
|
||||
"DuplicateTargetTagHelper",
|
||||
DuplicateTargetTagHelperDescriptors,
|
||||
DuplicateTargetTagHelperDescriptors,
|
||||
false
|
||||
},
|
||||
{
|
||||
"BasicTagHelpers",
|
||||
"BasicTagHelpers.DesignTime",
|
||||
|
|
@ -410,6 +467,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator
|
|||
{ "BasicTagHelpers.RemoveTagHelper", DefaultPAndInputTagHelperDescriptors },
|
||||
{ "BasicTagHelpers.Prefixed", PrefixedPAndInputTagHelperDescriptors },
|
||||
{ "ComplexTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
||||
{ "DuplicateTargetTagHelper", DuplicateTargetTagHelperDescriptors },
|
||||
{ "EmptyAttributeTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
||||
{ "EscapedTagHelpers", DefaultPAndInputTagHelperDescriptors },
|
||||
{ "AttributeTargetingTagHelpers", AttributeTargetingTagHelperDescriptors },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
#pragma checksum "DuplicateTargetTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "9cd2f5a40be40d26a0756bf6a74cee58bd13927f"
|
||||
namespace TestOutput
|
||||
{
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class DuplicateTargetTagHelper
|
||||
{
|
||||
#line hidden
|
||||
#pragma warning disable 0414
|
||||
private TagHelperContent __tagHelperStringValueBuffer = null;
|
||||
#pragma warning restore 0414
|
||||
private TagHelperExecutionContext __tagHelperExecutionContext = null;
|
||||
private TagHelperRunner __tagHelperRunner = null;
|
||||
private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager();
|
||||
private InputTagHelper __InputTagHelper = null;
|
||||
private CatchAllTagHelper __CatchAllTagHelper = null;
|
||||
#line hidden
|
||||
public DuplicateTargetTagHelper()
|
||||
{
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
public override async Task ExecuteAsync()
|
||||
{
|
||||
__tagHelperRunner = __tagHelperRunner ?? new TagHelperRunner();
|
||||
Instrumentation.BeginContext(33, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
Instrumentation.EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
|
||||
}
|
||||
, StartTagHelperWritingScope, EndTagHelperWritingScope);
|
||||
__InputTagHelper = CreateTagHelper<InputTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__InputTagHelper);
|
||||
__InputTagHelper.Type = "checkbox";
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type);
|
||||
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
|
||||
__CatchAllTagHelper.Type = __InputTagHelper.Type;
|
||||
__tagHelperExecutionContext.AddHtmlAttribute("checked", "true");
|
||||
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
await WriteTagHelperAsync(__tagHelperExecutionContext);
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@addTagHelper "something, nice"
|
||||
|
||||
<input type="checkbox" checked="true" />
|
||||
Loading…
Reference in New Issue