How to build a query string for a URL in C#?

405,177

Solution 1

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...

Solution 2

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://docs.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));

Solution 3

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

Solution 4

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

Solution 5

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
Share:
405,177
Boaz
Author by

Boaz

Updated on August 06, 2021

Comments

  • Boaz
    Boaz almost 3 years

    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?

  • Guss
    Guss about 15 years
    the Uri class has no methods to manage the query string, other then getting it and setting it (and I'm not sure about the latter)
  • ageektrapped
    ageektrapped about 15 years
    The Uri class is good once you have a URI built including the query. Uri is immutable so you can't add to it once it's created. There is the UriBuilder class, but IIRC it doesn't have a method for query string; it's still left to the programmer to create it. The Uri class is good once you have it constructed for things like proper escaping.
  • annakata
    annakata about 15 years
    nicely encapsulated but that format on "?{0}" is kind of unnecessarily expensive :)
  • Nick Allen
    Nick Allen about 15 years
    changed the class name to QueryString.. Query is a little ambiguous
  • Xiaolong
    Xiaolong almost 15 years
    Thank you... i noticed that the NameValueCollection it returns has a ToString() that acts differently but couldn't figure out why.
  • Roy Tinker
    Roy Tinker about 13 years
    You could probably create an extension method called ToURLQueryString for the IDictionary interface: public static string ToURLQueryString(this IDictionary dict) { ... }
  • Daniel Cassidy
    Daniel Cassidy about 13 years
    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, and 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 goo.gl/uk1Ag
  • Mauricio Scheffer
    Mauricio Scheffer almost 13 years
    @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.
  • David Pope
    David Pope over 12 years
    @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.
  • Mauricio Scheffer
    Mauricio Scheffer over 12 years
    @DavidPope : use GetValues(key)
  • David Pope
    David Pope over 12 years
    @MauricioScheffer: I'm not building the querystring, WebClient is. I guess my comment is more about WebClient; slightly offtopic but relevant to the broader discussion.
  • Mauricio Scheffer
    Mauricio Scheffer over 12 years
    @DavidPope : ah, sorry. That does look like a bug in WebClient, I'd file an issue in connect.microsoft.com , although I doubt they'll fix it :(
  • Sam
    Sam over 12 years
    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.
  • Josh Noe
    Josh Noe over 11 years
    This fails if any of the values are null
  • alex
    alex over 11 years
    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.
  • Frank Schwieterman
    Frank Schwieterman about 11 years
    Note there is an important difference between HttpUtilityPraseQueryString("") and new NameValueCollection() -- only the HttpUtility result will override ToString() to produce a proper querystring
  • Andy
    Andy almost 11 years
    Perfect. Added to my in-house library. :)
  • Ufuk Hacıoğulları
    Ufuk Hacıoğulları almost 11 years
    You should also URL encode the value. queryString.Add(name, Uri.EscapeDataString(value));
  • Ufuk Hacıoğulları
    Ufuk Hacıoğulları almost 11 years
    Check this line. / is not encoded.
  • Ufuk Hacıoğulları
    Ufuk Hacıoğulları over 10 years
    Thanks for improving this answer.It fixed the problem with multibyte characters.
  • mpen
    mpen over 10 years
    Nice! But you don't need the .ToArray()s.
  • dav_i
    dav_i over 10 years
    In your second solution, surely httpValueCollection.Count == 0 will never return true as it is preceded by httpValueCollection.Add(name, value)?
  • dav_i
    dav_i over 10 years
    httpValueCollection.ToString() actually calls httpValueCollection.ToString(true) so adding the true explicity is not required.
  • Vedran
    Vedran over 10 years
    @dav_i That's because I didn't want to make any changes to the original Microsoft code except the encoding part.
  • Yuriy Faktorovich
    Yuriy Faktorovich over 10 years
    Side note, this doesn't work with relative urls because you can't instantiate the UriBuilder from a relative Uri.
  • Byron Whitlock
    Byron Whitlock over 10 years
    This is the best answer
  • Todd Menier
    Todd Menier about 10 years
    GREAT trick! I used this in Flurl and credited you in the source. Thanks!
  • Kugel
    Kugel about 10 years
    I like that this doesn't use HttpUtility which is under System.Web and not available everywhere.
  • Finster
    Finster about 10 years
    What about cases where you want multiple instances of a name in the query string? For example, "type=10&type=21".
  • jeremysawesome
    jeremysawesome almost 10 years
    @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.
  • PEK
    PEK almost 10 years
    You might want to use Uri.EscapeDataString instead of HttpUtility.UrlEncode which is more portable. See stackoverflow.com/questions/2573290/…
  • Gayan
    Gayan almost 10 years
    this is wrong it generate many query strings for each key value pair
  • crush
    crush over 9 years
    @YuriyFaktorovich Gah. I'm wrong again. OperationNotSupported for relative uris. Best I can do is return a new Uri it seems by building it with strings internally.
  • Matti Virkkunen
    Matti Virkkunen over 9 years
    @GayanRanasinghe: What does that even mean?
  • Thomas Weller
    Thomas Weller about 9 years
    Would you mind additionally licensing this under CC0 or something? (also @Andy: please respect CC-BY-SA in your inhouse library)
  • Vedran
    Vedran about 9 years
    @Thomas .NET Core is under MIT license, so this shouldn't be a problem.
  • ozba
    ozba over 8 years
    HttpValueCollection is an internal class therefore you cannot instantiated it.
  • Luis Perez
    Luis Perez about 8 years
    For an example of using the same technique but being able to use a syntax like query = BuildQueryString(new { param = "value" }) see the answer stackoverflow.com/a/36826806/984780
  • Rostov
    Rostov about 8 years
    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.
  • Thomas Walsh
    Thomas Walsh over 7 years
    +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 "?".
  • daramasala
    daramasala over 7 years
    There is no reason to implement your own query builder when one is already available in C# libraries - see the answer by John Bledsoe
  • Ody
    Ody about 7 years
    I prefer your approach Simple and Elegant, However, HttpUtility requires System.Web
  • Mathemats
    Mathemats almost 7 years
    This answer handles single parameters with multiple values. eg ?id=1&id=3&id=2&id=9
  • Paul Totzke
    Paul Totzke over 6 years
    I added a remove key so that a duplicate can't be added. dotnetfiddle.net/hTlyAd
  • Gaspa79
    Gaspa79 about 6 years
    The standard-compliant version is awesome!
  • SeeSharp
    SeeSharp over 5 years
    fo mine nvc.GetValues(key) returns the incorrect result when the value contains some special characters, for example, '+'. It cast it to space!
  • skorenb
    skorenb over 5 years
    you have to also include using System.Linq to get rid of using Linq errors
  • Soroush Falahati
    Soroush Falahati over 5 years
    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.
  • mlhDev
    mlhDev over 5 years
    adding to @FrankSchwieterman & @igaltabachnik - HttpUtility.ParseQueryString actually returns the internal HttpValueCollection, a subclass of NameValueCollection, which overrides ToString and does the encoding there. code via referencesource.ms.com
  • Tyler Liu
    Tyler Liu over 5 years
    It's a pity that it requires System.Web which is not available in .Net core.
  • Oswald
    Oswald about 5 years
    Why you don't use "new NameValueCollection()" at the very first line?
  • Sven
    Sven over 4 years
    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
  • O. R. Mapper
    O. R. Mapper over 4 years
    @Oswald: Because it's not a NameValueCollection instance that you need, but an instance of a subclass of that type. And, as is stated below the code: "The HttpValueCollection is internal and so you cannot directly construct an instance."
  • Stanley G.
    Stanley G. over 4 years
    :+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!
  • Mahyar Mottaghi Zadeh
    Mahyar Mottaghi Zadeh over 3 years
    As far as I've tried, this is the solution that can give you the opportunity to add Arrays in your QueryString !!
  • Eric Mutta
    Eric Mutta about 3 years
    It appears the issue mentioned by @alex is fixed as of .NET 5.0 (e,g if you include a chinese character like it is encoded as %e6%98%af) so you shouldn't worry about HttpUtility.ParseQueryString() not being standards-compliant.
  • flq
    flq almost 3 years
    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.
  • Matt
    Matt over 2 years
    For the .Net Core QueryHelpers a Dictionary could cause issues as in certain circumstances you may need to pass multiple parameters with the same key, e.g. get a list of books back by book id's you may want ?book=1&book=2 so you may want to use an Enumerable KeyValue Pair instead.
  • GameSalutes
    GameSalutes over 2 years
    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
  • Denis Nutiu
    Denis Nutiu over 2 years
    I think the package is only available in dotnet 5.
  • Auspex
    Auspex almost 2 years
    The package is available in dotnet 3.1 (earlier, too, iirc), but it has the serious drawback of not being netstandard2.0 compatible, so not useful for my class library.