623

A common task when calling web resources from a code is building a query string to including all the necessary parameters. While by all means no rocket science, there are some nifty details you need to take care of like, appending an & if not the first parameter, encoding the parameters etc.

The code to do it is very simple, but a bit tedious:

StringBuilder SB = new StringBuilder();
if (NeedsToAddParameter A) 
{ 
  SB.Append("A="); SB.Append(HttpUtility.UrlEncode("TheValueOfA")); 
}

if (NeedsToAddParameter B) 
{
  if (SB.Length>0) SB.Append("&"); 
  SB.Append("B="); SB.Append(HttpUtility.UrlEncode("TheValueOfB")); }
}

This is such a common task one would expect a utility class to exist that makes it more elegant and readable. Scanning MSDN, I failed to find one—which brings me to the following question:

What is the most elegant clean way you know of doing the above?

5
  • 37
    It's a bit sad that even at the current point in time, there seems to be no straightforward way to deal with querystrings. And by straightforward, I mean an OOB, non-internal, standards-compliant framework class. Or maybe I'm missing out on something? Commented Dec 1, 2013 at 21:27
  • 7
    You're not missing anything. Querystring building is a major gap in the framework that I've tried to fill with Flurl. Commented May 7, 2014 at 14:37
  • 2
    Personally the technique I use is one that I've referenced in this question: Commented May 17, 2016 at 18:00
  • You just got me thinking I should build one.. new UrlBuilder(existing).AddQuery("key", "value").ToString() Commented Jun 22, 2017 at 13:48
  • This answer will work for easily nested objects as well enter link description here Commented May 30, 2021 at 11:02

43 Answers 43

883

You can create a new writeable instance of HttpValueCollection by calling System.Web.HttpUtility.ParseQueryString(string.Empty), and then use it as any NameValueCollection. Once you have added the values you want, you can call ToString on the collection to get a query string, as follows:

NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

queryString.Add("key1", "value1");
queryString.Add("key2", "value2");

return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded

The HttpValueCollection is internal and so you cannot directly construct an instance. However, once you obtain an instance you can use it like any other NameValueCollection. Since the actual object you are working with is an HttpValueCollection, calling ToString method will call the overridden method on HttpValueCollection, which formats the collection as a URL-encoded query string.

After searching SO and the web for an answer to a similar issue, this is the most simple solution I could find.

.NET Core

If you're working in .NET Core, you can use the Microsoft.AspNetCore.WebUtilities.QueryHelpers class, which simplifies this greatly.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers

Sample Code:

const string url = "https://customer-information.azure-api.net/customers/search/taxnbr";
var param = new Dictionary<string, string>() { { "CIKey", "123456789" } };

var newUrl = new Uri(QueryHelpers.AddQueryString(url, param));
Sign up to request clarification or add additional context in comments.

14 Comments

You could probably create an extension method called ToURLQueryString for the IDictionary interface: public static string ToURLQueryString(this IDictionary dict) { ... }
This method is not standard-compliant for multibyte characters. It will encode them as %uXXXX instead of %XX%XX. Resulting query strings may be incorrectly interpreted by web servers. This is even documented in internal framework class HttpValueCollection that is returned by HttpUtility.ParseQueryString() . Comment says that they keep this behavior for backward-compatibility reasons.
Note there is an important difference between HttpUtilityPraseQueryString("") and new NameValueCollection() -- only the HttpUtility result will override ToString() to produce a proper querystring
What about cases where you want multiple instances of a name in the query string? For example, "type=10&type=21".
@Finster You can add multiple instances of a name to the query string using the Add method. I.e queryString.Add("type", "1"); queryString.Add("type", "2"); Using the Add method is probably a better way of doing this all of the time actually.
|
330

If you look under the hood the QueryString property is a NameValueCollection. When I've done similar things I've usually been interested in serialising AND deserialising so my suggestion is to build a NameValueCollection up and then pass to:

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

private string ToQueryString(NameValueCollection nvc)
{
    var array = (
        from key in nvc.AllKeys
        from value in nvc.GetValues(key)
            select string.Format(
                "{0}={1}",
                HttpUtility.UrlEncode(key),
                HttpUtility.UrlEncode(value))
        ).ToArray();
    return "?" + string.Join("&", array);
}

I imagine there's a super elegant way to do this in LINQ too...

11 Comments

