594

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?

Keith Pinson
  • 7,835
  • 7
  • 61
  • 104
Boaz
  • 25,331
  • 21
  • 69
  • 77
  • 32
    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? – Grimace of Despair Dec 01 '13 at 21:27
  • 6
    You're not missing anything. Querystring building is a major gap in the framework that I've tried to fill with [Flurl](http://tmenier.github.io/Flurl). – Todd Menier May 07 '14 at 14:37
  • 2
    [Personally the technique I use is one that I've referenced in this question:](http://stackoverflow.com/a/26744471/2108310) – Rostov May 17 '16 at 18:00
  • You just got me thinking I should build one.. new UrlBuilder(existing).AddQuery("key", "value").ToString() – Demetris Leptos Jun 22 '17 at 13:48
  • This answer will work for easily nested objects as well [enter link description here](https://stackoverflow.com/a/60936159/9590885) – giorgi02 May 30 '21 at 11:02

40 Answers40

846

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));
Shaggy
  • 5,422
  • 28
  • 98
  • 163
John Bledsoe
  • 17,142
  • 5
  • 42
  • 59
  • 6
    You could probably create an extension method called ToURLQueryString for the IDictionary interface: `public static string ToURLQueryString(this IDictionary dict) { ... }` – Roy Tinker Mar 10 '11 at 03:28
  • 73
    This method is not [standard-compliant](http://en.wikipedia.org/wiki/Percent-encoding#Non-standard_implementations) 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. – alex Sep 26 '12 at 14:42
  • 29
    Note there is an important difference between HttpUtilityPraseQueryString("") and new NameValueCollection() -- only the HttpUtility result will override ToString() to produce a proper querystring – Frank Schwieterman Feb 19 '13 at 05:58
  • This is the best answer – Byron Whitlock Feb 10 '14 at 20:51
  • 2
    GREAT trick! I used this in [Flurl](https://github.com/tmenier/Flurl) and credited you in the source. Thanks! – Todd Menier Feb 21 '14 at 06:51
  • 10
    What about cases where you want multiple instances of a name in the query string? For example, "type=10&type=21". – Finster Mar 31 '14 at 21:29
  • 8
    @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. – jeremysawesome Jun 18 '14 at 15:11
  • For an example of using the same technique but being able to use a syntax like `query = BuildQueryString(new { param = "value" })` see the answer http://stackoverflow.com/a/36826806/984780 – Luis Perez Apr 24 '16 at 17:57
  • 1
    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](https://referencesource.microsoft.com/#System.Web/HttpValueCollection.cs,1b61ed162f050087,references) – mlhDev Jan 15 '19 at 12:46
  • Why you don't use "new NameValueCollection()" at the very first line? – Oswald Apr 22 '19 at 12:03
  • 1
    @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." – O. R. Mapper Nov 15 '19 at 06:51
  • 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. – Eric Mutta Apr 05 '21 at 12:06
  • 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. – Matt Sep 28 '21 at 21:13
324

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

Jay Allen
  • 465
  • 3
  • 8
annakata
  • 74,572
  • 17
  • 113
  • 180
  • 24
    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 http://goo.gl/uk1Ag – Daniel Cassidy Mar 22 '11 at 17:18
  • 15
    @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. – Mauricio Scheffer Jul 25 '11 at 02:50
  • 4
    @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. – David Pope Nov 04 '11 at 18:24
  • 1
    @DavidPope : use GetValues(key) – Mauricio Scheffer Nov 04 '11 at 20:29
  • 1
    @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. – David Pope Nov 04 '11 at 22:03
  • @DavidPope : ah, sorry. That does look like a bug in WebClient, I'd file an issue in http://connect.microsoft.com , although I doubt they'll fix it :( – Mauricio Scheffer Nov 04 '11 at 22:27
  • 9
    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. – Sam Feb 10 '12 at 20:38
  • 12
    You might want to use Uri.EscapeDataString instead of HttpUtility.UrlEncode which is more portable. See http://stackoverflow.com/questions/2573290/httputility-urlencode-in-windows-phone-7 – PEK Jul 21 '14 at 07:09
  • 1
    There is no reason to implement your own query builder when one is already available in C# libraries - see the answer by John Bledsoe – daramasala Jan 26 '17 at 11:07
  • fo mine nvc.GetValues(key) returns the incorrect result when the value contains some special characters, for example, '+'. It cast it to space! – SeeSharp Oct 03 '18 at 15:08
  • you have to also include `using System.Linq` to get rid of using Linq errors – skorenb Oct 08 '18 at 07:02
113

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;
}
Vedran
  • 10,369
  • 5
  • 50
  • 57
57

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

Thomas
  • 2,368
  • 20
  • 22
Denis Nutiu
  • 1,178
  • 15
  • 22
  • 4
    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. – flq Jul 15 '21 at 07:54
  • 1
    Can't seem to access this in dotnet core 3.1. The namespace import is not found and attempting to add the nuget package https://www.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` – GameSalutes Dec 20 '21 at 20:27
  • I think the package is only available in dotnet 5. – Denis Nutiu Dec 21 '21 at 21:22
  • 1
    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. – Auspex Jun 10 '22 at 14:47
  • 2
    This is the *correct* modern solution, (took a while to find). Additionally the class `QueryString` from 'Microsoft.AspNetCore.Http' namespace can be used: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.querystring.create?view=aspnetcore-6.0 – somedotnetguy Mar 29 '23 at 13:38
49

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

Damian
  • 574
  • 1
  • 6
  • 19
Todd Menier
  • 37,557
  • 17
  • 150
  • 173
32

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
Community
  • 1
  • 1
Igal Tabachnik
  • 31,174
  • 15
  • 92
  • 157
  • Thank you... i noticed that the NameValueCollection it returns has a ToString() that acts differently but couldn't figure out why. – calebt Aug 03 '09 at 16:52
  • `httpValueCollection.ToString()` actually calls `httpValueCollection.ToString(true)` so adding the `true` explicity is not required. – dav_i Dec 10 '13 at 09:58
  • 8
    HttpValueCollection is an internal class therefore you cannot instantiated it. – ozba Jan 11 '16 at 10:19
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;
}
Community
  • 1
  • 1
Alfred
  • 800
  • 8
  • 16
25

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
dso
  • 9,463
  • 10
  • 53
  • 59
  • I like that this doesn't use HttpUtility which is under System.Web and not available everywhere. – Kugel Mar 13 '14 at 01:50
  • 1
    +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 "?". – Mathew Leger Sep 15 '16 at 01:52
  • This answer handles single parameters with multiple values. eg ?id=1&id=3&id=2&id=9 – Mathemats Jun 09 '17 at 04:08
22

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;
hortman
  • 1,086
  • 10
  • 11
  • This is the technique I use, and have referenced it in another question [http://stackoverflow.com/a/26744471/2108310](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. – Rostov May 17 '16 at 18:02
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));
}
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • :+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! – Stanley G. Dec 04 '19 at 20:07
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());
    }
