// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; namespace System.Web.WebPages { /// /// This is a wrapper around Dictionary so that using PageData[key] returns null /// if the key is not found, instead of throwing an exception. /// // This is a generic type because C# does not allow implementing an interface // involving dynamic types (implementing IDictionary causes // a compile error // http://blogs.msdn.com/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx). internal class PageDataDictionary : IDictionary { private IDictionary _data = new Dictionary(new PageDataComparer()); private IDictionary _stringDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); private IList _indexedValues = new List(); internal IDictionary Data { get { return _data; } } internal IDictionary StringDictionary { get { return _stringDictionary; } } internal IList IndexedValues { get { return _indexedValues; } } public ICollection Keys { get { List keys = new List(); keys.AddRange(_stringDictionary.Keys); for (int i = 0; i < _indexedValues.Count; i++) { keys.Add(i); } foreach (var key in _data.Keys) { if (!ContainsIndex(key) && !ContainsStringKey(key)) { keys.Add(key); } } return keys; } } public ICollection Values { get { List values = new List(); foreach (var key in Keys) { values.Add(this[key]); } return values; } } internal ICollection> Items { get { var items = new List>(); foreach (var key in Keys) { var value = this[key]; var kvp = new KeyValuePair(key, value); items.Add(kvp); } return items; } } public int Count { get { return Items.Count; } } public bool IsReadOnly { get { return false; } } public TValue this[object key] { get { TValue v = default(TValue); TryGetValue(key, out v); return v; } // Note that this affects and updates the string dictionary and indexed list // only for existing keys found in these collections. // Otherwise, the key/value goes into the _data dictionary. set { if (ContainsStringKey(key)) { _stringDictionary[(string)key] = value; } else if (ContainsIndex(key)) { _indexedValues[(int)key] = value; } else { _data[key] = value; } } } public void Add(object key, TValue value) { _data.Add(key, value); } internal bool ContainsIndex(object o) { if (o is int) { return ContainsIndex((int)o); } else { return false; } } internal bool ContainsIndex(int index) { return _indexedValues.Count > index && index >= 0; } internal bool ContainsStringKey(object o) { string s = o as string; if (s != null) { return ContainsStringKey(s); } else { return false; } } internal bool ContainsStringKey(string key) { return _stringDictionary.ContainsKey(key); } public bool ContainsKey(object key) { if (ContainsIndex(key)) { return true; } else if (ContainsStringKey(key)) { return true; } else if (_data.ContainsKey(key)) { return true; } else { return false; } } // Note that if the key exists in more than one place, then only // the string dictionary and the indexed list will be updated. public bool Remove(object key) { if (ContainsStringKey(key)) { return _stringDictionary.Remove((string)key); } else if (ContainsIndex(key)) { return _indexedValues.Remove(_indexedValues[(int)key]); } else { return _data.Remove(key); } } public bool TryGetValue(object key, out TValue value) { if (ContainsStringKey(key)) { return _stringDictionary.TryGetValue((string)key, out value); } else if (ContainsIndex(key)) { value = _indexedValues[(int)key]; return true; } else { return _data.TryGetValue(key, out value); } } public void Add(KeyValuePair item) { this[item.Key] = item.Value; } public void Clear() { _stringDictionary.Clear(); _indexedValues.Clear(); _data.Clear(); } public bool Contains(KeyValuePair item) { return ContainsKey(item.Key) && Values.Contains(item.Value); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { Items.CopyTo(array, arrayIndex); } public bool Remove(KeyValuePair item) { if (Contains(item)) { return Remove((object)item.Key); } return false; } public IEnumerator> GetEnumerator() { return Items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return Items.GetEnumerator(); } // Creates a new PageData dictionary using only the original items from the pageData (excluding the string dictionary and indexed list), // and adding the parameters. internal static IDictionary CreatePageDataFromParameters(IDictionary previousPageData, params object[] data) { var oldPageData = previousPageData as PageDataDictionary; // Add the original items var pageData = new PageDataDictionary(); foreach (var kvp in oldPageData.Data) { pageData.Data.Add(kvp); } if (data != null && data.Length > 0) { // Add items to the indexed list for (int i = 0; i < data.Length; i++) { pageData.IndexedValues.Add(data[i]); } // Check for anonymous types, and add to the string dictionary object first = data[0]; Type type = first.GetType(); if (TypeHelper.IsAnonymousType(type)) { // Anonymous type TypeHelper.AddAnonymousObjectToDictionary(pageData.StringDictionary, first); } // Check if the first element is of type IDictionary if (typeof(IDictionary).IsAssignableFrom(type)) { // Dictionary var stringDictionary = first as IDictionary; foreach (var kvp in stringDictionary) { pageData.StringDictionary.Add(kvp); } } } return pageData; } // This comparer treats only strings as case-insensitive, but still handles objects // of other types as well. private sealed class PageDataComparer : IEqualityComparer { bool IEqualityComparer.Equals(object x, object y) { var s1 = x as string; var s2 = y as string; if (s1 != null && s2 != null) { return String.Equals(s1, s2, StringComparison.OrdinalIgnoreCase); } return Equals(x, y); } int IEqualityComparer.GetHashCode(object obj) { var s = obj as string; if (s != null) { return s.ToUpperInvariant().GetHashCode(); } return obj.GetHashCode(); } } } }