How to build a query string for a URL in C#?
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¶mA=A¶mB=B
Boaz
Updated on August 06, 2021Comments
-
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 about 15 yearsthe 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 about 15 yearsThe 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 about 15 yearsnicely encapsulated but that format on "?{0}" is kind of unnecessarily expensive :)
-
Nick Allen about 15 yearschanged the class name to QueryString.. Query is a little ambiguous
-
Xiaolong almost 15 yearsThank you... i noticed that the NameValueCollection it returns has a ToString() that acts differently but couldn't figure out why.
-
Roy Tinker about 13 yearsYou could probably create an extension method called ToURLQueryString for the IDictionary interface:
public static string ToURLQueryString(this IDictionary dict) { ... }
-
Daniel Cassidy about 13 yearsThe 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 aGET
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 samename
attribute. See goo.gl/uk1Ag -
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 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 over 12 years@DavidPope : use GetValues(key)
-
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 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 over 12 yearsRegarding 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 over 11 yearsThis fails if any of the values are null
-
alex over 11 yearsThis 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 about 11 yearsNote there is an important difference between HttpUtilityPraseQueryString("") and new NameValueCollection() -- only the HttpUtility result will override ToString() to produce a proper querystring
-
Andy almost 11 yearsPerfect. Added to my in-house library. :)
-
Ufuk Hacıoğulları almost 11 yearsYou should also URL encode the value. queryString.Add(name, Uri.EscapeDataString(value));
-
Ufuk Hacıoğulları almost 11 yearsCheck this line. / is not encoded.
-
Ufuk Hacıoğulları over 10 yearsThanks for improving this answer.It fixed the problem with multibyte characters.
-
mpen over 10 yearsNice! But you don't need the
.ToArray()
s. -
dav_i over 10 yearsIn your second solution, surely
httpValueCollection.Count == 0
will never returntrue
as it is preceded byhttpValueCollection.Add(name, value)
? -
dav_i over 10 years
httpValueCollection.ToString()
actually callshttpValueCollection.ToString(true)
so adding thetrue
explicity is not required. -
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 over 10 yearsSide note, this doesn't work with relative urls because you can't instantiate the UriBuilder from a relative Uri.
-
Byron Whitlock over 10 yearsThis is the best answer
-
Todd Menier about 10 yearsGREAT trick! I used this in Flurl and credited you in the source. Thanks!
-
Kugel about 10 yearsI like that this doesn't use HttpUtility which is under System.Web and not available everywhere.
-
Finster about 10 yearsWhat about cases where you want multiple instances of a name in the query string? For example, "type=10&type=21".
-
jeremysawesome almost 10 years@Finster You can add multiple instances of a name to the query string using the
Add
method. I.equeryString.Add("type", "1"); queryString.Add("type", "2");
Using theAdd
method is probably a better way of doing this all of the time actually. -
PEK almost 10 yearsYou might want to use Uri.EscapeDataString instead of HttpUtility.UrlEncode which is more portable. See stackoverflow.com/questions/2573290/…
-
Gayan almost 10 yearsthis is wrong it generate many query strings for each key value pair
-
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 over 9 years@GayanRanasinghe: What does that even mean?
-
Thomas Weller about 9 yearsWould you mind additionally licensing this under CC0 or something? (also @Andy: please respect CC-BY-SA in your inhouse library)
-
Vedran about 9 years@Thomas .NET Core is under MIT license, so this shouldn't be a problem.
-
ozba over 8 yearsHttpValueCollection is an internal class therefore you cannot instantiated it.
-
Luis Perez about 8 yearsFor 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 about 8 yearsThis 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 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 over 7 yearsThere is no reason to implement your own query builder when one is already available in C# libraries - see the answer by John Bledsoe
-
Ody about 7 yearsI prefer your approach Simple and Elegant, However, HttpUtility requires System.Web
-
Mathemats almost 7 yearsThis answer handles single parameters with multiple values. eg ?id=1&id=3&id=2&id=9
-
Paul Totzke over 6 yearsI added a remove key so that a duplicate can't be added. dotnetfiddle.net/hTlyAd
-
Gaspa79 about 6 yearsThe standard-compliant version is awesome!
-
SeeSharp over 5 yearsfo mine nvc.GetValues(key) returns the incorrect result when the value contains some special characters, for example, '+'. It cast it to space!
-
skorenb over 5 yearsyou have to also include
using System.Linq
to get rid of using Linq errors -
Soroush Falahati over 5 yearsNow, 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 over 5 yearsadding to @FrankSchwieterman & @igaltabachnik -
HttpUtility.ParseQueryString
actually returns the internalHttpValueCollection
, a subclass of NameValueCollection, which overridesToString
and does the encoding there. code via referencesource.ms.com -
Tyler Liu over 5 yearsIt's a pity that it requires System.Web which is not available in .Net core.
-
Oswald about 5 yearsWhy you don't use "new NameValueCollection()" at the very first line?
-
Sven over 4 yearsWhile 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 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. 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 aUri
first. Thanks! -
Mahyar Mottaghi Zadeh over 3 yearsAs far as I've tried, this is the solution that can give you the opportunity to add Arrays in your QueryString !!
-
Eric Mutta about 3 yearsIt 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 aboutHttpUtility.ParseQueryString()
not being standards-compliant. -
flq almost 3 yearsThere 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 over 2 yearsFor 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 over 2 yearsCan'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 over 2 yearsI think the package is only available in dotnet 5.
-
Auspex almost 2 yearsThe 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.