Jay Douglass
  • 4,828
  • 2
  • 27
  • 19
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"
Gian Marco
  • 22,140
  • 8
  • 55
  • 44
  • 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. – Soroush Falahati Oct 29 '18 at 00:45
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
dav_i
  • 27,509
  • 17
  • 104
  • 136
  • I prefer your approach Simple and Elegant, However, HttpUtility requires System.Web – Ody Mar 03 '17 at 14:09
7

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);
redcalx
  • 8,177
  • 4
  • 56
  • 105
  • 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. https://github.com/aspnet/AspNetCore/issues/7945 – Sven Nov 05 '19 at 14:00
7

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();
Nick Allen
  • 11,970
  • 11
  • 45
  • 58
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();
    }
}
Luis Perez
  • 27,650
  • 10
  • 79
  • 80
  • 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 – S.Serpooshan Jun 14 '23 at 05:04
5

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.

b-zurg
  • 461
  • 6
  • 8
  • 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. – Rustem Zinnatullin Oct 31 '22 at 14:30
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.

Martin Harris
  • 28,277
  • 7
  • 90
  • 101
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
Andrew Gray
  • 3,756
  • 3
  • 39
  • 75
3

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"));
Şafak Gür
  • 7,045
  • 5
  • 59
  • 96
3

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();
Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
kroehre
  • 1,104
  • 5
  • 15
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).