The HTTP spec (RFC 2616) doesn't say anything about what query strings can contain. Nor does RFC 3986, which defines the generic URI format. The key/value pair format that is commonly used is called application/x-www-form-urlencoded, and is actually defined by HTML, for the purpose of submitting form data as part of a GET request. HTML 5 does not forbid multiple values per key in this format; in fact it requires that the browser produce multiple values per key in the case that the page (incorrectly) contains multiple fields with the same name attribute. See html.spec.whatwg.org
@annakata: I know my comment is over a year old (and the answer over two years old!), but NameValueCollection very much supports multiple values per key, by using the GetValues(key) method.
@MauricioScheffer: But NameValueCollection doesn't transform into a querystring "correctly". For example, if you set the QueryString parameter on WebClient where the same key is present multiple times, it turns into "path?key=value1,value2" instead of "path?key=value1&key=value2", which is a common (standard?) pattern.
Regarding multiple values per key, I believe that in HTML, if you have a multi-select list-box with multiple items selected and submitted, they are sent in the multiple value format mentioned by David.
You might want to use Uri.EscapeDataString instead of HttpUtility.UrlEncode which is more portable. See stackoverflow.com/questions/2573290/…
|
116

With the inspiration from Roy Tinker's comment, I ended up using a simple extension method on the Uri class that keeps my code concise and clean:

using System.Web;

public static class HttpExtensions
{
    public static Uri AddQuery(this Uri uri, string name, string value)
    {
        var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

        httpValueCollection.Remove(name);
        httpValueCollection.Add(name, value);

        var ub = new UriBuilder(uri);
        ub.Query = httpValueCollection.ToString();

        return ub.Uri;
    }
}

Usage:

Uri url = new Uri("http://localhost/rest/something/browse").
          AddQuery("page", "0").
          AddQuery("pageSize", "200");

Edit - Standards compliant variant

As several people pointed out, httpValueCollection.ToString() encodes Unicode characters in a non-standards-compliant way. This is a variant of the same extension method that handles such characters by invoking HttpUtility.UrlEncode method instead of the deprecated HttpUtility.UrlEncodeUnicode method.

using System.Web;

public static Uri AddQuery(this Uri uri, string name, string value)
{
    var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

    httpValueCollection.Remove(name);
    httpValueCollection.Add(name, value);

    var ub = new UriBuilder(uri);

    // this code block is taken from httpValueCollection.ToString() method
    // and modified so it encodes strings with HttpUtility.UrlEncode
    if (httpValueCollection.Count == 0)
        ub.Query = String.Empty;
    else
    {
        var sb = new StringBuilder();

        for (int i = 0; i < httpValueCollection.Count; i++)
        {
            string text = httpValueCollection.GetKey(i);
            {
                text = HttpUtility.UrlEncode(text);

                string val = (text != null) ? (text + "=") : string.Empty;
                string[] vals = httpValueCollection.GetValues(i);

                if (sb.Length > 0)
                    sb.Append('&');

                if (vals == null || vals.Length == 0)
                    sb.Append(val);
                else
                {
                    if (vals.Length == 1)
                    {
                        sb.Append(val);
                        sb.Append(HttpUtility.UrlEncode(vals[0]));
                    }
                    else
                    {
                        for (int j = 0; j < vals.Length; j++)
                        {
                            if (j > 0)
                                sb.Append('&');

                            sb.Append(val);
                            sb.Append(HttpUtility.UrlEncode(vals[j]));
                        }
                    }
                }
            }
        }

        ub.Query = sb.ToString();
    }

    return ub.Uri;
}

12 Comments

You should also URL encode the value. queryString.Add(name, Uri.EscapeDataString(value));
Thanks for improving this answer.It fixed the problem with multibyte characters.
Side note, this doesn't work with relative urls because you can't instantiate the UriBuilder from a relative Uri.
I added a remove key so that a duplicate can't be added. dotnetfiddle.net/hTlyAd
It's a pity that it requires System.Web which is not available in .Net core.
|
80

Curious that no one has mentioned QueryBuilder from AspNet.Core.

It's helpful when you have a query with duplicate key like ?filter=a&filter=b

var qb = new QueryBuilder();
qb.Add("filter", new string[] {"A", "B"});

Then you'll just add qb to the URI, it is converted automatically to string.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.extensions.querybuilder?view=aspnetcore-5.0

8 Comments

