diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs
index ad9b586f03..5c698946e1 100644
--- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs
+++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpTagHelperCodeRenderer.cs
@@ -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
/// A to render.
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 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
+ {
+ 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);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs
index 90626b2740..4a0bdfbc21 100644
--- a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs
+++ b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs
@@ -19,6 +19,56 @@ namespace Microsoft.AspNet.Razor.Test.Generator
private static IEnumerable PrefixedPAndInputTagHelperDescriptors
=> BuildPAndInputTagHelperDescriptors("THS");
+ private static IEnumerable 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 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 },
diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/DuplicateTargetTagHelper.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/DuplicateTargetTagHelper.cs
new file mode 100644
index 0000000000..51d2dbb4d7
--- /dev/null
+++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Output/DuplicateTargetTagHelper.cs
@@ -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();
+ __tagHelperExecutionContext.Add(__InputTagHelper);
+ __InputTagHelper.Type = "checkbox";
+ __tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type);
+ __CatchAllTagHelper = CreateTagHelper();
+ __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
+ }
+}
diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/DuplicateTargetTagHelper.cshtml b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/DuplicateTargetTagHelper.cshtml
new file mode 100644
index 0000000000..d3c68eb0d4
--- /dev/null
+++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/CS/Source/DuplicateTargetTagHelper.cshtml
@@ -0,0 +1,3 @@
+@addTagHelper "something, nice"
+
+
\ No newline at end of file