¿Cómo construir una cadena de consulta para una URL en C #?

Una tarea común cuando se llaman recursos web desde un código es construir una cadena de consulta para incluir todos los parámetros necesarios. Si bien, por supuesto, no hay ciencia espacial, hay algunos detalles ingeniosos que necesita para cuidar como, agregar un & si no el primer parámetro, la encoding de los parámetros, etc.

El código para hacerlo es muy simple, pero un poco tedioso:

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

Esta es una tarea tan común que uno esperaría que existiera una clase de utilidad que la haga más elegante y legible. Al escanear MSDN, no pude encontrar uno, lo que me lleva a la siguiente pregunta:

¿Cuál es la forma más elegante y limpia que conoces de hacer lo anterior?

Si mira bajo el capó, la propiedad QueryString es una NameValueCollection. Cuando he hecho cosas similares, usualmente he estado interesado en serializar y deserializar, así que mi sugerencia es crear una NameValueCollection y pasarla a:

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

Posiblemente podría haber formateado eso mejor 🙂

Me imagino que también hay una manera súper elegante de hacer esto en LINQ …

Puede crear una nueva instancia grabable de HttpValueCollection llamando a System.Web.HttpUtility.ParseQueryString(string.Empty) , y luego usarlo como cualquier NameValueCollection . Una vez que haya agregado los valores que desea, puede llamar a ToString en la colección para obtener una cadena de consulta, de la siguiente manera:

 NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty); queryString["key1"] = "value1"; queryString["key2"] = "value2"; return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded 

HttpValueCollection es interno y por lo tanto no puede construir directamente una instancia. Sin embargo, una vez que obtenga una instancia, puede usarla como cualquier otra NameValueCollection . Dado que el objeto real con el que está trabajando es un HttpValueCollection , al llamar al método ToString llamará al método reemplazado en HttpValueCollection , que formatea la colección como una cadena de consulta codificada en URL.

Después de buscar SO y la web para obtener una respuesta a un problema similar, esta es la solución más simple que pude encontrar.

.NET Core

Si está trabajando en .NET Core, puede usar la clase Microsoft.AspNetCore.WebUtilities.QueryHelpers , que simplifica esto en gran medida.

https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.webutilities.queryhelpers

Con la inspiración del comentario de Roy Tinker, terminé usando un método de extensión simple en la clase Uri que mantiene mi código conciso y limpio:

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

Uso:

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

Editar – Variante que cumple con los estándares

Como señalaron varias personas, httpValueCollection.ToString() codifica caracteres Unicode de una manera que no cumple con los estándares . Esta es una variante del mismo método de extensión que maneja dichos caracteres invocando el método HttpUtility.UrlEncode lugar del método HttpUtility.UrlEncodeUnicode desuso.

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

Respondí una pregunta similar hace un tiempo. Básicamente, la mejor manera sería utilizar la clase HttpValueCollection , que en realidad es la propiedad Request.QueryString de ASP.NET, desafortunadamente es interna en .NET Framework. Podría usar Reflector para tomarlo (y colocarlo en su clase de Utils). De esta forma, podría manipular la cadena de consulta como NameValueCollection, pero con todos los problemas de encoding / deencoding de url que se preocupan por usted.

HttpValueCollection extiende NameValueCollection , y tiene un constructor que toma una cadena de consulta codificada (signos de unión y signos de interrogación incluidos), y reemplaza un método ToString() para luego reconstruir la cadena de consulta de la colección subyacente.

Ejemplo:

  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 

Aquí hay una forma fluida / lambda-ish como un método de extensión (que combina conceptos en publicaciones anteriores) que admite múltiples valores para la misma clave. Mi preferencia personal es extensiones sobre wrappers para descubrir la habilidad de otros miembros del equipo para cosas como esta. Tenga en cuenta que hay controversia sobre los métodos de encoding, muchas publicaciones sobre Stack Overflow (una de esas publicaciones ) y MSDN bloggers (como esta ).

 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: con soporte nulo, aunque probablemente necesites adaptarlo para tu situación particular

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