Thomas Bratt
  • 48,038
  • 36
  • 121
  • 139
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;
}
Tomino
  • 5,969
  • 6
  • 38
  • 50
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);
}
Jon
  • 9,156
  • 9
  • 56
  • 73
job
  • 21
  • 2
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")
mpen
  • 272,448
  • 266
  • 850
  • 1,236
1
// USAGE
[TestMethod]
public void TestUrlBuilder()
{
    Console.WriteLine(
        new UrlBuilder("http://www.google.com?A=B")
            .AddPath("SomePathName")
            .AddPath("AnotherPathName")
            .SetQuery("SomeQueryKey", "SomeQueryValue")
            .AlterQuery("A", x => x + "C"));
}

Output:

http://www.google.com/SomePathName/AnotherPathName?A=BC&SomeQueryKey=SomeQueryValue

The code; you can all thank me somewhere, somehow :D

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

// By Demetris Leptos
namespace TheOperator.Foundation.Web
{
    public class UrlBuilder
    {
        public string Scheme { get; set; }

        public string Host { get; set; }

        public int? Port { get; set; }

        public List<string> Paths { get; set; }

        public SortedDictionary<string, string> QueryPairs { get; set; }

        public UrlBuilder(string url)
        {
            this.Paths = new List<string>();
            this.QueryPairs = new SortedDictionary<string, string>();

            string path = null;
            string query = null;
            Uri relativeUri = null;
            if (!Uri.TryCreate(url, UriKind.Relative, out relativeUri))
            {
                var uriBuilder = new UriBuilder(url);
                this.Scheme = uriBuilder.Scheme;
                this.Host = uriBuilder.Host;
                this.Port = uriBuilder.Port;
                path = uriBuilder.Path;
                query = uriBuilder.Query;
            }
            else
            {
                var queryIndex = url.IndexOf('?');
                if (queryIndex >= 0)
                {
                    path = url.Substring(0, queryIndex);
                    query = url.Substring(queryIndex + 1);
                }
                else
                {
                    path = url;
                }
            }
            this.Paths.AddRange(path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries));
            if (query != null)
            {
                var queryKeyValuePairs = HttpUtility.ParseQueryString(query);
                foreach (var queryKey in queryKeyValuePairs.AllKeys)
                {
                    this.QueryPairs[queryKey] = queryKeyValuePairs[queryKey];
                }
            }
        }

        public UrlBuilder AddPath(string value)
        {
            this.Paths.Add(value);
            return this;
        }

        public UrlBuilder SetQuery(string key, string value)
        {
            this.QueryPairs[key] = value;
            return this;
        }

        public UrlBuilder RemoveQuery(string key)
        {
            this.QueryPairs.Remove(key);
            return this;
        }

        public UrlBuilder AlterQuery(string key, Func<string, string> alterMethod, bool removeOnNull = false)
        {
            string value;
            this.QueryPairs.TryGetValue(key, out value);
            value = alterMethod(value);
            if (removeOnNull && value == null)
            {
                return this.RemoveQuery(key);
            }
            else
            {
                return this.SetQuery(key, value);
            }
        }

        public override string ToString()
        {
            var path = !string.IsNullOrWhiteSpace(this.Host)
                ? string.Join("/", this.Host, string.Join("/", this.Paths))
                : string.Join("/", this.Paths);
            var query = string.Join("&", this.QueryPairs.Select(x => string.Concat(x.Key, "=", HttpUtility.UrlEncode(x.Value))));
            return string.Concat(
                !string.IsNullOrWhiteSpace(this.Scheme) ? string.Concat(this.Scheme, "://") : null,
                path,
                !string.IsNullOrWhiteSpace(query) ? string.Concat("?", query) : null);
        }
    }
}
Demetris Leptos
  • 1,560
  • 11
  • 12
1

