// 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. #if NETFX_CORE using System.Collections; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using System.Text; using System.Web.Http; #else using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.Contracts; using System.Linq; using System.Net.Http.Internal; using System.Runtime.Serialization; using System.Text; using System.Web.Http; #endif #if NETFX_CORE namespace System.Net.Http.Formatting #else namespace System.Net.Http.Formatting.Internal #endif { /// /// NameValueCollection to represent form data and to generate form data output. /// #if NETFX_CORE public class HttpValueCollection : IEnumerable> #else [Serializable] internal class HttpValueCollection : NameValueCollection #endif { #if NETFX_CORE internal readonly HashSet Names = new HashSet(StringComparer.OrdinalIgnoreCase); internal readonly List> List = new List>(); /// /// Creates a new instance /// public HttpValueCollection() { } #else protected HttpValueCollection(SerializationInfo info, StreamingContext context) : base(info, context) { } private HttpValueCollection() : base(StringComparer.OrdinalIgnoreCase) // case-insensitive keys { } #endif // Use a builder function instead of a ctor to avoid virtual calls from the ctor. // The above condition is only important in the Full .NET fx implementation. internal static HttpValueCollection Create() { return new HttpValueCollection(); } internal static HttpValueCollection Create(IEnumerable> pairs) { Contract.Assert(pairs != null); var hvc = new HttpValueCollection(); // Ordering example: // k=A&j=B&k=C --> k:[A,C];j=[B]. foreach (KeyValuePair kvp in pairs) { hvc.Add(kvp.Key, kvp.Value); } #if !NETFX_CORE hvc.IsReadOnly = false; #endif return hvc; } /// /// Adds a name-value pair to the collection. /// /// The name to be added as a case insensitive string. /// The value to be added. public #if !NETFX_CORE override #endif void Add(string name, string value) { ThrowIfMaxHttpCollectionKeysExceeded(Count); name = name ?? String.Empty; value = value ?? String.Empty; #if NETFX_CORE Names.Add(name); List.Add(new KeyValuePair(name, value)); #else base.Add(name, value); #endif } /// /// Converts the content of this instance to its equivalent string representation. /// /// The string representation of the value of this instance, multiple values with a single key are comma separated. public override string ToString() { return ToString(true); } private static void ThrowIfMaxHttpCollectionKeysExceeded(int count) { if (count >= MediaTypeFormatter.MaxHttpCollectionKeys) { throw Error.InvalidOperation(System.Net.Http.Properties.Resources.MaxHttpCollectionKeyLimitReached, MediaTypeFormatter.MaxHttpCollectionKeys, typeof(MediaTypeFormatter)); } } private string ToString(bool urlEncode) { if (Count == 0) { return String.Empty; } StringBuilder builder = new StringBuilder(); bool first = true; #if NETFX_CORE foreach (string name in Names) #else foreach (string name in this) #endif { string[] values = GetValues(name); if (values == null || values.Length == 0) { first = AppendNameValuePair(builder, first, urlEncode, name, String.Empty); } else { foreach (string value in values) { first = AppendNameValuePair(builder, first, urlEncode, name, value); } } } return builder.ToString(); } private static bool AppendNameValuePair(StringBuilder builder, bool first, bool urlEncode, string name, string value) { string effectiveName = name ?? String.Empty; string encodedName = urlEncode ? UriQueryUtility.UrlEncode(effectiveName) : effectiveName; string effectiveValue = value ?? String.Empty; string encodedValue = urlEncode ? UriQueryUtility.UrlEncode(effectiveValue) : effectiveValue; if (first) { first = false; } else { builder.Append("&"); } builder.Append(encodedName); if (!String.IsNullOrEmpty(encodedValue)) { builder.Append("="); builder.Append(encodedValue); } return first; } #if NETFX_CORE /// /// Gets the values associated with the specified name /// combined into one comma-separated list. /// /// The name of the entry that contains the values to get. The name can be null. /// A that contains a comma-separated list of url encoded values associated /// with the specified name if found; otherwise, null. The values are Url encoded. public string this[string name] { get { return Get(name); } } /// /// Gets the number of names in the collection. /// public int Count { get { return Names.Count; } } /// /// Gets the values associated with the specified name /// combined into one comma-separated list. /// /// The name of the entry that contains the values to get. The name can be null. /// /// A that contains a comma-separated list of url encoded values associated /// with the specified name if found; otherwise, null. The values are Url encoded. /// public string Get(string name) { name = name ?? String.Empty; if (!Names.Contains(name)) { return null; } List values = GetValuesInternal(name); Contract.Assert(values != null && values.Count > 0); return String.Join(",", values); } /// /// Gets the values associated with the specified name. /// /// The /// A that contains url encoded values associated with the name, or null if the name does not exist. public string[] GetValues(string name) { name = name ?? String.Empty; if (!Names.Contains(name)) { return null; } return GetValuesInternal(name).ToArray(); } // call this when only when there are values available. private List GetValuesInternal(string name) { List values = new List(); for (int i = 0; i < List.Count; i++) { KeyValuePair kvp = List[i]; if (String.Equals(kvp.Key, name, StringComparison.OrdinalIgnoreCase)) { values.Add(kvp.Value); } } return values; } /// public IEnumerator> GetEnumerator() { return List.GetEnumerator(); } /// IEnumerator IEnumerable.GetEnumerator() { return List.GetEnumerator(); } #endif } }