The provided answers have issues with relative Url's, such as "/some/path/" This is a limitation of the Uri and UriBuilder class, which is rather hard to understand, since I don't see any reason why relative urls would be problematic when it comes to query manipulation.
Here is a workaround that works for both absolute and relative paths, written and tested in .NET 4:
(small note: this should also work in .NET 4.5, you will only have to change propInfo.GetValue(values, null)
to propInfo.GetValue(values)
)
public static class UriExtensions{
/// <summary>
/// Adds query string value to an existing url, both absolute and relative URI's are supported.
/// </summary>
/// <example>
/// <code>
/// // returns "www.domain.com/test?param1=val1&param2=val2&param3=val3"
/// new Uri("www.domain.com/test?param1=val1").ExtendQuery(new Dictionary<string, string> { { "param2", "val2" }, { "param3", "val3" } });
///
/// // returns "/test?param1=val1&param2=val2&param3=val3"
/// new Uri("/test?param1=val1").ExtendQuery(new Dictionary<string, string> { { "param2", "val2" }, { "param3", "val3" } });
/// </code>
/// </example>
/// <param name="uri"></param>
/// <param name="values"></param>
/// <returns></returns>
public static Uri ExtendQuery(this Uri uri, IDictionary<string, string> values) {
var baseUrl = uri.ToString();
var queryString = string.Empty;
if (baseUrl.Contains("?")) {
var urlSplit = baseUrl.Split('?');
baseUrl = urlSplit[0];
queryString = urlSplit.Length > 1 ? urlSplit[1] : string.Empty;
}
NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString);
foreach (var kvp in values ?? new Dictionary<string, string>()) {
queryCollection[kvp.Key] = kvp.Value;
}
var uriKind = uri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative;
return queryCollection.Count == 0
? new Uri(baseUrl, uriKind)
: new Uri(string.Format("{0}?{1}", baseUrl, queryCollection), uriKind);
}
/// <summary>
/// Adds query string value to an existing url, both absolute and relative URI's are supported.
/// </summary>
/// <example>
/// <code>
/// // returns "www.domain.com/test?param1=val1&param2=val2&param3=val3"
/// new Uri("www.domain.com/test?param1=val1").ExtendQuery(new { param2 = "val2", param3 = "val3" });
///
/// // returns "/test?param1=val1&param2=val2&param3=val3"
/// new Uri("/test?param1=val1").ExtendQuery(new { param2 = "val2", param3 = "val3" });
/// </code>
/// </example>
/// <param name="uri"></param>
/// <param name="values"></param>
/// <returns></returns>
public static Uri ExtendQuery(this Uri uri, object values) {
return ExtendQuery(uri, values.GetType().GetProperties().ToDictionary
(
propInfo => propInfo.Name,
propInfo => { var value = propInfo.GetValue(values, null); return value != null ? value.ToString() : null; }
));
}
}
And here is a suite of unit tests to test the behavior:
[TestFixture]
public class UriExtensionsTests {
[Test]
public void Add_to_query_string_dictionary_when_url_contains_no_query_string_and_values_is_empty_should_return_url_without_changing_it() {
Uri url = new Uri("http://www.domain.com/test");
var values = new Dictionary<string, string>();
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test")));
}
[Test]
public void Add_to_query_string_dictionary_when_url_contains_hash_and_query_string_values_are_empty_should_return_url_without_changing_it() {
Uri url = new Uri("http://www.domain.com/test#div");
var values = new Dictionary<string, string>();
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test#div")));
}
[Test]
public void Add_to_query_string_dictionary_when_url_contains_no_query_string_should_add_values() {
Uri url = new Uri("http://www.domain.com/test");
var values = new Dictionary<string, string> { { "param1", "val1" }, { "param2", "val2" } };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test?param1=val1¶m2=val2")));
}
[Test]
public void Add_to_query_string_dictionary_when_url_contains_hash_and_no_query_string_should_add_values() {
Uri url = new Uri("http://www.domain.com/test#div");
var values = new Dictionary<string, string> { { "param1", "val1" }, { "param2", "val2" } };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test#div?param1=val1¶m2=val2")));
}
[Test]
public void Add_to_query_string_dictionary_when_url_contains_query_string_should_add_values_and_keep_original_query_string() {
Uri url = new Uri("http://www.domain.com/test?param1=val1");
var values = new Dictionary<string, string> { { "param2", "val2" } };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test?param1=val1¶m2=val2")));
}
[Test]
public void Add_to_query_string_dictionary_when_url_is_relative_contains_no_query_string_should_add_values() {
Uri url = new Uri("/test", UriKind.Relative);
var values = new Dictionary<string, string> { { "param1", "val1" }, { "param2", "val2" } };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1¶m2=val2", UriKind.Relative)));
}
[Test]
public void Add_to_query_string_dictionary_when_url_is_relative_and_contains_query_string_should_add_values_and_keep_original_query_string() {
Uri url = new Uri("/test?param1=val1", UriKind.Relative);
var values = new Dictionary<string, string> { { "param2", "val2" } };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1¶m2=val2", UriKind.Relative)));
}
[Test]
public void Add_to_query_string_dictionary_when_url_is_relative_and_contains_query_string_with_existing_value_should_add_new_values_and_update_existing_ones() {
Uri url = new Uri("/test?param1=val1", UriKind.Relative);
var values = new Dictionary<string, string> { { "param1", "new-value" }, { "param2", "val2" } };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("/test?param1=new-value¶m2=val2", UriKind.Relative)));
}
[Test]
public void Add_to_query_string_object_when_url_contains_no_query_string_should_add_values() {
Uri url = new Uri("http://www.domain.com/test");
var values = new { param1 = "val1", param2 = "val2" };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test?param1=val1¶m2=val2")));
}
[Test]
public void Add_to_query_string_object_when_url_contains_query_string_should_add_values_and_keep_original_query_string() {
Uri url = new Uri("http://www.domain.com/test?param1=val1");
var values = new { param2 = "val2" };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("http://www.domain.com/test?param1=val1¶m2=val2")));
}
[Test]
public void Add_to_query_string_object_when_url_is_relative_contains_no_query_string_should_add_values() {
Uri url = new Uri("/test", UriKind.Relative);
var values = new { param1 = "val1", param2 = "val2" };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1¶m2=val2", UriKind.Relative)));
}
[Test]
public void Add_to_query_string_object_when_url_is_relative_and_contains_query_string_should_add_values_and_keep_original_query_string() {
Uri url = new Uri("/test?param1=val1", UriKind.Relative);
var values = new { param2 = "val2" };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1¶m2=val2", UriKind.Relative)));
}
[Test]
public void Add_to_query_string_object_when_url_is_relative_and_contains_query_string_with_existing_value_should_add_new_values_and_update_existing_ones() {
Uri url = new Uri("/test?param1=val1", UriKind.Relative);
var values = new { param1 = "new-value", param2 = "val2" };
var result = url.ExtendQuery(values);
Assert.That(result, Is.EqualTo(new Uri("/test?param1=new-value¶m2=val2", UriKind.Relative)));
}
}