I went with the solution proposed by DSO (answered on Aug 2 '11 at 7:29), his solution does not require using HttpUtility. However, as per an article posted in Dotnetpearls, using a Dictionary is faster (in performance) than using NameValueCollection. Here is DSO's solution modified to use Dictionary in place of NameValueCollection.

    public static Dictionary<string, string> QueryParametersDictionary()
    {
        var dictionary = new Dictionary<string, string>();
        dictionary.Add("name", "John Doe");
        dictionary.Add("address.city", "Seattle");
        dictionary.Add("address.state_code", "WA");
        dictionary.Add("api_key", "5352345263456345635");

        return dictionary;
    }

    public static string ToQueryString(Dictionary<string, string> nvc)
    {
        StringBuilder sb = new StringBuilder();

        bool first = true;

        foreach (KeyValuePair<string, string> pair in nvc)
        {
                if (!first)
                {
                    sb.Append("&");
                }

                sb.AppendFormat("{0}={1}", Uri.EscapeDataString(pair.Key), Uri.EscapeDataString(pair.Value));

                first = false;
        }

        return sb.ToString();
    }
Ticus
  • 11
  • 1
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);
Mike Cole
  • 14,474
  • 28
  • 114
  • 194
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.

ccook
  • 5,869
  • 6
  • 56
  • 81
0

The code below is taken off the HttpValueCollection implementation of ToString, via ILSpy, which gives you a name=value querystring.

Unfortunately HttpValueCollection is an internal class which you only ever get back if you use HttpUtility.ParseQueryString(). I removed all the viewstate parts to it, and it encodes by default:

public static class HttpExtensions
{
    public static string ToQueryString(this NameValueCollection collection)
    {
        // This is based off the NameValueCollection.ToString() implementation
        int count = collection.Count;
        if (count == 0)
            return string.Empty;

        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < count; i++)
        {
            string text = collection.GetKey(i);
            text = HttpUtility.UrlEncodeUnicode(text);
            string value = (text != null) ? (text + "=") : string.Empty;
            string[] values = collection.GetValues(i);
            if (stringBuilder.Length > 0)
            {
                stringBuilder.Append('&');
            }
            if (values == null || values.Length == 0)
            {
                stringBuilder.Append(value);
            }
            else
            {
                if (values.Length == 1)
                {
                    stringBuilder.Append(value);
                    string text2 = values[0];
                    text2 = HttpUtility.UrlEncodeUnicode(text2);
                    stringBuilder.Append(text2);
                }
                else
                {
                    for (int j = 0; j < values.Length; j++)
                    {
                        if (j > 0)
                        {
                            stringBuilder.Append('&');
                        }
                        stringBuilder.Append(value);
                        string text2 = values[j];
                        text2 = HttpUtility.UrlEncodeUnicode(text2);
                        stringBuilder.Append(text2);
                    }
                }
            }
        }

        return stringBuilder.ToString();
    }
}
Chris S
  • 64,770
  • 52
  • 221
  • 239
0

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]))));
}
ThisGuy
  • 2,335
  • 1
  • 28
  • 34
0

Just for those that need the VB.NET version of the top-answer:

Public Function ToQueryString(nvc As System.Collections.Specialized.NameValueCollection) As String
    Dim array As String() = nvc.AllKeys.SelectMany(Function(key As String) nvc.GetValues(key), Function(key As String, value As String) String.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(key), System.Web.HttpUtility.UrlEncode(value))).ToArray()
    Return "?" + String.Join("&", array)
End Function

And the version without LINQ:

Public Function ToQueryString(nvc As System.Collections.Specialized.NameValueCollection) As String
    Dim lsParams As New List(Of String)()

    For Each strKey As String In nvc.AllKeys
        Dim astrValue As String() = nvc.GetValues(strKey)

        For Each strValue As String In astrValue
            lsParams.Add(String.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(strKey), System.Web.HttpUtility.UrlEncode(strValue)))
        Next ' Next strValue
    Next ' strKey
    Dim astrParams As String() = lsParams.ToArray()
    lsParams.Clear()
    lsParams = Nothing

    Return "?" + String.Join("&", astrParams)
