Add TagHelperOutput.Attributes and TagHelperContext.AllAttributes replacement.

- Added a TagHelperAttributes object that's used to hold 1=>many attributes. Is used for TagHelperOutput.Attributes.
- Added a ReadOnlyTagHelperAttributes object that holds 1=>many IReadOnlyTagHelperAttributes. Is used for TagHelperContext.AllAttributes.
- Added a TagHelperAttribute object which is used to represent attributes.
- Added a IReadOnlyTagHelperAttribute which is used to represent attributes which cannot be modified.

#279
This commit is contained in:
N. Taylor Mullen 2015-04-23 12:18:04 -07:00
parent de95f67400
commit 7c604d2b11
15 changed files with 625 additions and 44 deletions

View File

@ -218,6 +218,38 @@ namespace Microsoft.AspNet.Razor.Runtime
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidBoundAttributeName"), p0, p1, p2);
}
/// <summary>
/// Cannot add a '{0}' with a null '{1}'.
/// </summary>
internal static string TagHelperAttributeList_CannotAddWithNullName
{
get { return GetString("TagHelperAttributeList_CannotAddWithNullName"); }
}
/// <summary>
/// Cannot add a '{0}' with a null '{1}'.
/// </summary>
internal static string FormatTagHelperAttributeList_CannotAddWithNullName(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddWithNullName"), p0, p1);
}
/// <summary>
/// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.
/// </summary>
internal static string TagHelperAttributeList_CannotAddAttribute
{
get { return GetString("TagHelperAttributeList_CannotAddAttribute"); }
}
/// <summary>
/// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.
/// </summary>
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);

View File

@ -156,4 +156,10 @@
<data name="TagHelperDescriptorFactory_InvalidBoundAttributeName" xml:space="preserve">
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes beginning with '{2}'.</value>
</data>
<data name="TagHelperAttributeList_CannotAddWithNullName" xml:space="preserve">
<value>Cannot add a '{0}' with a null '{1}'.</value>
</data>
<data name="TagHelperAttributeList_CannotAddAttribute" xml:space="preserve">
<value>Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.</value>
</data>
</root>

View File

@ -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
{
/// <summary>
/// A read-only HTML tag helper attribute.
/// </summary>
public interface IReadOnlyTagHelperAttribute : IEquatable<IReadOnlyTagHelperAttribute>
{
/// <summary>
/// Gets the name of the attribute.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the value of the attribute.
/// </summary>
object Value { get; }
}
}

View File

