diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs
index bbfaf4292c..f1dcaa3908 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs
@@ -842,6 +842,22 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeToSession"), p0, p1);
}
+ ///
+ /// The collection already contains an entry with key '{0}'.
+ ///
+ internal static string Dictionary_DuplicateKey
+ {
+ get { return GetString("Dictionary_DuplicateKey"); }
+ }
+
+ ///
+ /// The collection already contains an entry with key '{0}'.
+ ///
+ internal static string FormatDictionary_DuplicateKey(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Dictionary_DuplicateKey"), p0);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/TagBuilder.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/TagBuilder.cs
index a329153a6c..0dadb9ea8f 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/TagBuilder.cs
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/TagBuilder.cs
@@ -17,6 +17,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
[DebuggerDisplay("{DebuggerToString()}")]
public class TagBuilder : IHtmlContent
{
+ private AttributeDictionary _attributes;
+
public TagBuilder(string tagName)
{
if (string.IsNullOrEmpty(tagName))
@@ -25,15 +27,25 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
TagName = tagName;
- Attributes = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
-
InnerHtml = new BufferedHtmlContent();
}
///
/// Gets the set of attributes that will be written to the tag.
///
- public IDictionary Attributes { get; }
+ public AttributeDictionary Attributes
+ {
+ get
+ {
+ // Perf: Avoid allocating `_attributes` if possible
+ if (_attributes == null)
+ {
+ _attributes = new AttributeDictionary();
+ }
+
+ return _attributes;
+ }
+ }
public IHtmlContentBuilder InnerHtml { get; }
@@ -151,20 +163,24 @@ namespace Microsoft.AspNet.Mvc.Rendering
private void AppendAttributes(TextWriter writer, IHtmlEncoder encoder)
{
- foreach (var attribute in Attributes)
+ // Perf: Avoid allocating enumerator for `_attributes` if possible
+ if (_attributes != null && _attributes.Count > 0)
{
- var key = attribute.Key;
- if (string.Equals(key, "id", StringComparison.OrdinalIgnoreCase) &&
- string.IsNullOrEmpty(attribute.Value))
+ foreach (var attribute in Attributes)
{
- continue;
- }
+ var key = attribute.Key;
+ if (string.Equals(key, "id", StringComparison.OrdinalIgnoreCase) &&
+ string.IsNullOrEmpty(attribute.Value))
+ {
+ continue;
+ }
- writer.Write(" ");
- writer.Write(key);
- writer.Write("=\"");
- encoder.HtmlEncode(attribute.Value, writer);
- writer.Write("\"");
+ writer.Write(" ");
+ writer.Write(key);
+ writer.Write("=\"");
+ encoder.HtmlEncode(attribute.Value, writer);
+ writer.Write("\"");
+ }
}
}
@@ -193,7 +209,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
public void MergeAttributes(IDictionary attributes, bool replaceExisting)
{
- if (attributes != null)
+ // Perf: Avoid allocating enumerator for `attributes` if possible
+ if (attributes != null && attributes.Count > 0)
{
foreach (var entry in attributes)
{
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx
index 3abf07efde..eddad01662 100644
--- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx
@@ -274,4 +274,7 @@
The '{0}' cannot serialize an object of type '{1}' to session state.
+
+ The collection already contains an entry with key '{0}'.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs
new file mode 100644
index 0000000000..5a4a4de110
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs
@@ -0,0 +1,681 @@
+// Copyright (c) .NET Foundation. 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;
+
+namespace Microsoft.AspNet.Mvc.ViewFeatures
+{
+ ///
+ /// A dictionary for HTML attributes.
+ ///
+ public class AttributeDictionary : IDictionary, IReadOnlyDictionary
+ {
+ private List> _items;
+
+ ///
+ public string this[string key]
+ {
+ get
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ var index = Find(key);
+ if (index < 0)
+ {
+ throw new KeyNotFoundException();
+ }
+ else
+ {
+ return Get(index).Value;
+ }
+ }
+
+ set
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ var item = new KeyValuePair(key, value);
+ var index = Find(key);
+ if (index < 0)
+ {
+ Insert(~index, item);
+ }
+ else
+ {
+ Set(index, item);
+ }
+ }
+ }
+
+ ///
+ public int Count => _items == null ? 0 : _items.Count;
+
+ ///
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ public ICollection Keys
+ {
+ get
+ {
+ return new KeyCollection(this);
+ }
+ }
+
+ ///
+ public ICollection Values
+ {
+ get
+ {
+ return new ValueCollection(this);
+ }
+ }
+
+ ///
+ IEnumerable IReadOnlyDictionary.Keys
+ {
+ get
+ {
+ return new KeyCollection(this);
+ }
+ }
+
+ ///
+ IEnumerable IReadOnlyDictionary.Values
+ {
+ get
+ {
+ return new ValueCollection(this);
+ }
+ }
+
+ private KeyValuePair Get(int index)
+ {
+ if (index >= Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return _items[index];
+ }
+
+ private void Set(int index, KeyValuePair value)
+ {
+ if (index > Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ if (_items == null)
+ {
+ _items = new List>();
+ }
+
+ _items[index] = value;
+ }
+
+ private void Insert(int index, KeyValuePair value)
+ {
+ if (index > Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ if (_items == null)
+ {
+ _items = new List>();
+ }
+
+ _items.Insert(index, value);
+ }
+
+ private void Remove(int index)
+ {
+ if (index >= Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+
+ _items.RemoveAt(index);
+ }
+
+ // This API is a lot like List.BinarySearch https://msdn.microsoft.com/en-us/library/3f90y839(v=vs.110).aspx
+ // If an item is not found, we return the compliment of where it belongs. Then we don't need to search again
+ // to do something with it.
+ private int Find(string key)
+ {
+ if (Count == 0)
+ {
+ return ~0;
+ }
+
+ var start = 0;
+ var end = Count - 1;
+
+ while (start <= end)
+ {
+ var pivot = start + (end - start >> 1);
+
+ var compare = StringComparer.OrdinalIgnoreCase.Compare(Get(pivot).Key, key);
+ if (compare == 0)
+ {
+ return pivot;
+ }
+ if (compare < 0)
+ {
+ start = pivot + 1;
+ }
+ else
+ {
+ end = pivot - 1;
+ }
+ }
+
+ return ~start;
+ }
+
+ ///
+ public void Clear()
+ {
+ if (_items != null)
+ {
+ _items.Clear();
+ }
+ }
+
+ ///
+ public void Add(KeyValuePair item)
+ {
+ if (item.Key == null)
+ {
+ throw new ArgumentException(
+ Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(KeyValuePair.Key),
+ nameof(KeyValuePair)),
+ nameof(item));
+ }
+
+ var index = Find(item.Key);
+ if (index < 0)
+ {
+ Insert(~index, item);
+ }
+ else
+ {
+ throw new InvalidOperationException(Resources.FormatDictionary_DuplicateKey(item.Key));
+ }
+ }
+
+ ///
+ public void Add(string key, string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ Add(new KeyValuePair(key, value));
+ }
+
+ ///
+ public bool Contains(KeyValuePair item)
+ {
+ if (item.Key == null)
+ {
+ throw new ArgumentException(
+ Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(KeyValuePair.Key),
+ nameof(KeyValuePair)),
+ nameof(item));
+ }
+
+ var index = Find(item.Key);
+ if (index < 0)
+ {
+ return false;
+ }
+ else
+ {
+ return string.Equals(item.Value, Get(index).Value, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ ///
+ public bool ContainsKey(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (Count == 0)
+ {
+ return false;
+ }
+
+ return Find(key) >= 0;
+ }
+
+ ///
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (arrayIndex < 0 || arrayIndex >= array.Length)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ for (var i = 0; i < Count; i++)
+ {
+ array[arrayIndex + i] = Get(i);
+ }
+ }
+
+ ///
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ ///
+ public bool Remove(KeyValuePair item)
+ {
+ if (item.Key == null)
+ {
+ throw new ArgumentException(
+ Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(KeyValuePair.Key),
+ nameof(KeyValuePair)),
+ nameof(item));
+ }
+
+ var index = Find(item.Key);
+ if (index < 0)
+ {
+ return false;
+ }
+ else if (string.Equals(item.Value, Get(index).Value, StringComparison.OrdinalIgnoreCase))
+ {
+ Remove(index);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ public bool Remove(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ var index = Find(key);
+ if (index < 0)
+ {
+ return false;
+ }
+ else
+ {
+ Remove(index);
+ return true;
+ }
+ }
+
+ ///
+ public bool TryGetValue(string key, out string value)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ var index = Find(key);
+ if (index < 0)
+ {
+ value = null;
+ return false;
+ }
+ else
+ {
+ value = Get(index).Value;
+ return true;
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// An enumerator for .
+ ///
+ public struct Enumerator : IEnumerator>
+ {
+ private readonly AttributeDictionary _attributes;
+
+ private int _index;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The .
+ public Enumerator(AttributeDictionary attributes)
+ {
+ _attributes = attributes;
+
+ _index = -1;
+ }
+
+ ///
+ public KeyValuePair Current
+ {
+ get
+ {
+ return _attributes.Get(_index);
+ }
+ }
+
+ ///
+ object IEnumerator.Current
+ {
+ get
+ {
+ return Current;
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ public bool MoveNext()
+ {
+ _index++;
+ return _index < _attributes.Count;
+ }
+
+ ///
+ public void Reset()
+ {
+ _index = -1;
+ }
+ }
+
+ private class KeyCollection : ICollection
+ {
+ private readonly AttributeDictionary _attributes;
+
+ public KeyCollection(AttributeDictionary attributes)
+ {
+ _attributes = attributes;
+ }
+
+ public int Count => _attributes.Count;
+
+ public bool IsReadOnly => true;
+
+ public void Add(string item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool Contains(string item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ for (var i = 0; i < _attributes.Count; i++)
+ {
+ if (string.Equals(item, _attributes.Get(i).Key, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void CopyTo(string[] array, int arrayIndex)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (arrayIndex < 0 || arrayIndex >= array.Length)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ for (var i = 0; i < _attributes.Count; i++)
+ {
+ array[arrayIndex + i] = _attributes.Get(i).Key;
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this._attributes);
+ }
+
+ public bool Remove(string item)
+ {
+ throw new NotSupportedException();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public struct Enumerator : IEnumerator
+ {
+ private readonly AttributeDictionary _attributes;
+
+ private int _index;
+
+ public Enumerator(AttributeDictionary attributes)
+ {
+ _attributes = attributes;
+
+ _index = -1;
+ }
+
+ public string Current
+ {
+ get
+ {
+ return _attributes.Get(_index).Key;
+ }
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ return Current;
+ }
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ _index++;
+ return _index < _attributes.Count;
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ }
+ }
+ }
+
+ private class ValueCollection : ICollection
+ {
+ private readonly AttributeDictionary _attributes;
+
+ public ValueCollection(AttributeDictionary attributes)
+ {
+ _attributes = attributes;
+ }
+ public int Count => _attributes.Count;
+
+ public bool IsReadOnly => true;
+
+ public void Add(string item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool Contains(string item)
+ {
+ for (var i = 0; i < _attributes.Count; i++)
+ {
+ if (string.Equals(item, _attributes.Get(i).Value, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void CopyTo(string[] array, int arrayIndex)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (arrayIndex < 0 || arrayIndex >= array.Length)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ for (var i = 0; i < _attributes.Count; i++)
+ {
+ array[arrayIndex + i] = _attributes.Get(i).Value;
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this._attributes);
+ }
+
+ public bool Remove(string item)
+ {
+ throw new NotSupportedException();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public struct Enumerator : IEnumerator
+ {
+ private readonly AttributeDictionary _attributes;
+
+ private int _index;
+
+ public Enumerator(AttributeDictionary attributes)
+ {
+ _attributes = attributes;
+
+ _index = -1;
+ }
+
+ public string Current
+ {
+ get
+ {
+ return _attributes.Get(_index).Key;
+ }
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ return Current;
+ }
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ _index++;
+ return _index < _attributes.Count;
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs
index fa83cf6cfe..948012918f 100644
--- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs
@@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Core.Rendering
[Theory]
[InlineData(false, "Hello", "World")]
- [InlineData(true, "Hello", "something else")]
+ [InlineData(true, "hello", "something else")]
public void MergeAttribute_IgnoresCase(bool replaceExisting, string expectedKey, string expectedValue)
{
// Arrange
@@ -54,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.Core.Rendering
// Assert
var attribute = Assert.Single(tagBuilder.Attributes);
- Assert.Equal(new KeyValuePair("ClaSs", "success btn"), attribute);
+ Assert.Equal(new KeyValuePair("class", "success btn"), attribute);
}
[Fact]
diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs
new file mode 100644
index 0000000000..64125efcf6
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs
@@ -0,0 +1,464 @@
+// Copyright (c) .NET Foundation. 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 System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.ViewFeatures
+{
+ public class AttributeDictionaryTest
+ {
+ [Fact]
+ public void AttributeDictionary_AddItems()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ // Act
+ attributes.Add("zero", "0");
+ attributes.Add("one", "1");
+ attributes.Add("two", "2");
+
+ // Assert
+ Assert.Equal(3, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("one", "1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_AddItems_AsKeyValuePairs()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ // Act
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Assert
+ Assert.Equal(3, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("one", "1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Add_DuplicateKey()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add("zero", "0");
+ attributes.Add("one", "1");
+ attributes.Add("two", "2");
+
+ // Act & Assert
+ Assert.Throws(() => attributes.Add("one", "15"));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Clear()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ attributes.Clear();
+
+ // Assert
+ Assert.Equal(0, attributes.Count);
+ Assert.Empty(attributes);
+ }
+
+ [Fact]
+ public void AttributeDictionary_Contains_Success()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.Contains(new KeyValuePair("zero", "0"));
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void AttributeDictionary_Contains_Failure()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.Contains(new KeyValuePair("zero", "nada"));
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void AttributeDictionary_ContainsKey_Success()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.ContainsKey("one");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void AttributeDictionary_ContainsKey_Failure()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.ContainsKey("one!");
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void AttributeDictionary_CopyTo()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ var array = new KeyValuePair[attributes.Count + 1];
+
+ // Act
+ attributes.CopyTo(array, 1);
+
+ // Assert
+ Assert.Collection(
+ array,
+ kvp => Assert.Equal(default(KeyValuePair), kvp),
+ kvp => Assert.Equal(new KeyValuePair("one", "1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_IsReadOnly()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ // Act
+ var result = attributes.IsReadOnly;
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void AttributeDictionary_Keys()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var keys = attributes.Keys;
+
+ // Assert
+ Assert.Equal(3, keys.Count);
+ Assert.Collection(
+ keys,
+ key => Assert.Equal("one", key),
+ key => Assert.Equal("two", key),
+ key => Assert.Equal("zero", key));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Remove_Success()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.Remove("one");
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(2, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Remove_Failure()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.Remove("nada");
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(3, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("one", "1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Remove_KeyValuePair_Success()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.Remove(new KeyValuePair("one", "1"));
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal(2, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Remove_KeyValuePair_Failure()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var result = attributes.Remove(new KeyValuePair("one", "0"));
+
+ // Assert
+ Assert.False(result);
+ Assert.Equal(3, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("one", "1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_TryGetValue_Success()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ string value;
+
+ // Act
+ var result = attributes.TryGetValue("two", out value);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal("2", value);
+ }
+
+ [Fact]
+ public void AttributeDictionary_TryGetValue_Failure()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ string value;
+
+ // Act
+ var result = attributes.TryGetValue("nada", out value);
+
+
+ // Assert
+ Assert.False(result);
+ Assert.Null(value);
+ }
+
+ [Fact]
+ public void AttributeDictionary_Values()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var values = attributes.Values;
+
+ // Assert
+ Assert.Equal(3, values.Count);
+ Assert.Collection(
+ values,
+ key => Assert.Equal("1", key),
+ key => Assert.Equal("2", key),
+ key => Assert.Equal("0", key));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Indexer_Success()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ var value = attributes["two"];
+
+ // Assert
+ Assert.Equal("2", value);
+ }
+
+ [Fact]
+ public void AttributeDictionary_Indexer_Fails()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act & Assert
+ Assert.Throws(() => attributes["nada"]);
+ }
+
+ [Fact]
+ public void AttributeDictionary_Indexer_SetValue()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ attributes["one"] = "1!";
+
+ // Assert
+ Assert.Equal(3, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("one", "1!"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_Indexer_Insert()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ attributes["exciting!"] = "1!";
+
+ // Assert
+ Assert.Equal(4, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("exciting!", "1!"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("one", "1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+
+ [Fact]
+ public void AttributeDictionary_CaseInsensitive()
+ {
+ // Arrange
+ var attributes = new AttributeDictionary();
+
+ attributes.Add(new KeyValuePair("zero", "0"));
+ attributes.Add(new KeyValuePair("one", "1"));
+ attributes.Add(new KeyValuePair("two", "2"));
+
+ // Act
+ attributes["oNe"] = "1!";
+
+ // Assert
+ Assert.Equal(3, attributes.Count);
+ Assert.Collection(
+ attributes,
+ kvp => Assert.Equal(new KeyValuePair("oNe", "1!"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("two", "2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair("zero", "0"), kvp));
+ }
+ }
+}