End Function ' ToQueryString

And the C# version without LINQ:

    public static string ToQueryString(System.Collections.Specialized.NameValueCollection nvc)
    {
        List<string> lsParams = new List<string>();

        foreach (string strKey in nvc.AllKeys)
        {
            string[] astrValue = nvc.GetValues(strKey);

            foreach (string strValue in astrValue)
            {
                lsParams.Add(string.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(strKey), System.Web.HttpUtility.UrlEncode(strValue)));
            } // Next strValue

        } // Next strKey

        string[] astrParams =lsParams.ToArray();
        lsParams.Clear();
        lsParams = null;

        return "?" + string.Join("&", astrParams);
    } // End Function ToQueryString
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
0

Works for multiple values per key in NameValueCollection.

ex: { {"k1", "v1"}, {"k1", "v1"} } => ?k1=v1&k1=v1

/// <summary>
/// Get query string for name value collection.
/// </summary>
public static string ToQueryString(this NameValueCollection collection,
    bool prefixQuestionMark = true)
{
    collection.NullArgumentCheck();
    if (collection.Keys.Count == 0)
    {
        return "";
    }
    var buffer = new StringBuilder();
    if (prefixQuestionMark)
    {
        buffer.Append("?");
    }
    var append = false;
    for (int i = 0; i < collection.Keys.Count; i++)
    {
        var key = collection.Keys[i];
        var values = collection.GetValues(key);
        key.NullCheck();
        values.NullCheck();
        foreach (var value in values)
        {
            if (append)
            {
                buffer.Append("&");
            }
            append = true;
            buffer.AppendFormat("{0}={1}", key.UrlEncode(), value.UrlEncode());
        }
    }
    return buffer.ToString();
}
hIpPy
  • 4,649
  • 6
  • 51
  • 65
0

This is another (maybe redundant :-]) way for do that.

The conceptuals are the same of the Vedran answer in this page (take a look here).

But this class is more efficient, because it iterate through all Keys only one time: when ToString is invoked.

The formatting code is also semplified and improved.

Hope that could be helpful.

public sealed class QueryStringBuilder
{
    public QueryStringBuilder()
    {
        this.inner = HttpUtility.ParseQueryString(string.Empty);
    }

    public QueryStringBuilder(string queryString)
    {
        this.inner = HttpUtility.ParseQueryString(queryString);
    }

    public QueryStringBuilder(string queryString, Encoding encoding)
    {
        this.inner = HttpUtility.ParseQueryString(queryString, encoding);
    }

    private readonly NameValueCollection inner;

    public QueryStringBuilder AddKey(string key, string value)
    {
        this.inner.Add(key, value);
        return this;
    }

    public QueryStringBuilder RemoveKey(string key)
    {
        this.inner.Remove(key);
        return this;
    }

    public QueryStringBuilder Clear()
    {
        this.inner.Clear();
        return this;
    }

    public override String ToString()
    {
        if (this.inner.Count == 0)
            return string.Empty;

        var builder = new StringBuilder();

        for (int i = 0; i < this.inner.Count; i++)
        {
            if (builder.Length > 0)
                builder.Append('&');

            var key = this.inner.GetKey(i);
            var values = this.inner.GetValues(i);

            if (key == null || values == null || values.Length == 0)
                continue;

            for (int j = 0; j < values.Length; j++)
            {
                if (j > 0)
                    builder.Append('&');

                builder.Append(HttpUtility.UrlEncode(key));
                builder.Append('=');
                builder.Append(HttpUtility.UrlEncode(values[j]));
            }
        }

        return builder.ToString();
    }
}
Community
  • 1
  • 1
T-moty
  • 2,679
  • 1
  • 26
  • 31
0

Another approach is to create an extension of the class NameValueCollection that returns the complete Url:

public static class CustomMethods
{
    public static string ToUrl(this System.Collections.Specialized.NameValueCollection collection)
    {
        if (collection.Count == 0) return "";

        string completeUrl = "?";
        for (int i = 0; i < collection.Count; i++)
        {
            completeUrl += new Page().Server.UrlEncode(collection.GetKey(i)) + "=" + new Page().Server.UrlEncode(collection.Get(i));
            if ((i + 1) < collection.Count) completeUrl += "&";
        }

        return completeUrl;
    }
}

Then, you can use your new method, for example:

System.Collections.Specialized.NameValueCollection qString = new System.Collections.Specialized.NameValueCollection();
qString.Add("name", "MyName");
qString.Add("email", "myemail@test.com");
qString.ToUrl(); //Result: "?name=MyName&email=myemail%40test.com"
Albert
  • 1
0

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.

Jakob Bagterp
  • 450
  • 4
  • 12
0

I wrote a helper for my razor project using some of the hints from other answers.

The ParseQueryString business is necessary because we are not allowed to tamper with the QueryString object of the current request.

@helper GetQueryStringWithValue(string key, string value) {
    var queryString = System.Web.HttpUtility.ParseQueryString(HttpContext.Current.Request.QueryString.ToString());
    queryString[key] = value;
    @Html.Raw(queryString.ToString())
}

I use it like this:

location.search = '?@Helpers.GetQueryStringWithValue("var-name", "var-value")';

If you want it to take more than one value, just change the parameters to a Dictionary and add the pairs to the query string.

LOAS
  • 7,161
  • 2
  • 28
  • 25
-2

Here is an implementation which uses very basic language features. It's part of a class which we have to port and maintain in Objective C so we choose to have more lines of code but easier to port and understand by a programmer that isn't very familiar with C#.

        /// <summary>
        /// Builds a complete http url with query strings.
        /// </summary>
        /// <param name="pHostname"></param>
        /// <param name="pPort"></param>
        /// <param name="pPage">ex "/index.html" or index.html</param>
        /// <param name="pGetParams">a Dictionary<string,string> collection containing the key value pairs.  Pass null if there are none.</param>
        /// <returns>a string of the form: http://[pHostname]:[pPort/[pPage]?key1=val1&key2=val2...</returns>

  static public string buildURL(string pHostname, int pPort, string pPage, Dictionary<string,string> pGetParams)
        {
            StringBuilder sb = new StringBuilder(200);
            sb.Append("http://");
            sb.Append(pHostname);
            if( pPort != 80 ) {
                sb.Append(pPort);
            }
            // Allows page param to be passed in with or without leading slash.
            if( !pPage.StartsWith("/") ) {
                sb.Append("/");
            }
            sb.Append(pPage);

            if (pGetParams != null && pGetParams.Count > 0)
            {
                sb.Append("?");
                foreach (KeyValuePair<string, string> kvp in pGetParams)
                {
                    sb.Append(kvp.Key);
                    sb.Append("=");
                    sb.Append( System.Web.HttpUtility.UrlEncode(kvp.Value) );
                    sb.Append("&");
                }
                sb.Remove(sb.Length - 1, 1); // Remove the final '&'
            }
            return sb.ToString();
        }
blak3r
  • 16,066
  • 16
  • 78
  • 98
-3
public string UrlQueryStr(object data)
{
    if (data == null)
        return string.Empty;

    object val;
    StringBuilder sb = new StringBuilder();

    foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(data))
    {
        if ((val = prop.GetValue(data)) != null)
        {
            sb.AppendFormat("{0}{1}={2}", sb.Length == 0 ? '?' : '&',
                HttpUtility.UrlEncode(prop.Name), HttpUtility.UrlEncode(val.ToString()));
        }
    }
    return sb.ToString();
}
-6

EDIT - as pointed out in the comments, this is not the way to go.

There is such a class - the URI Class. "Provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI." (Microsoft docs).

The following example creates an instance of the Uri class and uses it to create a WebRequest instance.

C# example

Uri siteUri = new Uri("http://www.contoso.com/");

WebRequest wr = WebRequest.Create(siteUri);

Check it out, there are lots of methods on this class.

JonnyBoats
  • 5,177
  • 1
  • 36
  • 60
  • 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) – Guss May 06 '09 at 11:32
  • 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. – ageektrapped May 06 '09 at 11:32