Flurl [revelación: soy el autor] admite la creación de cadenas de consulta a través de objetos anónimos (entre otras formas):

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

El complemento opcional Flurl.Http le permite hacer llamadas HTTP directamente desde la misma cadena de llamadas fluidas, extendiéndolas a un cliente REST completo:

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

El paquete completo está disponible en NuGet:

PM> Install-Package Flurl.Http

o solo el creador de URL independiente:

PM> Install-Package Flurl

Aquí está mi entrada tardía. No me gustaban ninguno de los otros por varias razones, así que escribí el mío.

Esta versión cuenta con:

  • Uso de StringBuilder solamente. No ToArray () llamadas u otros métodos de extensión. No se ve tan bonito como algunas de las otras respuestas, pero considero que es una función central, por lo que la eficiencia es más importante que tener un código “fluido”, “de una sola línea” que oculte ineficiencias.

  • Maneja múltiples valores por clave. (No lo necesitaba yo solo, solo para silenciar a 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(); } 

Ejemplo de uso

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

Salida

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

¿Qué hay de la creación de métodos de extensión que le permiten agregar los parámetros en un estilo fluido como este?

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

Aquí está la sobrecarga que usa una 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); } 

Y aquí está la sobrecarga que usa un 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)); } 

Necesitaba resolver el mismo problema para una biblioteca de clases portátil (PCL) en la que estoy trabajando. En este caso, no tengo acceso a System.Web, así que no puedo usar ParseQueryString.

En su lugar, utilicé System.Net.Http.FormUrlEncodedContent así:

 var url = new UriBuilder("http://example.com"); url.Query = new FormUrlEncodedContent(new Dictionary() { {"param1", "val1"}, {"param2", "val2"}, {"param3", "val3"}, }).ReadAsStringAsync().Result; 
  public static string ToQueryString(this Dictionary 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().Select(key => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(source[key]))).ToArray()); } 

Mi oferta:

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

Uso:

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

Agrega esta clase a tu proyecto

 using System; using System.Collections.Generic; using System.Linq; using System.Web; public class QueryStringBuilder { private readonly List> _list; public QueryStringBuilder() { _list = new List>(); } public void Add(string name, object value) { _list.Add(new KeyValuePair(name, value)); } public override string ToString() { return String.Join("&", _list.Select(kvp => String.Concat(Uri.EscapeDataString(kvp.Key), "=", Uri.EscapeDataString(kvp.Value.ToString())))); } } 

Y úsalo así:

 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" 

No probado, pero creo que algo en esta línea funcionaría muy bien

 public class QueryString { private Dictionary _Params = new Dictionary(); public overide ToString() { List returnParams = new List(); 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(); 

Una versión basada en el método de extensión rápida:

 class Program { static void Main(string[] args) { var parameters = new List> { new KeyValuePair("A", "AValue"), new KeyValuePair("B", "BValue") }; string output = "?" + string.Join("&", parameters.ConvertAll(param => param.ToQueryString()).ToArray()); } } public static class KeyValueExtensions { public static string ToQueryString(this KeyValuePair obj) { return obj.Key + "=" + HttpUtility.UrlEncode(obj.Value); } } 

Puede usar una cláusula where para seleccionar qué parámetros se agregan a la cadena.

Suponiendo que desea reducir las dependencias de otros ensambles y mantener las cosas simples, puede hacer lo siguiente:

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

Esto funciona bien con bucles también. La eliminación definitiva del ampersand debe salir del círculo.

Tenga en cuenta que el operador de concatenación se utiliza para mejorar la legibilidad. El costo de usarlo en comparación con el costo de usar un StringBuilder es mínimo (creo que Jeff Atwood publicó algo sobre este tema).

[También entrada tardía]

Clase contenedora con capacidad de cadena para 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(); } } } 

Ejemplo de uso:

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

Igual que la solución aceptada, pero transfundida a “punto” de syntax LINQ …

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

Combinadas las mejores respuestas para crear una versión de objeto anónimo :

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

Eso genera esto:

key2 = value2 y key1 = value1

Aquí está el código:

 public static class HttpUtility2 { public static string BuildQueryString(T obj) { var queryString = HttpUtility.ParseQueryString(string.Empty); foreach (var property in TypeDescriptor.GetProperties(typeof(T)).Cast()) { var value = (property.GetValue(obj) ?? "").ToString(); queryString.Add(property.Name, value); } return queryString.ToString(); } } 

Tengo un método de extensión para Uri que:

  • Acepta objetos anónimos: uri.WithQuery(new { name = "value" })
  • Acepta colecciones de parejas de string/string (por ejemplo, Dictionary`2 ).
  • Acepta colecciones de pares de string/object (por ejemplo, RouteValueDictionary ).
  • Acepta NameValueCollection s.
  • Ordena los valores de consulta por clave para que los mismos valores produzcan URI iguales.
  • Admite múltiples valores por clave, preservando su orden original.

La versión documentada se puede encontrar aquí .

La extensión:

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

El analizador de consultas:

 private static IEnumerable> 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(key, val); // Check if a string/string dictionary. var ssd = values as IEnumerable>; if (ssd != null) return ssd; // Check if a string/object dictionary. var sod = values as IEnumerable>; if (sod == null) { // Check if a non-generic dictionary. var ngd = values as IDictionary; if (ngd != null) sod = ngd.Cast().ToDictionary( 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 ?? new[] { pair.Value?.ToString() } select new KeyValuePair(pair.Key, val); } 

Aquí están las pruebas:

 var uri = new Uri("https://stackoverflow.com/yo?oldKey=oldValue"); // Test with a string/string dictionary. var q = uri.WithQuery(new Dictionary { ["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 { ["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> { [1] = new HashSet { "v1" }, [2] = new HashSet { "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 { "v3" }, k4 = true, k5 = null as Queue }); 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")); 

Agregué el siguiente método a mi clase PageBase.

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

Llamar:

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

Escribí algunos métodos de extensión que encontré muy útiles cuando trabajo con QueryStrings. A menudo quiero comenzar con el QueryString actual y modificar antes de usarlo. Algo como,

 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 

Para obtener más información y la fuente: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

Es básico, pero me gusta el estilo.

Solo quería arrojar mis 2 centavos:

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

¿Los documentos dicen que uri.Query comenzará con a ? si no está vacío y debes recortarlo si vas a modificarlo.

Tenga en cuenta que HttpUtility.UrlEncode se encuentra en System.Web .

Uso:

 var uri = new Uri("https://api.del.icio.us/v1/posts/suggest").AddQueryParam("url","http://stackoverflow.com") 
 // 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")); } 

Salida:

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

The code; you can all thank me somewhere, somehow 😀

 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 Paths { get; set; } public SortedDictionary QueryPairs { get; set; } public UrlBuilder(string url) { this.Paths = new List(); this.QueryPairs = new SortedDictionary(); 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 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); } } } 

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 QueryParametersDictionary() { var dictionary = new Dictionary(); 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 nvc) { StringBuilder sb = new StringBuilder(); bool first = true; foreach (KeyValuePair pair in nvc) { if (!first) { sb.Append("&"); } sb.AppendFormat("{0}={1}", Uri.EscapeDataString(pair.Key), Uri.EscapeDataString(pair.Value)); first = false; } return sb.ToString(); } 

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

Lo uso así:

 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.

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

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

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 lsParams = new List(); 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 

Works for multiple values per key in NameValueCollection.

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

 ///  /// Get query string for name value collection. ///  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(); } 

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