@ -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
{
/// <summary>
/// A read-only collection of <typeparamref name="TAttribute"/>s.
/// </summary>
/// <typeparam name="TAttribute">
/// The type of <see cref="IReadOnlyTagHelperAttribute"/>s in the collection.
/// </typeparam>
public class ReadOnlyTagHelperAttributeList<TAttribute> : IReadOnlyList<TAttribute>
where TAttribute : IReadOnlyTagHelperAttribute
{
/// <summary>
/// Instantiates a new instance of <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> with an empty
/// collection.
/// </summary>
protected ReadOnlyTagHelperAttributeList()
{
Attributes = new List<TAttribute>();
}
/// <summary>
/// Instantiates a new instance of <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> with the specified
/// <paramref name="attributes"/>.
/// </summary>
/// <param name="attributes">The collection to wrap.</param>
public ReadOnlyTagHelperAttributeList([NotNull] IEnumerable<TAttribute> attributes)
{
Attributes = new List<TAttribute>(attributes);
}
/// <summary>
/// The underlying collection of <typeparamref name="TAttribute"/>s.
/// </summary>
/// <remarks>Intended for use in a non-read-only subclass. Changes to this <see cref="List{TAttribute}"/> will
/// affect all getters that <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> provides.</remarks>
protected List<TAttribute> Attributes { get; }
/// <inheritdoc />
public TAttribute this[int index]
{
get
{
return Attributes[index];
}
}
/// <summary>
/// Gets the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
/// matching <paramref name="name"/>.
/// </summary>
/// <param name="name">
/// The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the <typeparamref name="TAttribute"/> to get.
/// </param>
/// <returns>The first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
/// matching <paramref name="name"/>.
/// </returns>
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
public TAttribute this[[NotNull] string name]
{
get
{
return Attributes.FirstOrDefault(attribute => NameEquals(name, attribute));
}
}
/// <inheritdoc />
public int Count
{
get
{
return Attributes.Count;
}
}
/// <summary>
/// Determines whether a <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
/// collection.
/// </summary>
/// <param name="item">The <typeparamref name="TAttribute"/> to locate.</param>
/// <returns>
/// <c>true</c> if an <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
/// collection; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// <paramref name="item"/>s <see cref="IReadOnlyTagHelperAttribute.Name"/> is compared case-insensitively.
/// </remarks>
public bool Contains([NotNull] TAttribute item)
{
return Attributes.Contains(item);
}
/// <summary>
/// Determines whether a <typeparamref name="TAttribute"/> with the same
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection.
/// </summary>
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
/// <typeparamref name="TAttribute"/> to get.</param>
/// <returns>
/// <c>true</c> if a <typeparamref name="TAttribute"/> with the same
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.
/// </returns>
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
public bool ContainsName([NotNull] string name)
{
return Attributes.Any(attribute => NameEquals(name, attribute));
}
/// <summary>
/// Searches for a <typeparamref name="TAttribute"/> matching <paramref name="item"/> in the collection and
/// returns the zero-based index of the first occurrence.
/// </summary>
/// <param name="item">The <typeparamref name="TAttribute"/> to locate.</param>
/// <returns>The zero-based index of the first occurrence of a <typeparamref name="TAttribute"/> matching
/// <paramref name="item"/> in the collection, if found; otherwise, 1.</returns>
/// <remarks>
/// <paramref name="item"/>s <see cref="IReadOnlyTagHelperAttribute.Name"/> is compared case-insensitively.
/// </remarks>
public int IndexOf([NotNull] TAttribute item)
{
return Attributes.IndexOf(item);
}
/// <summary>
/// Retrieves the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
/// matching <paramref name="name"/>.
/// </summary>
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
/// <typeparamref name="TAttribute"/> to get.</param>
/// <param name="attribute">When this method returns, the first <typeparamref name="TAttribute"/> with
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>, if found; otherwise,
/// <c>null</c>.</param>
/// <returns><c>true</c> if a <typeparamref name="TAttribute"/> with the same
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.</returns>
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
public bool TryGetAttribute([NotNull] string name, out TAttribute attribute)
{
attribute = Attributes.FirstOrDefault(attr => NameEquals(name, attr));
return attribute != null;
}
/// <summary>
/// Retrieves <typeparamref name="TAttribute"/>s in the collection with
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>.
/// </summary>
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
/// <typeparamref name="TAttribute"/>s to get.</param>
/// <param name="attributes">When this method returns, the <typeparamref name="TAttribute"/>s with
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>, if at least one is
/// found; otherwise, <c>null</c>.</param>
/// <returns><c>true</c> if at least one <typeparamref name="TAttribute"/> with the same
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.</returns>
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
public bool TryGetAttributes([NotNull] string name, out IEnumerable<TAttribute> attributes)
{
attributes = Attributes.Where(attribute => NameEquals(name, attribute));
return attributes.Any();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <inheritdoc />
public IEnumerator<TAttribute> GetEnumerator()
{
return Attributes.GetEnumerator();
}
/// <summary>
/// Determines if the specified <paramref name="attribute"/> has the same name as <paramref name="name"/>.
/// </summary>
/// <param name="name">The value to compare against <paramref name="attribute"/>s
/// <see cref="TagHelperAttribute.Name"/>.</param>
/// <param name="attribute">The attribute to compare against.</param>
/// <returns><c>true</c> if <paramref name="name"/> case-insensitively matches <paramref name="attribute"/>s
/// <see cref="TagHelperAttribute.Name"/>.</returns>
protected static bool NameEquals(string name, [NotNull] TAttribute attribute)
{
return string.Equals(name, attribute.Name, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -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
{
/// <summary>
/// An HTML tag helper attribute.
/// </summary>
public class TagHelperAttribute : IReadOnlyTagHelperAttribute
{
private static readonly int TypeHashCode = typeof(TagHelperAttribute).GetHashCode();
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperAttribute"/>.
/// </summary>
public TagHelperAttribute()
{
}
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperAttribute"/> with the specified <paramref name="name"/>
/// and <paramref name="value"/>.
/// </summary>
/// <param name="name">The <see cref="Name"/> of the attribute.</param>
/// <param name="value">The <see cref="Value"/> of the attribute.</param>
public TagHelperAttribute(string name, object value)
{
Name = name;
Value = value;
}
/// <summary>
/// Gets or sets the name of the attribute.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the value of the attribute.
/// </summary>
public object Value { get; set; }
/// <summary>
/// Converts the specified <paramref name="value"/> into a <see cref="TagHelperAttribute"/>.
/// </summary>
/// <param name="value">The <see cref="Value"/> of the created <see cref="TagHelperAttribute"/>.</param>
/// <remarks>Created <see cref="TagHelperAttribute"/>s <see cref="Name"/> is set to <c>null</c>.</remarks>
public static implicit operator TagHelperAttribute(string value)
{
return new TagHelperAttribute
{
Value = value
};
}
/// <inheritdoc />
/// <remarks><see cref="Name"/> is compared case-insensitively.</remarks>
public bool Equals(IReadOnlyTagHelperAttribute other)
{
return
other != null &&
string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) &&
Equals(Value, other.Value);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
var other = obj as IReadOnlyTagHelperAttribute;
return Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return TypeHashCode;
}
}
}

View File

@ -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
{
/// <summary>
/// A collection of <see cref="TagHelperAttribute"/>s.
/// </summary>
public class TagHelperAttributeList : ReadOnlyTagHelperAttributeList<TagHelperAttribute>, IList<TagHelperAttribute>
{
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperAttributeList"/> with an empty collection.
/// </summary>
public TagHelperAttributeList()
: base()
{
}
/// <summary>
/// Instantiates a new instance of <see cref="TagHelperAttributeList"/> with the specified
/// <paramref name="attributes"/>.
/// </summary>
/// <param name="attributes">The collection to wrap.</param>
public TagHelperAttributeList([NotNull] IEnumerable<TagHelperAttribute> attributes)
: base(attributes)
{
}
/// <inheritdoc />
/// <remarks>
/// <paramref name="value"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
/// </remarks>
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;
}
}
/// <summary>
/// Gets the first <see cref="TagHelperAttribute"/> with <see cref="TagHelperAttribute.Name"/> matching
/// <paramref name="name"/>. When setting, replaces the first matching
/// <see cref="TagHelperAttribute"/> with the specified <paramref name="value"/> and removes any additional
/// matching <see cref="TagHelperAttribute"/>s. If a matching <see cref="TagHelperAttribute"/> is not found,
/// adds the specified <paramref name="value"/> to the end of the collection.
/// </summary>
/// <param name="name">
/// The <see cref="TagHelperAttribute.Name"/> of the <see cref="TagHelperAttribute"/> to get or set.
/// </param>
/// <returns>The first <see cref="TagHelperAttribute"/> with <see cref="TagHelperAttribute.Name"/> matching
/// <paramref name="name"/>.
/// </returns>
/// <remarks><paramref name="name"/> is compared case-insensitively. When setting,
/// <see cref="TagHelperAttribute"/>s <see cref="TagHelperAttribute.Name"/> must be <c>null</c> or
/// case-insensitively match the specified <paramref name="name"/>.</remarks>
/// <example>
/// <code>
/// 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");
/// </code>
/// </example>
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);
}
}
}
/// <inheritdoc />
bool ICollection<TagHelperAttribute>.IsReadOnly
{
get
{
return false;
}
}
/// <summary>
/// Adds a <see cref="TagHelperAttribute"/> to the end of the collection with the specified
/// <paramref name="name"/> and <paramref name="value"/>.
/// </summary>
/// <param name="name">The <see cref="TagHelperAttribute.Name"/> of the attribute to add.</param>
/// <param name="value">The <see cref="TagHelperAttribute.Value"/> of the attribute to add.</param>
public void Add([NotNull] string name, object value)
{
Attributes.Add(new TagHelperAttribute(name, value));
}
/// <inheritdoc />
/// <remarks>
/// <paramref name="attribute"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
/// </remarks>
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);
}
/// <inheritdoc />
/// <remarks>
/// <paramref name="attribute"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
/// </remarks>
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);
}
/// <inheritdoc />
public void CopyTo([NotNull] TagHelperAttribute[] array, int index)
{
Attributes.CopyTo(array, index);
}
/// <inheritdoc />
/// <remarks>
/// <paramref name="attribute"/>s <see cref="TagHelperAttribute.Name"/> is compared case-insensitively.
/// </remarks>
public bool Remove([NotNull] TagHelperAttribute attribute)
{
return Attributes.Remove(attribute);
}
/// <inheritdoc />
public void RemoveAt(int index)
{
Attributes.RemoveAt(index);
}
/// <summary>
/// Removes all <see cref="TagHelperAttribute"/>s with <see cref="TagHelperAttribute.Name"/> matching
/// <paramref name="name"/>.
/// </summary>
/// <param name="name">
/// The <see cref="TagHelperAttribute.Name"/> of <see cref="TagHelperAttribute"/>s to remove.
/// </param>
/// <returns>
/// <c>true</c> if at least 1 <see cref="TagHelperAttribute"/> was removed; otherwise, <c>false</c>.
/// </returns>
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
public bool RemoveAll([NotNull] string name)
{
return Attributes.RemoveAll(attribute => NameEquals(name, attribute)) > 0;
}
/// <inheritdoc />
public void Clear()
{
Attributes.Clear();
}
}
}

