Monty’s Gush

Query string extension methods for System.UriBuilder

Posted on: January 27, 2009

kick it on DotNetKicks.com

In contrast with its more famous sibling System.StringBuilder, the System.UriBuilder class goes mostly unnoticed and unloved.

This is unfortunate, since although the URI is just a string, it’s restricted by detailed specification to a limited subset of all possible strings. This is why we have framework and library code to help us out. Think how many bugs and security errors have been caused by concatenating general-purpose strings!

The .NET UriBuilder class provides additional safety but inexplicably lacks support for one of the most common reasons for concatenating strings to form a URL – manipulating query strings.

In particular, you often find that you would like to set a query key/value pair on an existing URL. You want to add it if it doesn’t already exist (but change it if it does) and leave the rest of the URL intact.

A convenient API can be grafted with a couple of extension methods, GetQueryParams and SetQueryParam.

var uri = new UriBuilder("http://blah.com");

Assert.True( uri.GetQueryParams().Count() == 0 );
uri.SetQueryParam("sortBy", "price");

Assert.True( uri.GetQueryParams().Count() == 1 );
Assert.True( uri.Query.EndsWith("?sortBy=price") );
uri.SetQueryParam("page", "3");

Assert.True( uri.GetQueryParams().Count() == 2 );
Assert.True( uri.Query.Contains("page=3") );
uri.SetQueryParam("page", "4");

Assert.True( uri.GetQueryParams().Count() == 2 );
Assert.True( uri.Query.Contains("page=4") );

Here’s the full source.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Collections.Specialized;

// ...

    public static class UriBuilderExtensions
    {
        /// <summary>
        /// Sets the specified query parameter key-value pair of the URI.
        /// If the key already exists, the value is overwritten.
        /// </summary>
        public static UriBuilder SetQueryParam(this UriBuilder uri, string key, string value)
        {
            var collection = uri.ParseQuery();

            // add (or replace existing) key-value pair
            collection.Set(key, value);

            string query = collection
                .AsKeyValuePairs()
                .ToConcatenatedString(pair =>
                    pair.Key == null
                    ? pair.Value
                    : pair.Key + "=" + pair.Value, "&");

            uri.Query = query;

            return uri;
        }

        /// <summary>
        /// Gets the query string key-value pairs of the URI.
        /// Note that the one of the keys may be null ("?123") and
        /// that one of the keys may be an empty string ("?=123").
        /// </summary>
        public static IEnumerable<KeyValuePair<string, string>> GetQueryParams(
            this UriBuilder uri)
        {
            return uri.ParseQuery().AsKeyValuePairs();
        }

        /// <summary>
        /// Converts the legacy NameValueCollection into a strongly-typed KeyValuePair sequence.
        /// </summary>
        static IEnumerable<KeyValuePair<string, string>> AsKeyValuePairs(this NameValueCollection collection)
        {
            foreach (string key in collection.AllKeys)
            {
                yield return new KeyValuePair<string, string>(key, collection.Get(key));
            }
        }

        /// <summary>
        /// Parses the query string of the URI into a NameValueCollection.
        /// </summary>
        static NameValueCollection ParseQuery(this UriBuilder uri)
        {
            return HttpUtility.ParseQueryString(uri.Query);
        }
    }

The general-purpose folding function ToConcatenatedString is used in the implementation. Here it is:


	public static class EnumerableExtensions
	{
		/// <summary>
		/// Creates a string from the sequence by concatenating the result
		/// of the specified string selector function for each element.
		/// </summary>
		public static string ToConcatenatedString<T>(this IEnumerable<T> source,
			Func<T, string> stringSelector)
		{
			return source.ToConcatenatedString(stringSelector, String.Empty);
		}

		/// <summary>
		/// Creates a string from the sequence by concatenating the result
		/// of the specified string selector function for each element.
		/// </summary>
		///<param name="separator">The string which separates each concatenated item.</param>
		public static string ToConcatenatedString<T>(this IEnumerable<T> source,
			Func<T, string> stringSelector,
			string separator)
		{
			var b = new StringBuilder();
			bool needsSeparator = false; // don't use for first item

			foreach (var item in source)
			{
				if (needsSeparator)
					b.Append(separator);

				b.Append(stringSelector(item));
				needsSeparator = true;
			}

			return b.ToString();
		}
	}

10 Responses to "Query string extension methods for System.UriBuilder"

nice! may I recommend:

public static UriBuilder RemoveQueryParam(this UriBuilder uri, string key)
{
var collection = uri.ParseQuery();

// add (or replace existing) key-value pair
collection.Remove(key);

string query = collection
.AsKeyValuePairs()
.ToConcatenatedString(pair =>
pair.Key == null
? pair.Value
: pair.Key + “=” + pair.Value, “&”);

uri.Query = query;

return uri;
}

Good idea! I should have included Remove.

Hi misters,

any code sample for

public static string ToConcatenatedString(this IEnumerable source,
Func stringSelector)

Greetings

Thanks. Saved a lot of time in rewriting the same.

what about a getter for one specific parameter?

///
/// Gets the specified query parameter key’s value of the URI.
/// If the key dont exists, null is returned.
///
public static string GetQueryParam(this UriBuilder uri, string key)
{
return uri.ParseQuery().Get(key);
}

Great! Thanks for posting this.

Very cool indeed. Thanks. Just curious — what license or copyright are you asserting on this code, if I wanted to use it elsewhere? No plans right now, but it’s sure nice stuff. :)

Thanks , I’ve just been looking for info approximately this subject for ages and yours is the best I have came upon till now.
However, what in regards to the conclusion? Are you positive
about the source?

It’s genuinely ver difficult in this full of
activity life to listen news on Television, so I simply use the web
for that reason, and get the newest information.

Hello to all, it’s genuinely a nice for me to pay a
quick visit this site, it consists of important Information.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: