Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
An extension method for URI to set its query string using anonymous objects.
namespace Dawn.Web
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Web.Routing;
/// <summary>
/// Contains utility methods involving
/// uniform resource identifiers (URIs).
/// </summary>
/// <content>
/// Provides an extension method to set a URI's
/// query string using an anonymous object.
/// </content>
public static partial class UriUtils
#region Methods
/// <summary>
/// Returns a new URI with a query set to the specified values.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="values">The query values.</param>
/// <returns>A new URI with the specified query values.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="uri" /> is <c>null</c>.
/// </exception>
/// <remarks>
/// This method removes the existing query of the URI and
/// adds the specified values ordered by key - performing
/// a stable sort, that is, if two keys are identical,
/// the original order of values are preserved.
/// </remarks>
public static Uri WithQuery(this Uri uri, object values)
if (uri == null)
throw new ArgumentNullException(nameof(uri));
if (values != null)
var query = string.Join(
"&", from p in ParseQueryValues(values)
where !string.IsNullOrWhiteSpace(p.Key)
let k = HttpUtility.UrlEncode(p.Key.Trim())
let v = HttpUtility.UrlEncode(p.Value)
orderby k
select string.IsNullOrEmpty(v) ? k : $"{k}={v}");
if (query.Length != 0 || uri.Query.Length != 0)
uri = new UriBuilder(uri) { Query = query }.Uri;
return uri;
/// <summary>Parses a query object as a collection of key/value pairs.</summary>
/// <param name="values">The query values.</param>
/// <returns><paramref name="values" /> as key/value pairs.</returns>
private static IEnumerable<KeyValuePair<string, string>> ParseQueryValues(object values)
// Check if a name/value collection.
var nvc = values as NameValueCollection;
if (nvc != null)
return from key in nvc.AllKeys
from val in nvc.GetValues(key)
select new KeyValuePair<string, string>(key, val);
// Check if a string/string dictionary.
var ssd = values as IEnumerable<KeyValuePair<string, string>>;
if (ssd != null)
return ssd;
// Check if a string/object dictionary.
var sod = values as IEnumerable<KeyValuePair<string, object>>;
if (sod == null)
// Check if a non-generic dictionary.
var ngd = values as IDictionary;
if (ngd != null)
sod = ngd.Cast<dynamic>().ToDictionary<dynamic, string, object>(
p => p.Key.ToString(),
p => p.Value as object);
// Convert object properties to dictionary.
if (sod == null)
sod = new RouteValueDictionary(values);
// Normalize and return the values.
return from pair in sod
from val in pair.Value as IEnumerable<string>
?? new[] { pair.Value?.ToString() }
select new KeyValuePair<string, string>(pair.Key, val);
#endregion Methods
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment