diff --git a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs index e5b254e1e1..abc7c4710b 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/Properties/Resources.Designer.cs @@ -218,6 +218,38 @@ namespace Microsoft.AspNet.Razor.Runtime return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"), p0, p1, p2); } + /// + /// Cannot add a '{0}' with a null '{1}'. + /// + internal static string TagHelperAttributeList_CannotAddWithNullName + { + get { return GetString("TagHelperAttributeList_CannotAddWithNullName"); } + } + + /// + /// Cannot add a '{0}' with a null '{1}'. + /// + internal static string FormatTagHelperAttributeList_CannotAddWithNullName(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddWithNullName"), p0, p1); + } + + /// + /// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'. + /// + internal static string TagHelperAttributeList_CannotAddAttribute + { + get { return GetString("TagHelperAttributeList_CannotAddAttribute"); } + } + + /// + /// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'. + /// + internal static string FormatTagHelperAttributeList_CannotAddAttribute(object p0, object p1, object p2, object p3) + { + return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddAttribute"), p0, p1, p2, p3); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx index 19695614ab..af51cbfab4 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/Resources.resx +++ b/src/Microsoft.AspNet.Razor.Runtime/Resources.resx @@ -156,4 +156,10 @@ Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'. + + Cannot add a '{0}' with a null '{1}'. + + + Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/IReadOnlyTagHelperAttribute.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/IReadOnlyTagHelperAttribute.cs new file mode 100644 index 0000000000..c297b26d79 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/IReadOnlyTagHelperAttribute.cs @@ -0,0 +1,23 @@ +// 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; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// A read-only HTML tag helper attribute. + /// + public interface IReadOnlyTagHelperAttribute : IEquatable + { + /// + /// Gets the name of the attribute. + /// + string Name { get; } + + /// + /// Gets the value of the attribute. + /// + object Value { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ReadOnlyTagHelperAttributeList.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ReadOnlyTagHelperAttributeList.cs new file mode 100644 index 0000000000..a4653e38a4 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ReadOnlyTagHelperAttributeList.cs @@ -0,0 +1,195 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// A read-only collection of s. + /// + /// + /// The type of s in the collection. + /// + public class ReadOnlyTagHelperAttributeList : IReadOnlyList + where TAttribute : IReadOnlyTagHelperAttribute + { + /// + /// Instantiates a new instance of with an empty + /// collection. + /// + protected ReadOnlyTagHelperAttributeList() + { + Attributes = new List(); + } + + /// + /// Instantiates a new instance of with the specified + /// . + /// + /// The collection to wrap. + public ReadOnlyTagHelperAttributeList([NotNull] IEnumerable attributes) + { + Attributes = new List(attributes); + } + + /// + /// The underlying collection of s. + /// + /// Intended for use in a non-read-only subclass. Changes to this will + /// affect all getters that provides. + protected List Attributes { get; } + + /// + public TAttribute this[int index] + { + get + { + return Attributes[index]; + } + } + + /// + /// Gets the first with + /// matching . + /// + /// + /// The of the to get. + /// + /// The first with + /// matching . + /// + /// is compared case-insensitively. + public TAttribute this[[NotNull] string name] + { + get + { + return Attributes.FirstOrDefault(attribute => NameEquals(name, attribute)); + } + } + + /// + public int Count + { + get + { + return Attributes.Count; + } + } + + /// + /// Determines whether a matching exists in the + /// collection. + /// + /// The to locate. + /// + /// true if an matching exists in the + /// collection; otherwise, false. + /// + /// + /// s is compared case-insensitively. + /// + public bool Contains([NotNull] TAttribute item) + { + return Attributes.Contains(item); + } + + /// + /// Determines whether a with the same + /// exists in the collection. + /// + /// The of the + /// to get. + /// + /// true if a with the same + /// exists in the collection; otherwise, false. + /// + /// is compared case-insensitively. + public bool ContainsName([NotNull] string name) + { + return Attributes.Any(attribute => NameEquals(name, attribute)); + } + + /// + /// Searches for a matching in the collection and + /// returns the zero-based index of the first occurrence. + /// + /// The to locate. + /// The zero-based index of the first occurrence of a matching + /// in the collection, if found; otherwise, –1. + /// + /// s is compared case-insensitively. + /// + public int IndexOf([NotNull] TAttribute item) + { + return Attributes.IndexOf(item); + } + + /// + /// Retrieves the first with + /// matching . + /// + /// The of the + /// to get. + /// When this method returns, the first with + /// matching , if found; otherwise, + /// null. + /// true if a with the same + /// exists in the collection; otherwise, false. + /// is compared case-insensitively. + public bool TryGetAttribute([NotNull] string name, out TAttribute attribute) + { + attribute = Attributes.FirstOrDefault(attr => NameEquals(name, attr)); + + return attribute != null; + } + + /// + /// Retrieves s in the collection with + /// matching . + /// + /// The of the + /// s to get. + /// When this method returns, the s with + /// matching , if at least one is + /// found; otherwise, null. + /// true if at least one with the same + /// exists in the collection; otherwise, false. + /// is compared case-insensitively. + public bool TryGetAttributes([NotNull] string name, out IEnumerable attributes) + { + attributes = Attributes.Where(attribute => NameEquals(name, attribute)); + + return attributes.Any(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + public IEnumerator GetEnumerator() + { + return Attributes.GetEnumerator(); + } + + /// + /// Determines if the specified has the same name as . + /// + /// The value to compare against s + /// . + /// The attribute to compare against. + /// true if case-insensitively matches s + /// . + protected static bool NameEquals(string name, [NotNull] TAttribute attribute) + { + return string.Equals(name, attribute.Name, StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperAttribute.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperAttribute.cs new file mode 100644 index 0000000000..16abc15b2c --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperAttribute.cs @@ -0,0 +1,82 @@ +// 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 Microsoft.Internal.Web.Utils; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// An HTML tag helper attribute. + /// + public class TagHelperAttribute : IReadOnlyTagHelperAttribute + { + private static readonly int TypeHashCode = typeof(TagHelperAttribute).GetHashCode(); + + /// + /// Instantiates a new instance of . + /// + public TagHelperAttribute() + { + } + + /// + /// Instantiates a new instance of with the specified + /// and . + /// + /// The of the attribute. + /// The of the attribute. + public TagHelperAttribute(string name, object value) + { + Name = name; + Value = value; + } + + /// + /// Gets or sets the name of the attribute. + /// + public string Name { get; set; } + + /// + /// Gets or sets the value of the attribute. + /// + public object Value { get; set; } + + /// + /// Converts the specified into a . + /// + /// The of the created . + /// Created s is set to null. + public static implicit operator TagHelperAttribute(string value) + { + return new TagHelperAttribute + { + Value = value + }; + } + + /// + /// is compared case-insensitively. + public bool Equals(IReadOnlyTagHelperAttribute other) + { + return + other != null && + string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) && + Equals(Value, other.Value); + } + + /// + public override bool Equals(object obj) + { + var other = obj as IReadOnlyTagHelperAttribute; + + return Equals(other); + } + + /// + public override int GetHashCode() + { + return TypeHashCode; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperAttributeList.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperAttributeList.cs new file mode 100644 index 0000000000..d0f2a0c0f6 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperAttributeList.cs @@ -0,0 +1,239 @@ +// 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.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + /// + /// A collection of s. + /// + public class TagHelperAttributeList : ReadOnlyTagHelperAttributeList, IList + { + /// + /// Instantiates a new instance of with an empty collection. + /// + public TagHelperAttributeList() + : base() + { + } + + /// + /// Instantiates a new instance of with the specified + /// . + /// + /// The collection to wrap. + public TagHelperAttributeList([NotNull] IEnumerable attributes) + : base(attributes) + { + } + + /// + /// + /// 's must not be null. + /// + public new TagHelperAttribute this[int index] + { + get + { + return base[index]; + } + [param: NotNull] + set + { + if (value.Name == null) + { + throw new ArgumentException( + Resources.FormatTagHelperAttributeList_CannotAddWithNullName( + typeof(TagHelperAttribute).FullName, + nameof(TagHelperAttribute.Name)), + nameof(value)); + } + + Attributes[index] = value; + } + } + + /// + /// Gets the first with matching + /// . When setting, replaces the first matching + /// with the specified and removes any additional + /// matching s. If a matching is not found, + /// adds the specified to the end of the collection. + /// + /// + /// The of the to get or set. + /// + /// The first with matching + /// . + /// + /// is compared case-insensitively. When setting, + /// s must be null or + /// case-insensitively match the specified . + /// + /// + /// var attributes = new TagHelperAttributeList(); + /// + /// // Will "value" be converted to a TagHelperAttribute with a null Name + /// attributes["name"] = "value"; + /// + /// // TagHelperAttribute.Name must match the specified name. + /// attributes["name"] = new TagHelperAttribute("name", "value"); + /// + /// + public new TagHelperAttribute this[[NotNull] string name] + { + get + { + return base[name]; + } + [param: NotNull] + set + { + // Name will be null if user attempts to set the attribute via an implicit conversion: + // output.Attributes["someName"] = "someValue" + if (value.Name == null) + { + value.Name = name; + } + else if (!NameEquals(name, value)) + { + throw new ArgumentException( + Resources.FormatTagHelperAttributeList_CannotAddAttribute( + nameof(TagHelperAttribute), + nameof(TagHelperAttribute.Name), + value.Name, + name), + nameof(name)); + } + + var attributeReplaced = false; + + for (var i = 0; i < Attributes.Count; i++) + { + if (NameEquals(name, Attributes[i])) + { + // We replace the first attribute with the provided value, remove all the rest. + if (!attributeReplaced) + { + // We replace the first attribute we find with the same name. + Attributes[i] = value; + attributeReplaced = true; + } + else + { + Attributes.RemoveAt(i--); + } + } + } + + // If we didn't replace an attribute value we should add value to the end of the collection. + if (!attributeReplaced) + { + Add(value); + } + } + } + + /// + bool ICollection.IsReadOnly + { + get + { + return false; + } + } + + /// + /// Adds a to the end of the collection with the specified + /// and . + /// + /// The of the attribute to add. + /// The of the attribute to add. + public void Add([NotNull] string name, object value) + { + Attributes.Add(new TagHelperAttribute(name, value)); + } + + /// + /// + /// 's must not be null. + /// + public void Add([NotNull] TagHelperAttribute attribute) + { + if (attribute.Name == null) + { + throw new ArgumentException( + Resources.FormatTagHelperAttributeList_CannotAddWithNullName( + typeof(TagHelperAttribute).FullName, + nameof(TagHelperAttribute.Name)), + nameof(attribute)); + } + + Attributes.Add(attribute); + } + + /// + /// + /// 's must not be null. + /// + public void Insert(int index, [NotNull] TagHelperAttribute attribute) + { + if (attribute.Name == null) + { + throw new ArgumentException( + Resources.FormatTagHelperAttributeList_CannotAddWithNullName( + typeof(TagHelperAttribute).FullName, + nameof(TagHelperAttribute.Name)), + nameof(attribute)); + } + + Attributes.Insert(index, attribute); + } + + /// + public void CopyTo([NotNull] TagHelperAttribute[] array, int index) + { + Attributes.CopyTo(array, index); + } + + /// + /// + /// s is compared case-insensitively. + /// + public bool Remove([NotNull] TagHelperAttribute attribute) + { + return Attributes.Remove(attribute); + } + + /// + public void RemoveAt(int index) + { + Attributes.RemoveAt(index); + } + + /// + /// Removes all s with matching + /// . + /// + /// + /// The of s to remove. + /// + /// + /// true if at least 1 was removed; otherwise, false. + /// + /// is compared case-insensitively. + public bool RemoveAll([NotNull] string name) + { + return Attributes.RemoveAll(attribute => NameEquals(name, attribute)) > 0; + } + + /// + public void Clear() + { + Attributes.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs index 0d9ac0d58a..973a246444 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperContext.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Framework.Internal; @@ -25,12 +26,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// A delegate used to execute and retrieve the rendered child content /// asynchronously. public TagHelperContext( - [NotNull] IDictionary allAttributes, + [NotNull] IEnumerable allAttributes, [NotNull] IDictionary items, [NotNull] string uniqueId, [NotNull] Func> getChildContentAsync) { - AllAttributes = allAttributes; + AllAttributes = new ReadOnlyTagHelperAttributeList( + allAttributes.Select(attribute => new TagHelperAttribute(attribute.Name, attribute.Value))); Items = items; UniqueId = uniqueId; _getChildContentAsync = getChildContentAsync; @@ -39,7 +41,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// Every attribute associated with the current HTML element. /// - public IDictionary AllAttributes { get; } + public ReadOnlyTagHelperAttributeList AllAttributes { get; } /// /// Gets the collection of items used to communicate with other s. diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs index 3b47e47f6c..cb20d273fd 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperExecutionContext.cs @@ -60,8 +60,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers _endTagHelperWritingScope = endTagHelperWritingScope; SelfClosing = selfClosing; - AllAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); - HTMLAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + HTMLAttributes = new TagHelperAttributeList(); + AllAttributes = new TagHelperAttributeList(); TagName = tagName; Items = items; UniqueId = uniqueId; @@ -91,12 +91,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// /// HTML attributes. /// - public IDictionary HTMLAttributes { get; } + public TagHelperAttributeList HTMLAttributes { get; } /// /// bound attributes and HTML attributes. /// - public IDictionary AllAttributes { get; } + public TagHelperAttributeList AllAttributes { get; } /// /// An identifier unique to the HTML element this context is for. diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs index 0d6d0d33e6..44aa5754d3 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperOutput.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { // Internal for testing internal TagHelperOutput(string tagName) - : this(tagName, new Dictionary(StringComparer.OrdinalIgnoreCase)) + : this(tagName, new TagHelperAttributeList()) { } @@ -25,10 +25,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// The HTML attributes. public TagHelperOutput( string tagName, - [NotNull] IDictionary attributes) + [NotNull] TagHelperAttributeList attributes) { TagName = tagName; - Attributes = new Dictionary(attributes, StringComparer.OrdinalIgnoreCase); + Attributes = new TagHelperAttributeList(attributes); } /// @@ -94,7 +94,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// a Microsoft.AspNet.Mvc.Rendering.HtmlString instance. MVC converts most other types to a /// , then HTML encodes the result. /// - public IDictionary Attributes { get; } + public TagHelperAttributeList Attributes { get; } /// /// Changes to generate nothing. diff --git a/src/Microsoft.AspNet.Razor/Common/HashCodeCombiner.cs b/src/Microsoft.AspNet.Razor/Common/HashCodeCombiner.cs index 9e9023a92d..a1a04503ff 100644 --- a/src/Microsoft.AspNet.Razor/Common/HashCodeCombiner.cs +++ b/src/Microsoft.AspNet.Razor/Common/HashCodeCombiner.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace Microsoft.Internal.Web.Utils { - internal class HashCodeCombiner + public class HashCodeCombiner { private long _combinedHash64 = 0x1505L; diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlock.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlock.cs index 6ed4dc2fa9..57012f69d4 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlock.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlock.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers { TagName = source.TagName; Descriptors = source.Descriptors; - Attributes = new Dictionary(source.Attributes); + Attributes = new List>(source.Attributes); _start = source.Start; SelfClosing = source.SelfClosing; SourceStartTag = source.SourceStartTag; @@ -36,9 +36,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers source.Reset(); - foreach (var attributeChildren in Attributes.Values) + foreach (var attributeChildren in Attributes) { - attributeChildren.Parent = this; + attributeChildren.Value.Parent = this; } } @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers /// /// The HTML attributes. /// - public IDictionary Attributes { get; private set; } + public IList> Attributes { get; } /// public override SourceLocation Start diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs index c2849f539d..bfaab915d1 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockBuilder.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers { TagName = original.TagName; Descriptors = original.Descriptors; - Attributes = new Dictionary(original.Attributes); + Attributes = new List>(original.Attributes); } /// @@ -39,26 +39,28 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers /// Attributes of the . /// The s associated with the current HTML /// tag. - public TagHelperBlockBuilder(string tagName, - bool selfClosing, - SourceLocation start, - IDictionary attributes, - IEnumerable descriptors) + public TagHelperBlockBuilder( + string tagName, + bool selfClosing, + SourceLocation start, + IList> attributes, + IEnumerable descriptors) { TagName = tagName; SelfClosing = selfClosing; Start = start; Descriptors = descriptors; - Attributes = new Dictionary(attributes); + Attributes = new List>(attributes); Type = BlockType.Tag; CodeGenerator = new TagHelperCodeGenerator(descriptors); } // Internal for testing - internal TagHelperBlockBuilder(string tagName, - bool selfClosing, - IDictionary attributes, - IEnumerable children) + internal TagHelperBlockBuilder( + string tagName, + bool selfClosing, + IList> attributes, + IEnumerable children) { TagName = tagName; SelfClosing = selfClosing; @@ -98,7 +100,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers /// /// The HTML attributes. /// - public IDictionary Attributes { get; private set; } + public IList> Attributes { get; } /// /// The HTML tag name. diff --git a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs index d872c3f29e..b070252777 100644 --- a/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs +++ b/src/Microsoft.AspNet.Razor/Parser/TagHelpers/TagHelperBlockRewriter.cs @@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal return new TagHelperBlockBuilder(tagName, selfClosing, start, attributes, descriptors); } - private static IDictionary GetTagAttributes( + private static IList> GetTagAttributes( string tagName, bool validStructure, Block tagBlock, IEnumerable descriptors, ErrorSink errorSink) { - var attributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + var attributes = new List>(); // Build a dictionary so we can easily lookup expected attribute value lookups IReadOnlyDictionary attributeValueTypes = @@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal attribute.Key.Length); } - attributes[attribute.Key] = attribute.Value; + attributes.Add(new KeyValuePair(attribute.Key, attribute.Value)); } } @@ -137,13 +137,13 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal if (afterEquals) { - // We've captured all leading whitespace, the attribute name, and an equals with an optional + // We've captured all leading whitespace, the attribute name, and an equals with an optional // quote/double quote. We're now at: " asp-for='|...'" or " asp-for=|..." - // The goal here is to capture all symbols until the end of the attribute. Note this will not + // The goal here is to capture all symbols until the end of the attribute. Note this will not // consume an ending quote due to the symbolOffset. - // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the - // parent which is why we need to mark our start location prior to adding the symbol. + // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the + // parent which is why we need to mark our start location prior to adding the symbol. // This is needed to know the location of the attribute value start within the document. if (!capturedAttributeValueStart) { @@ -206,10 +206,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal } else if (symbol.Type == HtmlSymbolType.WhiteSpace) { - // We're at the start of the attribute, this branch may be hit on the first iterations of + // We're at the start of the attribute, this branch may be hit on the first iterations of // the loop since the parser separates attributes with their spaces included as symbols. // We're at: "| asp-for='...'" or "| asp-for=..." - // Note: This will not be hit even for situations like asp-for ="..." because the core Razor + // Note: This will not be hit even for situations like asp-for ="..." because the core Razor // parser currently does not know how to handle attributes in that format. This will be addressed // by https://github.com/aspnet/Razor/issues/123. @@ -230,7 +230,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal { errorSink.OnError( span.Start, - RazorResources.TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed, + RazorResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed, span.Content.Length); } @@ -375,7 +375,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal else if (isDynamic && childSpan.CodeGenerator == SpanCodeGenerator.Null) { // Usually the dynamic code generator handles rendering the null code generators underneath - // it. This doesn't make sense in terms of tag helpers though, we need to change null code + // it. This doesn't make sense in terms of tag helpers though, we need to change null code // generators to markup code generators. newCodeGenerator = new MarkupCodeGenerator(); diff --git a/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs b/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs index d798e7f7f3..59baba43be 100644 --- a/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs +++ b/src/Microsoft.AspNet.Razor/Properties/RazorResources.Designer.cs @@ -1369,17 +1369,17 @@ namespace Microsoft.AspNet.Razor /// /// TagHelper attributes must be welformed. /// - internal static string TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed + internal static string TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed { - get { return GetString("TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed"); } + get { return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed"); } } /// /// TagHelper attributes must be welformed. /// - internal static string FormatTagHelperBlockRewriter_TagHelperAttributesMustBeWelformed() + internal static string FormatTagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed() { - return GetString("TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed"); + return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed"); } /// diff --git a/src/Microsoft.AspNet.Razor/RazorResources.resx b/src/Microsoft.AspNet.Razor/RazorResources.resx index 5b67709170..a94a7393dc 100644 --- a/src/Microsoft.AspNet.Razor/RazorResources.resx +++ b/src/Microsoft.AspNet.Razor/RazorResources.resx @@ -390,7 +390,7 @@ Instead, wrap the contents of the block in "{{}}": Missing close angle for tag helper '{0}'. - + TagHelper attributes must be welformed.