diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs
index be7410a15c..cb738ba070 100644
--- a/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs
+++ b/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs
@@ -23,9 +23,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// The this method extends.
/// The name of the bound attribute.
/// The .
- /// Only copies the attribute if 's
+ ///
+ ///
+ /// Only copies the attribute if 's
/// does not contain an attribute with the given
- /// .
+ /// .
+ ///
+ ///
+ /// Duplicate attributes same name in 's
+ /// or 's may result in copied
+ /// attribute order not being maintained.
+ ///
public static void CopyHtmlAttribute(
[NotNull] this TagHelperOutput tagHelperOutput,
[NotNull] string attributeName,
@@ -33,21 +41,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
if (!tagHelperOutput.Attributes.ContainsName(attributeName))
{
- IEnumerable entries;
+ var copiedAttribute = false;
- // We look for the original attribute so we can restore the exact attribute name the user typed.
- // Approach also ignores changes made to tagHelperOutput[attributeName].
- if (!context.AllAttributes.TryGetAttributes(attributeName, out entries))
+ // We iterate context.AllAttributes backwards since we prioritize TagHelperOutput values occurring
+ // before the current context.AllAttribtes[i].
+ for (var i = context.AllAttributes.Count - 1; i >= 0; i--)
+ {
+ // We look for the original attribute so we can restore the exact attribute name the user typed in
+ // approximately the same position where the user wrote it in the Razor source.
+ if (string.Equals(
+ attributeName,
+ context.AllAttributes[i].Name,
+ StringComparison.OrdinalIgnoreCase))
+ {
+ CopyHtmlAttribute(i, tagHelperOutput, context);
+ copiedAttribute = true;
+ }
+ }
+
+ if (!copiedAttribute)
{
throw new ArgumentException(
Resources.FormatTagHelperOutput_AttributeDoesNotExist(attributeName, nameof(TagHelperContext)),
nameof(attributeName));
}
-
- foreach (var entry in entries)
- {
- tagHelperOutput.Attributes.Add(entry.Name, entry.Value);
- }
}
}
@@ -100,5 +117,61 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
tagHelperOutput.Attributes.Remove(attribute);
}
}
+
+ private static void CopyHtmlAttribute(
+ int allAttributeIndex,
+ TagHelperOutput tagHelperOutput,
+ TagHelperContext context)
+ {
+ var existingAttribute = context.AllAttributes[allAttributeIndex];
+ var copiedAttribute = new TagHelperAttribute
+ {
+ Name = existingAttribute.Name,
+ Value = existingAttribute.Value,
+ Minimized = existingAttribute.Minimized
+ };
+
+ // Move backwards through context.AllAttributes from the provided index until we find a familiar attribute
+ // in tagHelperOutput where we can insert the copied value after the familiar one.
+ for (var i = allAttributeIndex - 1; i >= 0; i--)
+ {
+ var previousName = context.AllAttributes[i].Name;
+ var index = IndexOfFirstMatch(previousName, tagHelperOutput.Attributes);
+ if (index != -1)
+ {
+ tagHelperOutput.Attributes.Insert(index + 1, copiedAttribute);
+ return;
+ }
+ }
+
+ // Move forward through context.AllAttributes from the provided index until we find a familiar attribute in
+ // tagHelperOutput where we can insert the copied value.
+ for (var i = allAttributeIndex + 1; i < context.AllAttributes.Count; i++)
+ {
+ var nextName = context.AllAttributes[i].Name;
+ var index = IndexOfFirstMatch(nextName, tagHelperOutput.Attributes);
+ if (index != -1)
+ {
+ tagHelperOutput.Attributes.Insert(index, copiedAttribute);
+ return;
+ }
+ }
+
+ // Couldn't determine the attribute's location, add it to the end.
+ tagHelperOutput.Attributes.Add(copiedAttribute);
+ }
+
+ private static int IndexOfFirstMatch(string name, TagHelperAttributeList attributes)
+ {
+ for (var i = 0; i < attributes.Count; i++)
+ {
+ if (string.Equals(name, attributes[i].Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html
index 7d6cf27c87..9026b3bc0d 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html
@@ -3,7 +3,7 @@