220

I have set of URLs similar to the ones below in a list

  • http://somesite.example/backup/lol.php?id=1&server=4&location=us
  • http://somesite.example/news.php?article=1&lang=en

I have managed to get the query strings using the following code:

myurl = longurl.Split('?');
NameValueCollection qs = HttpUtility.ParseQueryString(myurl [1]);

foreach (string lol in qs)
{
    // results will return
}

But it only returns the parameters like id, server, location and so on based on the URL provided.

What I need is to add / append values to the existing query strings.

For example with the URL:

http://somesite.example/backup/index.php?action=login&attempts=1

I need to alter the values of the query string parameters:

action=login1

attempts=11

As you can see, I have appended "1" for each value. I need to get a set of URLs from a string with different query strings in them and add a value to each parameter at the end & again add them to a list.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
DriverBoy
  • 3,047
  • 3
  • 19
  • 21

8 Answers8

439

You could use the HttpUtility.ParseQueryString method and an UriBuilder which provides a nice way to work with query string parameters without worrying about things like parsing, URL encoding, ...:

string longurl = "http://somesite.example/news.php?article=1&lang=en";
var uriBuilder = new UriBuilder(longurl);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["action"] = "login1";
query["attempts"] = "11";
uriBuilder.Query = query.ToString();
longurl = uriBuilder.ToString();
// "http://somesite.example:80/news.php?article=1&lang=en&action=login1&attempts=11"
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • I Think you have misunderstood the question, what i exactly need do is to append values to existing paramers. in this case **longurl** should return **"http://somesite.com/news.php?article=11&lang=en1";** the parameters are dynamic as well. they are always not **action** or **attempts** – DriverBoy Jan 25 '13 at 08:47
  • 6
    As you can see from my example you could use variable names for the parameters. And that's exactly what it does: it appends 2 parameters to the existing url that I have hardcoded here but they could perfectly fine be dynamic. – Darin Dimitrov Jan 25 '13 at 08:50
  • I just got the query strings with a loop since **query** is a list. Is their a better way ? and how can i add the values to strings .. im a bit confused. Thanks – DriverBoy Jan 25 '13 at 09:06
  • Yeah you could get all query string parameters in a loop using my approach: `foreach (string key in query.AllKeys) { var value = query[key]; }`. – Darin Dimitrov Jan 25 '13 at 09:09
  • I've already covered the part of adding query string parameters in my answer: `query["myCustomKey"] = "my custom value";` – Darin Dimitrov Jan 25 '13 at 09:15
  • Yeah, I used a forloop with **query.count** :-) .. Thanks You Darin – DriverBoy Jan 25 '13 at 09:17
  • 1
    Shouldn't we use `HttpUtility.UrlEncode()` when assigning parameter value? – UserControl Mar 25 '14 at 11:37
  • 3
    @UserControl, no, the `HttpUtility.ParseQueryString` method returns a special NameValueCollection implementation which already handles this behind the scenes when you set a value. – Darin Dimitrov Mar 25 '14 at 15:33
  • 8
    Bummer that this has a dependency on System.Web :/ – Pure.Krome Jul 31 '14 at 13:28
  • 4
    it is worth noting that this approach can cause issues with internationalization as special characters will be converted to their unicode equivalents in the query.ToString() method. – tezromania Nov 11 '14 at 17:05
  • It is also worth noting that this solution does not work with relative Uri's. See my answer below for more information. – Moeri Dec 18 '14 at 11:03
  • @tezromania: Actually it doesn't break i18n (how can Unicode break i18n? duh). It just [uses a non-standard URI encoding](http://stackoverflow.com/a/1825657/236871). It seems that ParseQueryString uses UrlEncodeUnicode() (which is already deprecated) and the resulting URI is not accepted in some browsers. – KurzedMetal Oct 02 '15 at 18:55
  • If you want to avoid the reliance on `System.Web`, you can use the extensions provided [here](https://github.com/xamarin/PortableRazor/blob/master/PortableRazor.Web/HttpUtility.cs) – DavidG Jul 12 '17 at 15:58
  • 1
    `HttpUtility.ParseQueryString()` returns `HttpValueCollection` which is not public class and implements `NameValueCollection`. – mihkov Jan 08 '19 at 09:31
  • @DarinDimitrov It does some of the encoding for my values, but what about the ISO date format which has + chars. It left them as + chars - I need them to be converted to %2B. Please could you help shed some light on this? – Alan Ball Oct 03 '19 at 17:29
141

I've wrapped Darin's answer into a nicely reusable extension method.

public static class UriExtensions
{
    /// <summary>
    /// Adds the specified parameter to the Query String.
    /// </summary>
    /// <param name="url"></param>
    /// <param name="paramName">Name of the parameter to add.</param>
    /// <param name="paramValue">Value for the parameter to add.</param>
    /// <returns>Url with added parameter.</returns>
    public static Uri AddParameter(this Uri url, string paramName, string paramValue)
    {
        var uriBuilder = new UriBuilder(url);
        var query = HttpUtility.ParseQueryString(uriBuilder.Query);
        query[paramName] = paramValue;
        uriBuilder.Query = query.ToString();

        return uriBuilder.Uri;
    }
}
starball
  • 20,030
  • 7
  • 43
  • 238
Brinkie
  • 1,411
  • 1
  • 9
  • 2
61

The provided answers have issues with relative URLs, 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.example/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("www.domain.example/test?param1=val1").ExtendQuery(new Dictionary&lt;string, string&gt; { { "param2", "val2" }, { "param3", "val3" } });
    ///
    ///     // returns "/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("/test?param1=val1").ExtendQuery(new Dictionary&lt;string, string&gt; { { "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.example/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("www.domain.example/test?param1=val1").ExtendQuery(new { param2 = "val2", param3 = "val3" });
    ///
    ///     // returns "/test?param1=val1&amp;param2=val2&amp;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.example/test");
      var values = new Dictionary<string, string>();
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/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.example/test#div");
      var values = new Dictionary<string, string>();
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/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.example/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.example/test?param1=val1&param2=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.example/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.example/test#div?param1=val1&param2=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.example/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.example/test?param1=val1&param2=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&param2=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&param2=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&param2=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.example/test");
      var values = new { param1 = "val1", param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test?param1=val1&param2=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.example/test?param1=val1");
      var values = new { param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test?param1=val1&param2=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&param2=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&param2=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&param2=val2", UriKind.Relative)));
    }
  }
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Moeri
  • 9,104
  • 5
  • 43
  • 56
  • unfortunately this solution does not work for ASP.NET 5 using cloud .NET as HttpUtility does not seem to be available. But it's a great solution otherwise. See http://stackoverflow.com/questions/29992848/parse-and-modify-a-query-string-in-dnx – diegosasw Aug 28 '15 at 03:39
  • 1
    "Add_to_query_string_dictionary_when_url_contains_hash_and_no_query_string_should_add_values" should test that the URL becomes http://www.domain.com/test?param1=val1&param2=val2#div – gliljas Sep 01 '15 at 10:04
  • Please counter check, whether you're not better off using `uri.AbsoluteUri` instead of `uri.ToString()` because of nasty un-escaping effects. – Nico Feb 17 '17 at 09:44
  • 2
    Addition: `uri.AbsoluteUri` throws, if the uri is not absolute! – Nico Mar 03 '17 at 10:22
46

Note you can add the Microsoft.AspNetCore.WebUtilities nuget package from Microsoft and then use this to append values to query string:

QueryHelpers.AddQueryString(longurl, "action", "login1")
QueryHelpers.AddQueryString(longurl, new Dictionary<string, string> { { "action", "login1" }, { "attempts", "11" } });
Michael
  • 11,571
  • 4
  • 63
  • 61
  • 10
    As of ASP.NET Core 3.0 WebUtilities is now part of the ASP.NET SDK so no nuget package needed. – user1069816 Mar 15 '20 at 22:56
  • 2
    The problem with `AddQueryString` is that it will always add, if there is already the key, it won't update, but create duplicate keys, with is bad – Vencovsky May 21 '20 at 20:30
  • 1
    @Vencovsky But you can check if it exists using `QueryHelpers.ParseQuery` – DavidG Feb 03 '21 at 14:54
  • 1
    @Vencovsky I disagree;, why would you do multiple passes with different values of the same parameter on the same query string? – Zimano Feb 24 '21 at 09:24
  • 3
    @Vencovsky: *"it won't update, but create duplicate keys, with is bad"* On the contrary: Duplicate keys are perfectly valid in query strings. – Heinzi Aug 03 '22 at 14:13
17

The following solution works for ASP.NET 5 (vNext) and it uses QueryHelpers class to build a URI with parameters.

    public Uri GetUri()
    {
        var location = _config.Get("http://iberia.com");
        Dictionary<string, string> values = GetDictionaryParameters();

        var uri = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(location, values);
        return new Uri(uri);
    }

    private Dictionary<string,string> GetDictionaryParameters()
    {
        Dictionary<string, string> values = new Dictionary<string, string>
        {
            { "param1", "value1" },
            { "param2", "value2"},
            { "param3", "value3"}
        };
        return values;
    }

The result URI would have http://iberia.com?param1=value1&param2=value2&param3=value3

Fred
  • 12,086
  • 7
  • 60
  • 83
diegosasw
  • 13,734
  • 16
  • 95
  • 159
  • 1
    The only problem with using a Dictionary as the store for query keys and values is that query strings can have duplicate keys with different values. I believe that a request to an ASP.NET site parses that as an array of values for that one key. – Seth Dec 08 '16 at 21:50
2

This is even more frustrating because now (.net 5) MS have marked many (all) of their methods that take a string instead of a Uri as obsolete.

Anyway, probably a better way to manipulate relative Uris is to give it what it wants:

var requestUri = new Uri("x://x").MakeRelativeUri(
   new UriBuilder("x://x") { Path = path, Query = query }.Uri);

You can use the other answers to actually build the query string.

satnhak
  • 9,407
  • 5
  • 63
  • 81
1

I like Bjorn's answer, however the solution he's provided is misleading, as the method updates an existing parameter, rather than adding it if it doesn't exist.. To make it a bit safer, I've adapted it below.

public static class UriExtensions
{
    /// <summary>
    /// Adds or Updates the specified parameter to the Query String.
    /// </summary>
    /// <param name="url"></param>
    /// <param name="paramName">Name of the parameter to add.</param>
    /// <param name="paramValue">Value for the parameter to add.</param>
    /// <returns>Url with added parameter.</returns>
    public static Uri AddOrUpdateParameter(this Uri url, string paramName, string paramValue)
    {
        var uriBuilder = new UriBuilder(url);
        var query = HttpUtility.ParseQueryString(uriBuilder.Query);

        if (query.AllKeys.Contains(paramName))
        {
            query[paramName] = paramValue;
        }
        else
        {
            query.Add(paramName, paramValue);
        }
        uriBuilder.Query = query.ToString();

        return uriBuilder.Uri;
    }
}
Stevieboy84
  • 155
  • 1
  • 9
  • I really made a marginal edit of the code, I didn't provide it (the OP did) ... what will the difference though? –  Mar 17 '16 at 11:10
  • 1
    The if / else is not necessary, just do `query[paramName] = paramValue;` in all cases. It it exists, it wil be overridden. If it not exists, the key wil be created. – Richard Mar 08 '19 at 09:13
1

The end to all URL query string editing woes

After lots of toil and fiddling with the Uri class, and other solutions, here're my string extension methods to solve my problems.

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

public static class StringExtensions
{
    public static string AddToQueryString(this string url, params object[] keysAndValues)
    {
        return UpdateQueryString(url, q =>
        {
            for (var i = 0; i < keysAndValues.Length; i += 2)
            {
                q.Set(keysAndValues[i].ToString(), keysAndValues[i + 1].ToString());
            }
        });
    }

    public static string RemoveFromQueryString(this string url, params string[] keys)
    {
        return UpdateQueryString(url, q =>
        {
            foreach (var key in keys)
            {
                q.Remove(key);
            }
        });
    }

    public static string UpdateQueryString(string url, Action<NameValueCollection> func)
    {
        var urlWithoutQueryString = url.Contains('?') ? url.Substring(0, url.IndexOf('?')) : url;
        var queryString = url.Contains('?') ? url.Substring(url.IndexOf('?')) : null;
        var query = HttpUtility.ParseQueryString(queryString ?? string.Empty);

        func(query);

        return urlWithoutQueryString + (query.Count > 0 ? "?" : string.Empty) + query;
    }
}
  • 1
    I'd discourage people from using raw `string`s to represent URLs like this considering the `Uri` class already exists for that purpose. Either use that, or create a brand new abstraction if features are missing. – julealgon Nov 20 '18 at 19:11