View File

@ -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
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
/// asynchronously.</param>
public TagHelperContext(
[NotNull] IDictionary<string, object> allAttributes,
[NotNull] IEnumerable<IReadOnlyTagHelperAttribute> allAttributes,
[NotNull] IDictionary<object, object> items,
[NotNull] string uniqueId,
[NotNull] Func<Task<TagHelperContent>> getChildContentAsync)
{
AllAttributes = allAttributes;
AllAttributes = new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
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
/// <summary>
/// Every attribute associated with the current HTML element.
/// </summary>
public IDictionary<string, object> AllAttributes { get; }
public ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute> AllAttributes { get; }
/// <summary>
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.

View File

@ -60,8 +60,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
_endTagHelperWritingScope = endTagHelperWritingScope;
SelfClosing = selfClosing;
AllAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
HTMLAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
HTMLAttributes = new TagHelperAttributeList();
AllAttributes = new TagHelperAttributeList();
TagName = tagName;
Items = items;
UniqueId = uniqueId;
@ -91,12 +91,12 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <summary>
/// HTML attributes.
/// </summary>
public IDictionary<string, object> HTMLAttributes { get; }
public TagHelperAttributeList HTMLAttributes { get; }
/// <summary>
/// <see cref="ITagHelper"/> bound attributes and HTML attributes.
/// </summary>
public IDictionary<string, object> AllAttributes { get; }
public TagHelperAttributeList AllAttributes { get; }
/// <summary>
/// An identifier unique to the HTML element this context is for.

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
// Internal for testing
internal TagHelperOutput(string tagName)
: this(tagName, new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase))
: this(tagName, new TagHelperAttributeList())
{
}
@ -25,10 +25,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// <param name="attributes">The HTML attributes.</param>
public TagHelperOutput(
string tagName,
[NotNull] IDictionary<string, object> attributes)
[NotNull] TagHelperAttributeList attributes)
{
TagName = tagName;
Attributes = new Dictionary<string, object>(attributes, StringComparer.OrdinalIgnoreCase);
Attributes = new TagHelperAttributeList(attributes);
}
/// <summary>
@ -94,7 +94,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// a <c>Microsoft.AspNet.Mvc.Rendering.HtmlString</c> instance. MVC converts most other types to a
/// <see cref="string"/>, then HTML encodes the result.
/// </remarks>
public IDictionary<string, object> Attributes { get; }
public TagHelperAttributeList Attributes { get; }
/// <summary>
/// Changes <see cref="TagHelperOutput"/> to generate nothing.

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace Microsoft.Internal.Web.Utils
{
internal class HashCodeCombiner
public class HashCodeCombiner
{
private long _combinedHash64 = 0x1505L;

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
{
TagName = source.TagName;
Descriptors = source.Descriptors;
Attributes = new Dictionary<string, SyntaxTreeNode>(source.Attributes);
Attributes = new List<KeyValuePair<string, SyntaxTreeNode>>(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
/// <summary>
/// The HTML attributes.
/// </summary>
public IDictionary<string, SyntaxTreeNode> Attributes { get; private set; }
public IList<KeyValuePair<string, SyntaxTreeNode>> Attributes { get; }
/// <inheritdoc />
public override SourceLocation Start

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
{
TagName = original.TagName;
Descriptors = original.Descriptors;
Attributes = new Dictionary<string, SyntaxTreeNode>(original.Attributes);
Attributes = new List<KeyValuePair<string, SyntaxTreeNode>>(original.Attributes);
}
/// <summary>
@ -39,26 +39,28 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
/// <param name="attributes">Attributes of the <see cref="TagHelperBlock"/>.</param>
/// <param name="descriptors">The <see cref="TagHelperDescriptor"/>s associated with the current HTML
/// tag.</param>
public TagHelperBlockBuilder(string tagName,
bool selfClosing,
SourceLocation start,
IDictionary<string, SyntaxTreeNode> attributes,
IEnumerable<TagHelperDescriptor> descriptors)
public TagHelperBlockBuilder(
string tagName,
bool selfClosing,
SourceLocation start,
IList<KeyValuePair<string, SyntaxTreeNode>> attributes,
IEnumerable<TagHelperDescriptor> descriptors)
{
TagName = tagName;
SelfClosing = selfClosing;
Start = start;
Descriptors = descriptors;
Attributes = new Dictionary<string, SyntaxTreeNode>(attributes);
Attributes = new List<KeyValuePair<string, SyntaxTreeNode>>(attributes);
Type = BlockType.Tag;
CodeGenerator = new TagHelperCodeGenerator(descriptors);
}
// Internal for testing
internal TagHelperBlockBuilder(string tagName,
bool selfClosing,
IDictionary<string, SyntaxTreeNode> attributes,
IEnumerable<SyntaxTreeNode> children)
internal TagHelperBlockBuilder(
string tagName,
bool selfClosing,
IList<KeyValuePair<string, SyntaxTreeNode>> attributes,
IEnumerable<SyntaxTreeNode> children)
{
TagName = tagName;
SelfClosing = selfClosing;
@ -98,7 +100,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
/// <summary>
/// The HTML attributes.
/// </summary>
public IDictionary<string, SyntaxTreeNode> Attributes { get; private set; }
public IList<KeyValuePair<string, SyntaxTreeNode>> Attributes { get; }
/// <summary>
/// The HTML tag name.

View File

@ -31,14 +31,14 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
return new TagHelperBlockBuilder(tagName, selfClosing, start, attributes, descriptors);
}
private static IDictionary<string, SyntaxTreeNode> GetTagAttributes(
private static IList<KeyValuePair<string, SyntaxTreeNode>> GetTagAttributes(
string tagName,
bool validStructure,
Block tagBlock,
IEnumerable<TagHelperDescriptor> descriptors,
ErrorSink errorSink)
{
var attributes = new Dictionary<string, SyntaxTreeNode>(StringComparer.OrdinalIgnoreCase);
var attributes = new List<KeyValuePair<string, SyntaxTreeNode>>();
// Build a dictionary so we can easily lookup expected attribute value lookups
IReadOnlyDictionary<string, string> attributeValueTypes =
@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
attribute.Key.Length);
}
attributes[attribute.Key] = attribute.Value;
attributes.Add(new KeyValuePair<string, SyntaxTreeNode>(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();

View File

@ -1369,17 +1369,17 @@ namespace Microsoft.AspNet.Razor
/// <summary>
/// TagHelper attributes must be welformed.
/// </summary>
internal static string TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed
internal static string TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed
{
get { return GetString("TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed"); }
get { return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed"); }
}
/// <summary>
/// TagHelper attributes must be welformed.
/// </summary>
internal static string FormatTagHelperBlockRewriter_TagHelperAttributesMustBeWelformed()
internal static string FormatTagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed()
{
return GetString("TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed");
return GetString("TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed");
}
/// <summary>

View File

@ -390,7 +390,7 @@ Instead, wrap the contents of the block in "{{}}":
<data name="TagHelpersParseTreeRewriter_MissingCloseAngle" xml:space="preserve">
<value>Missing close angle for tag helper '{0}'.</value>
</data>
<data name="TagHelperBlockRewriter_TagHelperAttributesMustBeWelformed" xml:space="preserve">
<data name="TagHelperBlockRewriter_TagHelperAttributeListMustBeWelformed" xml:space="preserve">
<value>TagHelper attributes must be welformed.</value>
</data>
<data name="TagHelpers_AttributeExpressionRequired" xml:space="preserve">