See More

using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Microsoft.QueryStringDotNET { internal static class ListExtensions { internal static bool RemoveFirstWhere(this IList list, Func shouldRemovePredicate) { for (int i = 0; i < list.Count; i++) { if (shouldRemovePredicate.Invoke(list[i])) { list.RemoveAt(i); return true; } } return false; } } ///

/// A single query string parameter (name and value pair). /// public sealed class QueryStringParameter { private string _name; /// /// The name of the parameter. Cannot be null. /// public string Name { get { return _name; } set { if (value == null) throw new ArgumentNullException("Name"); _name = value; } } /// /// The value of the parameter (or null if there's no value). /// public string Value { get; set; } /// /// Initializes a new query string parameter with the specified name and optional value. /// /// The name of the parameter. Cannot be null. /// The optional value of the parameter. internal QueryStringParameter(string name, string value = null) { if (name == null) throw new ArgumentNullException("name"); Name = name; Value = value; } } /// /// Specifies the separator to be used between query string parameters. /// public enum QueryStringSeparator { /// /// The default separator for query string parameters. Generated query string is like "a=1&b=5". /// Ampersand, /// /// An alternative separator for query string parameters. Generated query string is like "a=1;b=5". /// Semicolon } /// /// A portable string serializer/deserializer for .NET. /// public class QueryString : IEnumerable, IEquatable { private Dictionary> _dictionary = new Dictionary>(); /// /// Constructs a new empty query string. /// public QueryString() { // Nothing } /// /// Gets the first value of the first parameter with the matching name. Throws if a parameter with a matching name could not be found. O(n) where n = Count of the current object. /// /// The parameter name to find. /// public string this[string name] { get { if (name == null) throw new ArgumentNullException("name"); string value; if (TryGetValue(name, out value)) return value; throw new KeyNotFoundException($"A parameter with name '{name}' could not be found."); } } /// /// Gets the first value of the first parameter with the matching name. If no parameter with a matching name exists, returns false. /// /// The parameter name to find. /// The parameter's value will be written here once found. /// public bool TryGetValue(string name, out string value) { if (name == null) throw new ArgumentNullException("name"); List values; if (_dictionary.TryGetValue(name, out values)) { value = values.First(); return true; } value = null; return false; } /// /// Gets the values of the parameter with the matching name. If no parameter with a matching name exists, sets to null and returns false. /// /// The parameter name to find. /// The parameter's values will be written here once found. /// public bool TryGetValues(string name, out string[] values) { if (name == null) throw new ArgumentNullException("name"); List storedValues; if (_dictionary.TryGetValue(name, out storedValues)) { values = storedValues.ToArray(); return true; } values = null; return false; } /// /// Returns the count of parameters in the current query string. /// public int Count() { return _dictionary.Select(i => i.Value.Count).Sum(); } /// /// Adds a query string parameter to the query string. /// /// The name of the parameter. /// The optional value of the parameter. public void Add(string name, string value = null) { if (name == null) throw new ArgumentNullException("name"); List values; if (!_dictionary.TryGetValue(name, out values)) { values = new List(); _dictionary[name] = values; } values.Add(value); } /// /// Sets a query string parameter. If there are existing parameters with the same name, they are removed. /// /// The name of the parameter. /// The optional value of the parameter. public void Set(string name, string value = null) { if (name == null) throw new ArgumentNullException("name"); _dictionary[name] = new List() { value }; } /// /// Determines if the query string contains at least one parameter with the specified name. /// /// The parameter name to look for. /// True if the query string contains at least one parameter with the specified name, else false. public bool Contains(string name) { if (name == null) throw new ArgumentNullException("name"); return _dictionary.ContainsKey(name); } /// /// Determines if the query string contains a parameter with the specified name and value. /// /// The parameter name to look for. /// The value to look for when the name has been matched. /// True if the query string contains a parameter with the specified name and value, else false. public bool Contains(string name, string value) { if (name == null) throw new ArgumentNullException("name"); List values; return _dictionary.TryGetValue(name, out values) && values.Contains(value); } /// /// Removes the first parameter with the specified name. /// /// The name of parameter to remove. /// True if the parameters were removed, else false. public bool Remove(string name) { List values; if (_dictionary.TryGetValue(name, out values)) { if (values.Count == 1) _dictionary.Remove(name); else values.RemoveAt(0); return true; } return false; } /// /// Removes all parameters with the specified name. /// /// The name of parameters to remove. /// True if the parameters were removed, else false. public bool RemoveAll(string name) { return _dictionary.Remove(name); } /// /// Removes the first parameter with the specified name and value. /// /// The name of the parameter to remove. /// /// True if parameter was removed, else false. public bool Remove(string name, string value) { List values; if (_dictionary.TryGetValue(name, out values)) { if (values.RemoveFirstWhere(i => object.Equals(i, value))) { // If removed last value, remove the key if (values.Count == 0) _dictionary.Remove(name); return true; } } return false; } /// /// Removes all parameters with the specified name and value. /// /// The name of parameters to remove. /// The value to match when deciding whether to remove. /// The count of parameters removed. public int RemoveAll(string name, string value) { List values; if (_dictionary.TryGetValue(name, out values)) { int countRemoved = values.RemoveAll(i => object.Equals(i, value)); // If removed last value, remove the key if (values.Count == 0) _dictionary.Remove(name); return countRemoved; } return 0; } private static string UrlEncode(string str) { return Uri.EscapeDataString(str) // It incorrectly encodes spaces as %20, should use + .Replace("%20", "+"); } private static string UrlDecode(string str) { // Doesn't handle decoding the +, so we manually do that return Uri.UnescapeDataString(str.Replace('+', ' ')); } /// /// Parses a query string into a object. Keys/values are automatically URL decoded. /// /// The query string to deserialize. This should NOT have a leading ? character. Valid input would be something like "a=1&b=5". URL decoding of keys/values is automatically performed. Also supports query strings that are serialized using ; instead of &, like "a=1;b=5" /// public static QueryString Parse(string queryString) { if (string.IsNullOrWhiteSpace(queryString)) return new QueryString(); string[] pairs = queryString.Split('&', ';'); QueryString answer = new QueryString(); foreach (string pair in pairs) { string name; string value; int indexOfEquals = pair.IndexOf('='); if (indexOfEquals == -1) { name = UrlDecode(pair); value = null; } else { name = UrlDecode(pair.Substring(0, indexOfEquals)); value = UrlDecode(pair.Substring(indexOfEquals + 1)); } answer.Add(name, value); } return answer; } /// /// Serializes the key-value pairs into a query string, using the default & separator. Produces something like "a=1&b=5". URL encoding of keys/values is automatically performed. Null values are not written (only their key is written). /// /// public override string ToString() { return ToString(QueryStringSeparator.Ampersand); } private static string GetSeparatorString(QueryStringSeparator separator) { switch (separator) { case QueryStringSeparator.Ampersand: return "&"; case QueryStringSeparator.Semicolon: return ";"; default: throw new NotImplementedException(separator.ToString()); } } /// /// Serializes the key-value pairs into a query string, using the specified separator. URL encoding of keys/values is automatically performed. Null values are not written (only their key is written). If there are no parameters, an empty string will be returned. /// /// /// public string ToString(QueryStringSeparator separator) { return string.Join(GetSeparatorString(separator), this.Select( pair => // Key UrlEncode(pair.Name) + // Write value if not null ((pair.Value == null) ? "" : ("=" + UrlEncode(pair.Value))) )); } /// /// Gets an enumerator to enumerate the query string parameters. Note that order of the parameters is NOT preserved. /// /// public IEnumerator GetEnumerator() { foreach (var pair in _dictionary) { foreach (var value in pair.Value) { yield return new QueryStringParameter(pair.Key, value); } } } /// /// Gets an enumerator to enumerate the query string parameters. /// /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Determines whether the current query string is equivalent to the provided query string. /// /// The query string to compare to. /// Returns true if the query string has the exact same parameters as the current query string (order is irrelevant). public bool Equals(QueryString other) { return this.Equals(other, default(StringComparison), default(StringComparison)); } public bool Equals(QueryString other, StringComparison nameComparisonType, StringComparison valueComparisonType) { // If they have a different count of keys if (_dictionary.Count != other._dictionary.Count) return false; // Go through each key from current object foreach (var param in _dictionary) { // Get values for this key List thisValues = param.Value; List otherValues; // If the other didn't have param name if (!other._dictionary.TryGetValue(param.Key, out otherValues)) return false; // If the count of values is different if (thisValues.Count != otherValues.Count) return false; // Create copy of the other values list otherValues = new List(otherValues); // And then remove matching values foreach (string thisVal in thisValues) { // If we couldn't find matching to remove if (!otherValues.RemoveFirstWhere(i => ValueEquals(thisVal, i, valueComparisonType))) return false; } } // Otherwise they're equal, all matched! return true; } private bool ValueEquals(string value1, string value2, StringComparison comparisonType) { // If both are null, true if (value1 == null && value2 == null) return true; // If only one is null, and therefore the other initialized, then false if (value1 == null || value2 == null) return false; // Otherwise both are initialized, compare with equals return value1.Equals(value2, comparisonType); } } }