diff --git a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpTagHelperCodeRenderer.cs b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpTagHelperCodeRenderer.cs index 4bf8acaedb..8ecaefdedf 100644 --- a/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpTagHelperCodeRenderer.cs +++ b/src/Microsoft.AspNet.Razor/CodeGenerators/CSharpTagHelperCodeRenderer.cs @@ -229,15 +229,10 @@ namespace Microsoft.AspNet.Razor.CodeGenerators descriptor.Attributes.Any(attributeDescriptor => attributeDescriptor.IsNameMatch(attributeName))); - // Bound attributes have associated descriptors. - if (associatedDescriptors.Any()) + // Bound attributes have associated descriptors. First attribute value wins if there are duplicates; + // later values of duplicate bound attributes are treated as if they were unbound. + if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attributeName)) { - if (!renderedBoundAttributeNames.Add(attributeName)) - { - // First attribute value wins if there are duplicates. - continue; - } - if (attributeValueChunk == null) { // Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already diff --git a/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpTagHelperRenderingTest.cs b/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpTagHelperRenderingTest.cs index c8c34f91fd..0ee768f192 100644 --- a/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpTagHelperRenderingTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpTagHelperRenderingTest.cs @@ -879,6 +879,36 @@ namespace Microsoft.AspNet.Razor.Test.Generator contentLength: 2), } }, + { + "DuplicateAttributeTagHelpers", + "DuplicateAttributeTagHelpers.DesignTime", + DefaultPAndInputTagHelperDescriptors, + new List + { + BuildLineMapping( + documentAbsoluteIndex: 14, + documentLineIndex: 0, + generatedAbsoluteIndex: 501, + generatedLineIndex: 15, + characterOffsetIndex: 14, + contentLength: 17), + BuildLineMapping( + documentAbsoluteIndex: 146, + documentLineIndex: 4, + generatedAbsoluteIndex: 1567, + generatedLineIndex: 43, + characterOffsetIndex: 34, + contentLength: 4), + BuildLineMapping( + documentAbsoluteIndex: 43, + documentLineIndex: 2, + documentCharacterOffsetIndex: 8, + generatedAbsoluteIndex: 1730, + generatedLineIndex: 49, + generatedCharacterOffsetIndex: 19, + contentLength: 1), + } + }, }; } } @@ -921,6 +951,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator "PrefixedAttributeTagHelpers.Reversed", PrefixedAttributeTagHelperDescriptors.Reverse() }, + { "DuplicateAttributeTagHelpers", null, DefaultPAndInputTagHelperDescriptors }, }; } } diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/DuplicateAttributeTagHelpers.DesignTime.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/DuplicateAttributeTagHelpers.DesignTime.cs new file mode 100644 index 0000000000..cec5d58b62 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/DuplicateAttributeTagHelpers.DesignTime.cs @@ -0,0 +1,57 @@ +namespace TestOutput +{ + using Microsoft.AspNet.Razor.Runtime.TagHelpers; + using System; + using System.Threading.Tasks; + + public class DuplicateAttributeTagHelpers + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 + string __tagHelperDirectiveSyntaxHelper = null; + __tagHelperDirectiveSyntaxHelper = +#line 1 "DuplicateAttributeTagHelpers.cshtml" + "something, nice" + +#line default +#line hidden + ; + #pragma warning restore 219 + } + #line hidden + private PTagHelper __PTagHelper = null; + private InputTagHelper __InputTagHelper = null; + private InputTagHelper2 __InputTagHelper2 = null; + #line hidden + public DuplicateAttributeTagHelpers() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + __InputTagHelper = CreateTagHelper(); + __InputTagHelper2 = CreateTagHelper(); + __InputTagHelper.Type = "button"; + __InputTagHelper2.Type = __InputTagHelper.Type; + __InputTagHelper = CreateTagHelper(); + __InputTagHelper2 = CreateTagHelper(); + __InputTagHelper.Type = "button"; + __InputTagHelper2.Type = __InputTagHelper.Type; +#line 5 "DuplicateAttributeTagHelpers.cshtml" + __InputTagHelper2.Checked = true; + +#line default +#line hidden + __PTagHelper = CreateTagHelper(); +#line 3 "DuplicateAttributeTagHelpers.cshtml" +__PTagHelper.Age = 3; + +#line default +#line hidden + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/DuplicateAttributeTagHelpers.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/DuplicateAttributeTagHelpers.cs new file mode 100644 index 0000000000..833b1bffd0 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/DuplicateAttributeTagHelpers.cs @@ -0,0 +1,101 @@ +#pragma checksum "DuplicateAttributeTagHelpers.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "af64a6adaf73e4143024a1145e70cbd3a24d2c0e" +namespace TestOutput +{ + using Microsoft.AspNet.Razor.Runtime.TagHelpers; + using System; + using System.Threading.Tasks; + + public class DuplicateAttributeTagHelpers + { + #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 PTagHelper __PTagHelper = null; + private InputTagHelper __InputTagHelper = null; + private InputTagHelper2 __InputTagHelper2 = null; + #line hidden + public DuplicateAttributeTagHelpers() + { + } + + #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("p", false, "test", async() => { + Instrumentation.BeginContext(65, 6, true); + WriteLiteral("\r\n "); + Instrumentation.EndContext(); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => { + } + , StartTagHelperWritingScope, EndTagHelperWritingScope); + __InputTagHelper = CreateTagHelper(); + __tagHelperExecutionContext.Add(__InputTagHelper); + __InputTagHelper2 = CreateTagHelper(); + __tagHelperExecutionContext.Add(__InputTagHelper2); + __InputTagHelper.Type = "button"; + __tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type); + __InputTagHelper2.Type = __InputTagHelper.Type; + __tagHelperExecutionContext.AddHtmlAttribute("TYPE", Html.Raw("checkbox")); + __tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext); + Instrumentation.BeginContext(71, 39, false); + await WriteTagHelperAsync(__tagHelperExecutionContext); + Instrumentation.EndContext(); + __tagHelperExecutionContext = __tagHelperScopeManager.End(); + Instrumentation.BeginContext(110, 6, true); + WriteLiteral("\r\n "); + Instrumentation.EndContext(); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => { + } + , StartTagHelperWritingScope, EndTagHelperWritingScope); + __InputTagHelper = CreateTagHelper(); + __tagHelperExecutionContext.Add(__InputTagHelper); + __InputTagHelper2 = CreateTagHelper(); + __tagHelperExecutionContext.Add(__InputTagHelper2); + __InputTagHelper.Type = "button"; + __tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type); + __InputTagHelper2.Type = __InputTagHelper.Type; +#line 5 "DuplicateAttributeTagHelpers.cshtml" + __InputTagHelper2.Checked = true; + +#line default +#line hidden + __tagHelperExecutionContext.AddTagHelperAttribute("checked", __InputTagHelper2.Checked); + __tagHelperExecutionContext.AddHtmlAttribute("type", Html.Raw("checkbox")); + __tagHelperExecutionContext.AddHtmlAttribute("checked", Html.Raw("false")); + __tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext); + Instrumentation.BeginContext(116, 70, false); + await WriteTagHelperAsync(__tagHelperExecutionContext); + Instrumentation.EndContext(); + __tagHelperExecutionContext = __tagHelperScopeManager.End(); + Instrumentation.BeginContext(186, 2, true); + WriteLiteral("\r\n"); + Instrumentation.EndContext(); + } + , StartTagHelperWritingScope, EndTagHelperWritingScope); + __PTagHelper = CreateTagHelper(); + __tagHelperExecutionContext.Add(__PTagHelper); +#line 3 "DuplicateAttributeTagHelpers.cshtml" +__PTagHelper.Age = 3; + +#line default +#line hidden + __tagHelperExecutionContext.AddTagHelperAttribute("age", __PTagHelper.Age); + __tagHelperExecutionContext.AddHtmlAttribute("AGE", Html.Raw("40")); + __tagHelperExecutionContext.AddHtmlAttribute("Age", Html.Raw("500")); + __tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext); + Instrumentation.BeginContext(35, 157, false); + await WriteTagHelperAsync(__tagHelperExecutionContext); + Instrumentation.EndContext(); + __tagHelperExecutionContext = __tagHelperScopeManager.End(); + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs index f577815d69..501f490065 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs @@ -166,6 +166,7 @@ __InputTagHelper2.IntDictionaryProperty["grabber"] = 42; #line hidden __tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-pepper", __InputTagHelper2.IntDictionaryProperty["pepper"]); __InputTagHelper1.IntDictionaryProperty["pepper"] = __InputTagHelper2.IntDictionaryProperty["pepper"]; + __tagHelperExecutionContext.AddHtmlAttribute("int-prefix-salt", Html.Raw("8")); __InputTagHelper2.StringDictionaryProperty["grabber"] = "string"; __tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-grabber", __InputTagHelper2.StringDictionaryProperty["grabber"]); __InputTagHelper1.StringProperty = __InputTagHelper2.StringDictionaryProperty["grabber"]; diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.cs index f0c9090e9a..9e22db40da 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.cs @@ -166,6 +166,7 @@ __InputTagHelper1.IntProperty = 42; #line hidden __tagHelperExecutionContext.AddTagHelperAttribute("int-prefix-pepper", __InputTagHelper1.IntDictionaryProperty["pepper"]); __InputTagHelper2.IntDictionaryProperty["pepper"] = __InputTagHelper1.IntDictionaryProperty["pepper"]; + __tagHelperExecutionContext.AddHtmlAttribute("int-prefix-salt", Html.Raw("8")); __InputTagHelper1.StringProperty = "string"; __tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-grabber", __InputTagHelper1.StringProperty); __InputTagHelper2.StringDictionaryProperty["grabber"] = __InputTagHelper1.StringProperty; diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/DuplicateAttributeTagHelpers.cshtml b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/DuplicateAttributeTagHelpers.cshtml new file mode 100644 index 0000000000..900a174557 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/DuplicateAttributeTagHelpers.cshtml @@ -0,0 +1,6 @@ +@addTagHelper "something, nice" + +

+ + +

\ No newline at end of file