There are so many answers here, it took a while to find the one that is correct in current times of developing with .NET Core.
Can't seem to access this in dotnet core 3.1. The namespace import is not found and attempting to add the nuget package nuget.org/packages/Microsoft.AspNetCore.App.Ref/3.1.10 results in a restore error error: NU1213: The package Microsoft.AspNetCore.App.Ref 3.1.10 has a package type DotnetPlatform that is incompatible with this project. error: Package 'Microsoft.AspNetCore.App.Ref' is incompatible with 'all' frameworks in project
This is the correct modern solution, (took a while to find). Additionally the class QueryString from 'Microsoft.AspNetCore.Http' namespace can be used: learn.microsoft.com/en-us/dotnet/api/…
NB: QueryBuilder requires the Microsoft.AspNetCore.Http.Extensions package which has been deprecated and is no longer maintained.
@MarcL. From the QueryBuilder docs, notice the Package: Microsoft.AspNetCore.App.Ref. That means it ships with the ASP.NET Core shared framework, which is installed with the .NET Core SDK and runtime installers. As of .NET Core 3.0, projects using the Microsoft.NET.Sdk.Web MSBuild SDK implicitly reference the shared framework.
|
51

Flurl [disclosure: I'm the author] supports building query strings via anonymous objects (among other ways):

var url = "http://www.some-api.com".SetQueryParams(new
{
    api_key = ConfigurationManager.AppSettings["SomeApiKey"],
    max_results = 20,
    q = "Don't worry, I'll get encoded!"
});

The optional Flurl.Http companion lib allows you to do HTTP calls right off the same fluent call chain, extending it into a full-blown REST client:

T result = await "https://api.mysite.com"
    .AppendPathSegment("person")
    .SetQueryParams(new { ap_key = "my-key" })
    .WithOAuthBearerToken("MyToken")
    .PostJsonAsync(new { first_name = firstName, last_name = lastName })
    .ReceiveJson<T>();

The full package is available on NuGet:

PM> Install-Package Flurl.Http

or just the stand-alone URL builder:

PM> Install-Package Flurl

1 Comment

As far as I've tried, this is the solution that can give you the opportunity to add Arrays in your QueryString !!
31

I answered a similar question a while ago. Basically, the best way would be to use the class HttpValueCollection, which ASP.NET's Request.QueryString property actually is, unfortunately it is internal in the .NET framework. You could use Reflector to grab it (and place it into your Utils class). This way you could manipulate the query string like a NameValueCollection, but with all the url encoding/decoding issues taken care for you.

HttpValueCollection extends NameValueCollection, and has a constructor that takes an encoded query string (ampersands and question marks included), and it overrides a ToString() method to later rebuild the query string from the underlying collection.

Example:

  var coll = new HttpValueCollection();

  coll["userId"] = "50";
  coll["paramA"] = "A";
  coll["paramB"] = "B";      

  string query = coll.ToString(true); // true means use urlencode

  Console.WriteLine(query); // prints: userId=50&paramA=A&paramB=B

3 Comments

Thank you... i noticed that the NameValueCollection it returns has a ToString() that acts differently but couldn't figure out why.
httpValueCollection.ToString() actually calls httpValueCollection.ToString(true) so adding the true explicity is not required.
HttpValueCollection is an internal class therefore you cannot instantiated it.
31

Here's a fluent/lambda-ish way as an extension method (combining concepts in previous posts) that supports multiple values for the same key. My personal preference is extensions over wrappers for discover-ability by other team members for stuff like this. Note that there's controversy around encoding methods, plenty of posts about it on Stack Overflow (one such post) and MSDN bloggers (like this one).

public static string ToQueryString(this NameValueCollection source)
{
    return String.Join("&", source.AllKeys
        .SelectMany(key => source.GetValues(key)
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))))
        .ToArray());
}

edit: with null support, though you'll probably need to adapt it for your particular situation

public static string ToQueryString(this NameValueCollection source, bool removeEmptyEntries)
{
    return source != null ? String.Join("&", source.AllKeys
        .Where(key => !removeEmptyEntries || source.GetValues(key)
            .Where(value => !String.IsNullOrEmpty(value))
            .Any())
        .SelectMany(key => source.GetValues(key)
            .Where(value => !removeEmptyEntries || !String.IsNullOrEmpty(value))
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), value != null ? HttpUtility.UrlEncode(value) : string.Empty)))
        .ToArray())
        : string.Empty;
}

3 Comments

This fails if any of the values are null
this is wrong it generate many query strings for each key value pair
@GayanRanasinghe: What does that even mean?
26

Here's my late entry. I didn't like any of the others for various reasons, so I wrote my own.

This version features:

  • Use of StringBuilder only. No ToArray() calls or other extension methods. It doesn't look as pretty as some of the other responses, but I consider this a core function so efficiency is more important than having "fluent", "one-liner" code which hide inefficiencies.

  • Handles multiple values per key. (Didn't need it myself but just to silence Mauricio ;)

    public string ToQueryString(NameValueCollection nvc)
    {
        StringBuilder sb = new StringBuilder("?");
    
        bool first = true;
    
        foreach (string key in nvc.AllKeys)
        {
            foreach (string value in nvc.GetValues(key))
            {
                if (!first)
                {
                    sb.Append("&");
                }
    
                sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));
    
                first = false;
            }
        }
    
        return sb.ToString();
    }
    

Example Usage

        var queryParams = new NameValueCollection()
        {
            { "x", "1" },
            { "y", "2" },
            { "foo", "bar" },
            { "foo", "baz" },
            { "special chars", "? = &" },
        };

        string url = "http://example.com/stuff" + ToQueryString(queryParams);

        Console.WriteLine(url);

Output

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26

3 Comments

I like that this doesn't use HttpUtility which is under System.Web and not available everywhere.
+1 for not using linq and not using HttpUtility. I'd create an empty sb and ditch the "bool first" variable and then in the loop simply have sb.Append(sb.Length == 0 ? "?" : "&") before the sb.AppendFormat(). Now if nvc is empty the method will return an empty string instead of a lonely "?".
This answer handles single parameters with multiple values. eg ?id=1&id=3&id=2&id=9
25

I needed to solve the same problem for a portable class library (PCL) that I'm working on. In this case, I don't have access to System.Web so I can't use ParseQueryString.

Instead I used System.Net.Http.FormUrlEncodedContent like so:

var url = new UriBuilder("http://example.com");

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;

1 Comment

This is the technique I use, and have referenced it in another question http://stackoverflow.com/a/26744471/2108310 The only difference is that I use an array of KeyValue pairs... other than needing the reference to System.Net (which is available PCL as you stated) this is IMHO the simplest way to do it without including some third party package, or trying to hobble together some homebrew spaghetti mess.
14

How about creating extension methods that allow you to add the parameters in a fluent style like this?

string a = "http://www.somedomain.com/somepage.html"
    .AddQueryParam("A", "TheValueOfA")
    .AddQueryParam("B", "TheValueOfB")
    .AddQueryParam("Z", "TheValueOfZ");

string b = new StringBuilder("http://www.somedomain.com/anotherpage.html")
    .AddQueryParam("A", "TheValueOfA")
    .AddQueryParam("B", "TheValueOfB")
    .AddQueryParam("Z", "TheValueOfZ")
    .ToString(); 

Here's the overload that uses a string:

public static string AddQueryParam(
    this string source, string key, string value)
{
    string delim;
    if ((source == null) || !source.Contains("?"))
    {
        delim = "?";
    }
    else if (source.EndsWith("?") || source.EndsWith("&"))
    {
        delim = string.Empty;
    }
    else
    {
        delim = "&";
    }

    return source + delim + HttpUtility.UrlEncode(key)
        + "=" + HttpUtility.UrlEncode(value);
}

And here's the overload that uses a StringBuilder:

public static StringBuilder AddQueryParam(
    this StringBuilder source, string key, string value)
{
    bool hasQuery = false;
    for (int i = 0; i < source.Length; i++)
    {
        if (source[i] == '?')
        {
            hasQuery = true;
            break;
        }
    }

    string delim;
    if (!hasQuery)
    {
        delim = "?";
    }
    else if ((source[source.Length - 1] == '?')
        || (source[source.Length - 1] == '&'))
    {
        delim = string.Empty;
    }
    else
    {
        delim = "&";
    }

    return source.Append(delim).Append(HttpUtility.UrlEncode(key))
        .Append("=").Append(HttpUtility.UrlEncode(value));
}

1 Comment

:+1: for the simple String-based extension method. Some of the other answers may cover more edge cases, but this is sufficient for my situation, and it does not require me to construct a NameValueCollection, HttpValueCollection, or a Uri first. Thanks!
12

In dotnet core QueryHelpers.AddQueryString() will accept an IDictionary<string,string> of key-value pairs. To save a few memory allocs and CPU cycles you can use SortedList<,> instead of Dictionary<,>, with an appropriate capacity and items added in sort order...

var queryParams = new SortedList<string,string>(2);
queryParams.Add("abc", "val1");
queryParams.Add("def", "val2");

string requestUri = QueryHelpers.AddQueryString("https://localhost/api", queryParams);

2 Comments

While this seems to be a very elegant solution with .NET Core there is an issue because it does not support arrays. I try to send a list of IDs. github.com/aspnet/AspNetCore/issues/7945
This is probably the best answer. QueryHelpers is a hidden gem. .NET team should probably add methods on UriBuilder to make this better known.
10
    public static string ToQueryString(this Dictionary<string, string> source)
    {
        return String.Join("&", source.Select(kvp => String.Format("{0}={1}", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))).ToArray());
    }

    public static string ToQueryString(this NameValueCollection source)
    {
        return String.Join("&", source.Cast<string>().Select(key => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(source[key]))).ToArray());
    }

1 Comment

Nice! But you don't need the .ToArray()s.
9

Add this class to your project

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

public class QueryStringBuilder
{
    private readonly List<KeyValuePair<string, object>> _list;

    public QueryStringBuilder()
    {
        _list = new List<KeyValuePair<string, object>>();
    }

    public void Add(string name, object value)
    {
        _list.Add(new KeyValuePair<string, object>(name, value));
    }

    public override string ToString()
    {
        return String.Join("&", _list.Select(kvp => String.Concat(Uri.EscapeDataString(kvp.Key), "=", Uri.EscapeDataString(kvp.Value.ToString()))));
    }
}

And use it like this:

var actual = new QueryStringBuilder {
    {"foo", 123},
    {"bar", "val31"},
    {"bar", "val32"}
};

actual.Add("a+b", "c+d");

actual.ToString(); // "foo=123&bar=val31&bar=val32&a%2bb=c%2bd"

1 Comment

Now, this should be the accepted answer; works perfectly for arrays like "foo[]=1,foo[]=2" as well as keeping the order of parameters which is very important by the way.
8

Untested, but I think something along these lines would work quite nicely

public class QueryString
{
    private Dictionary<string,string> _Params = new Dictionary<string,string>();

    public overide ToString()
    {
        List<string> returnParams = new List<string>();

        foreach (KeyValuePair param in _Params)
        {
            returnParams.Add(String.Format("{0}={1}", param.Key, param.Value));
        }

        // return String.Format("?{0}", String.Join("&", returnParams.ToArray())); 

        // credit annakata
        return "?" + String.Join("&", returnParams.ToArray());
    }

    public void Add(string key, string value)
    {
        _Params.Add(key, HttpUtility.UrlEncode(value));
    }
}

QueryString query = new QueryString();

query.Add("param1", "value1");
query.Add("param2", "value2");

return query.ToString();

2 Comments

nicely encapsulated but that format on "?{0}" is kind of unnecessarily expensive :)
changed the class name to QueryString.. Query is a little ambiguous
7

My offering:

public static Uri AddQuery(this Uri uri, string name, string value)
{
    // this actually returns HttpValueCollection : NameValueCollection
    // which uses unicode compliant encoding on ToString()
    var query = HttpUtility.ParseQueryString(uri.Query);

    query.Add(name, value);

    var uriBuilder = new UriBuilder(uri)
    {
        Query = query.ToString()
    };

    return uriBuilder.Uri;
}

Usage:

var uri = new Uri("http://stackoverflow.com").AddQuery("such", "method")
                                             .AddQuery("wow", "soFluent");

// http://stackoverflow.com?such=method&wow=soFluent

1 Comment

I prefer your approach Simple and Elegant, However, HttpUtility requires System.Web
6

Combined the top answers to create an anonymous object version:

var queryString = HttpUtility2.BuildQueryString(new
{
    key2 = "value2",
    key1 = "value1",
});

That generates this:

key2=value2&key1=value1

Here's the code:

public static class HttpUtility2
{
    public static string BuildQueryString<T>(T obj)
    {
        var queryString = HttpUtility.ParseQueryString(string.Empty);

        foreach (var property in TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>())
        {
            var value = (property.GetValue(obj) ?? "").ToString();
            queryString.Add(property.Name, value);
        }

        return queryString.ToString();
    }
}

1 Comment

Using dnamic object and Reflection instead of other options like NameValueCollection does not seem to be good idea. Consider if query param names are unknown (variable) as in some web api's
6

There's lots of good answers here but for those using modern C# this may be a nice utility class to keep around.

public class QueryParamBuilder
{
    private readonly Dictionary<string, string> _fields = new();
    public QueryParamBuilder Add(string key, string value)
    {
        _fields.Add(key, value);
        return this;
    }
    public string Build()
    {
        return $"?{String.Join("&", _fields.Select(pair => $"{HttpUtility.UrlEncode(pair.Key)}={HttpUtility.UrlEncode(pair.Value)}"))}";
    }
    public static QueryParamBuilder New => new();
}

I use an internal Dictionary here because dictionaries are enumerable key value pairs internally which makes iterating over them much easier than a NameValueCollection.

Then the query string itself is a simple interpolated string with a join.

Additionally I provide a static interface into the constructor to make the construction of a new builder very easy and only allow one exposed method Add to add new query parameter values. Finally you terminate the chain with Build() to actually get the final string.

Here's an example of its usage

var queryString = QueryParamBuilder.New
     .Add("id", "0123")
     .Add("value2", 1234.ToString())
     .Add("valueWithSpace","value with spa12!@#@!ce")
     .Build();

The result is as expected

?id=0123&value2=1234&valueWithSpace=value+with+spa12!%40%23%40!ce

Hopefully some of you will find this nice and elegant.

1 Comment

Some servers accept duplicating keys and convert them to an array of values for the duplicating key. In the suggested implementation you're using Dictionary which wouldn't allow duplicate keys.
5

A quick extension method based version:

class Program
{
    static void Main(string[] args)
    {
        var parameters = new List<KeyValuePair<string, string>>
                             {
                                 new KeyValuePair<string, string>("A", "AValue"),
                                 new KeyValuePair<string, string>("B", "BValue")
                             };

        string output = "?" + string.Join("&", parameters.ConvertAll(param => param.ToQueryString()).ToArray());
    }
}

public static class KeyValueExtensions
{
    public static string ToQueryString(this KeyValuePair<string, string> obj)
    {
        return obj.Key + "=" + HttpUtility.UrlEncode(obj.Value);
    }
}

You could use a where clause to select which parameters get added to the string.

Comments

4

I have an extension method for Uri that:

  • Accepts anonymous objects: uri.WithQuery(new { name = "value" })
  • Accepts collections of string/string pairs (e.g. Dictionary`2).
  • Accepts collections of string/object pairs (e.g. RouteValueDictionary).
  • Accepts NameValueCollections.
  • Sorts the query values by key so the same values produce equal URIs.
  • Supports multiple values per key, preserving their original order.

The documented version can be found here.

The extension:

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;
}

The query parser:

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);
}

Here are the tests:

var uri = new Uri("https://stackoverflow.com/yo?oldKey=oldValue");

// Test with a string/string dictionary.
var q = uri.WithQuery(new Dictionary<string, string>
{
    ["k1"] = string.Empty,
    ["k2"] = null,
    ["k3"] = "v3"
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2&k3=v3"));

// Test with a string/object dictionary.
q = uri.WithQuery(new Dictionary<string, object>
{
    ["k1"] = "v1",
    ["k2"] = new[] { "v2a", "v2b" },
    ["k3"] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3"));

// Test with a name/value collection.
var nvc = new NameValueCollection()
{
    ["k1"] = string.Empty,
    ["k2"] = "v2a"
};

nvc.Add("k2", "v2b");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2=v2a&k2=v2b"));

// Test with any dictionary.
q = uri.WithQuery(new Dictionary<int, HashSet<string>>
{
    [1] = new HashSet<string> { "v1" },
    [2] = new HashSet<string> { "v2a", "v2b" },
    [3] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?1=v1&2=v2a&2=v2b&3"));

// Test with an anonymous object.
q = uri.WithQuery(new
{
    k1 = "v1",
    k2 = new[] { "v2a", "v2b" },
    k3 = new List<string> { "v3" },
    k4 = true,
    k5 = null as Queue<string>
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3=v3&k4=True&k5"));

// Keep existing query using a name/value collection.
nvc = HttpUtility.ParseQueryString(uri.Query);
nvc.Add("newKey", "newValue");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?newKey=newValue&oldKey=oldValue"));

// Merge two query objects using the RouteValueDictionary.
var an1 = new { k1 = "v1" };
var an2 = new { k2 = "v2" };

q = uri.WithQuery(
    new RouteValueDictionary(an1).Concat(
        new RouteValueDictionary(an2)));

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2"));

Comments

4

Chain-able wrapper class for HttpValueCollection:

namespace System.Web.Mvc {
    public class QueryStringBuilder {
        private NameValueCollection collection;
        public QueryStringBuilder() {
            collection = System.Web.HttpUtility.ParseQueryString(string.Empty);
        }
        public QueryStringBuilder Add(string key, string value) {
            collection.Add(key, value);
            return this;
        }
        public QueryStringBuilder Remove(string key) {
            collection.Remove(key);
            return this;
        }
        public string this[string key] {
            get { return collection[key]; }
            set { collection[key] = value; }
        }
        public string ToString() {
            return collection.ToString();
        }
    }
}

Example usage:

QueryStringBuilder parameters = new QueryStringBuilder()
    .Add("view", ViewBag.PageView)
    .Add("page", ViewBag.PageNumber)
    .Add("size", ViewBag.PageSize);
string queryString = parameters.ToString();

Comments

3

Assuming that you want to reduce dependencies to other assemblies and to keep things simple, you can do:

var sb = new System.Text.StringBuilder();

sb.Append("a=" + HttpUtility.UrlEncode("TheValueOfA") + "&");
sb.Append("b=" + HttpUtility.UrlEncode("TheValueOfB") + "&");
sb.Append("c=" + HttpUtility.UrlEncode("TheValueOfC") + "&");
sb.Append("d=" + HttpUtility.UrlEncode("TheValueOfD") + "&");

sb.Remove(sb.Length-1, 1); // Remove the final '&'

string result = sb.ToString();

This works well with loops too. The final ampersand removal needs to go outside of the loop.

Note that the concatenation operator is used to improve readability. The cost of using it compared to the cost of using a StringBuilder is minimal (I think Jeff Atwood posted something on this topic).

Comments

3

While not elegant, I opted for a simpler version that doesn't use NameValueCollecitons - just a builder pattern wrapped around StringBuilder.

public class UrlBuilder
{
    #region Variables / Properties

    private readonly StringBuilder _builder;

    #endregion Variables / Properties

    #region Constructor

    public UrlBuilder(string urlBase)
    {
        _builder = new StringBuilder(urlBase);
    }

    #endregion Constructor

    #region Methods

    public UrlBuilder AppendParameter(string paramName, string value)
    {
        if (_builder.ToString().Contains("?"))
            _builder.Append("&");
        else
            _builder.Append("?");

        _builder.Append(HttpUtility.UrlEncode(paramName));
        _builder.Append("=");
        _builder.Append(HttpUtility.UrlEncode(value));

        return this;
    }

    public override string ToString()
    {
        return _builder.ToString();
    }

    #endregion Methods
}

Per existing answers, I made sure to use HttpUtility.UrlEncode calls. It's used like so:

string url = new UrlBuilder("http://www.somedomain.com/")
             .AppendParameter("a", "true")
             .AppendParameter("b", "muffin")
             .AppendParameter("c", "muffin button")
             .ToString();
// Result: http://www.somedomain.com?a=true&b=muffin&c=muffin%20button

Comments

3

As the context of a query string often is HttpRequest as Request.QueryString used in MVC or surface controllers, why not use the built in QueryString type instead of converting with StringBuilder, NameValueCollection, HttpUtility, and other custom code?

For instance, a little something like this:

using Microsoft.AspNetCore.Http;

var queryString = QueryString.Create("key1", "value1")
queryString = queryString.Add("key2", "value2");

Disclaimer: The QueryString type isn't great for reading and updating values like a NameValueCollection, but it's simple for creating query strings and has built-in encoding.

Comments

3

The IEnumerable overload of QueryHelpers.AddQueryString lets you control the order of the parameters, avoids expensive data types, and minimizes boilerplate code.

string uri = QueryHelpers.AddQueryString("https://example.com", new KeyValuePair<string, string?>[] {
    new("param1", "value1"),
    new("param2", "value2") });

Comments

2

Same as accepted solution, but transfred to "dot" LINQ syntax...

private string ToQueryString(NameValueCollection nvc)
{
    if (nvc == null) return String.Empty;
    var queryParams = 
          string.Join("&", nvc.AllKeys.Select(key => 
              string.Join("&", nvc.GetValues(key).Select(v => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(v))))));
    return "?" + queryParams;
}

Comments

2

The query string can be added to a URL by:

  1. create a name value collection object
  2. add the query string items and their values to this object
  3. encode this name value collection object to the url the code is provided in the below link

https://blog.codingnovice.com/blog

public ActionResult Create()
{
    //declaring name value collection object
    NameValueCollection collection = new NameValueCollection();

    //adding new value to the name value collection object
    collection.Add("Id1", "wwe323");
    collection.Add("Id2", "454w");
    collection.Add("Id3", "tyt5656");
    collection.Add("Id4", "343wdsd");

    //generating query string
    string url = GenerateQueryString(collection);

    return View();
}

private string GenerateQueryString(NameValueCollection collection)
{
    var querystring = (
        from key in collection.AllKeys
        from value in collection.GetValues(key)
        select string.Format("{0}={1}",
            HttpUtility.UrlEncode(key),
            HttpUtility.UrlEncode(value))
    ).ToArray();
    return "?" + string.Join("&", querystring);
}

Comments

1

I added the following method to my PageBase class.

protected void Redirect(string url)
    {
        Response.Redirect(url);
    }
protected void Redirect(string url, NameValueCollection querystrings)
    {
        StringBuilder redirectUrl = new StringBuilder(url);

        if (querystrings != null)
        {
            for (int index = 0; index < querystrings.Count; index++)
            {
                if (index == 0)
                {
                    redirectUrl.Append("?");
                }

                redirectUrl.Append(querystrings.Keys[index]);
                redirectUrl.Append("=");
                redirectUrl.Append(HttpUtility.UrlEncode(querystrings[index]));

                if (index < querystrings.Count - 1)
                {
                    redirectUrl.Append("&");
                }
            }
        }

        this.Redirect(redirectUrl.ToString());
    }

To call:

NameValueCollection querystrings = new NameValueCollection();    
querystrings.Add("language", "en");
querystrings.Add("id", "134");
this.Redirect("http://www.mypage.com", querystrings);

Comments

1

I wrote some extension methods that I have found very useful when working with QueryStrings. Often I want to start with the current QueryString and modify before using it. Something like,

var res = Request.QueryString.Duplicate()
  .ChangeField("field1", "somevalue")
  .ChangeField("field2", "only if following is true", true)
  .ChangeField("id", id, id>0)
  .WriteLocalPathWithQuery(Request.Url)); //Uses context to write the path

For more and the source: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

It's basic, but I like the style.

Comments

1

This is the identical to the accepted answer except slightly more compact:

private string ToQueryString(NameValueCollection nvc)
{
    return "?" + string.Join("&", nvc.AllKeys.Select(k => string.Format("{0}={1}", 
        HttpUtility.UrlEncode(k), 
        HttpUtility.UrlEncode(nvc[k]))));
}

Comments

1

Just wanted to throw in my 2 cents:

public static class HttpClientExt
{
    public static Uri AddQueryParams(this Uri uri, string query)
    {
        var ub = new UriBuilder(uri);
        ub.Query = string.IsNullOrEmpty(uri.Query) ? query : string.Join("&", uri.Query.Substring(1), query);
        return ub.Uri;
    }

    public static Uri AddQueryParams(this Uri uri, IEnumerable<string> query)
    {
        return uri.AddQueryParams(string.Join("&", query));
    } 

    public static Uri AddQueryParams(this Uri uri, string key, string value)
    {
        return uri.AddQueryParams(string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)));
    }

    public static Uri AddQueryParams(this Uri uri, params KeyValuePair<string,string>[] kvps)
    {
        return uri.AddQueryParams(kvps.Select(kvp => string.Join("=", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))));
    }

    public static Uri AddQueryParams(this Uri uri, IDictionary<string, string> kvps)
    {
        return uri.AddQueryParams(kvps.Select(kvp => string.Join("=", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))));
    }

    public static Uri AddQueryParams(this Uri uri, NameValueCollection nvc)
    {
        return uri.AddQueryParams(nvc.AllKeys.SelectMany(nvc.GetValues, (key, value) => string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))));
    }
}

The docs say that uri.Query will start with a ? if it's non-empty and you should trim it off if you're going to modify it.

Note that HttpUtility.UrlEncode is found in System.Web.

Usage:

var uri = new Uri("https://api.del.icio.us/v1/posts/suggest").AddQueryParam("url","http://stackoverflow.com")

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.