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

473

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 de cohetes, hay algunos detalles ingeniosos que debe tener en cuenta, como agregar un &primer parámetro, codificar 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 cabría esperar que exista 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 limpia más elegante que sabes de hacer lo anterior?

Booz
fuente
26
Es un poco triste que incluso en el momento actual, no parece haber una forma directa de lidiar con las cadenas de consulta. Y por directo, me refiero a una clase de marco OOB, no interna, que cumple con los estándares. ¿O tal vez me estoy perdiendo algo?
Mueca de desesperación del
55
No te estás perdiendo nada. El edificio de Querystring es una brecha importante en el marco que he intentado llenar con Flurl .
Todd Menier
Acabas de hacerme pensar que debería construir uno ... nuevo UrlBuilder (existente). AddQuery ("key", "value"). ToString ()
Demetris Leptos

Respuestas:

293

Si observa debajo del capó, la propiedad QueryString es una NameValueCollection. Cuando he hecho cosas similares, generalmente me ha interesado serializar y deserializar, así que mi sugerencia es crear una NameValueCollection y luego pasar a:

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

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

annakata
fuente
22
La especificación HTTP (RFC 2616) no dice nada sobre qué cadenas de consulta pueden contener. Tampoco RFC 3986, que define el formato genérico de URI. El formato de par clave / valor que se usa comúnmente se llamaapplication/x-www-form-urlencoded , y en realidad está definido por HTML, con el fin de enviar datos de formulario como parte de una GETsolicitud. HTML 5 no prohíbe múltiples valores por clave en este formato, y de hecho requiere que el navegador produzca múltiples valores por clave en caso de que la página (incorrectamente) contenga múltiples campos con el mismo nameatributo. Ver goo.gl/uk1Ag
Daniel Cassidy
14
@annakata: Sé que mi comentario tiene más de un año (¡y la respuesta tiene más de dos años!), pero NameValueCollection admite muchos valores por clave, utilizando el método GetValues ​​(clave).
Mauricio Scheffer
44
@MauricioScheffer: Pero NameValueCollection no se transforma en una cadena de consulta "correctamente". Por ejemplo, si configura el parámetro QueryString en WebClient donde la misma clave está presente varias veces, se convierte en "ruta? Clave = valor1, valor2" en lugar de "ruta? Clave = valor1 & clave = valor2", que es común (estándar ?) patrón.
David Pope el
8
Con respecto a los valores múltiples por clave, creo que en HTML, si tiene un cuadro de lista de selección múltiple con elementos múltiples seleccionados y enviados, se envían en el formato de valor múltiple mencionado por David.
Sam
10
Es posible que desee utilizar Uri.EscapeDataString en lugar de HttpUtility.UrlEncode, que es más portátil. Ver stackoverflow.com/questions/2573290/…
PEK
688

Puede crear una nueva instancia de escritura HttpValueCollectional llamar System.Web.HttpUtility.ParseQueryString(string.Empty)y luego usarla como cualquiera NameValueCollection. Una vez que haya agregado los valores que desea, puede llamar ToStringa la colección para obtener una cadena de consulta, de la siguiente manera:

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

El HttpValueCollectiones 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. Como el objeto real con el que está trabajando es un HttpValueCollection, llamar al método ToString llamará al método anulado enHttpValueCollection , que formatea la colección como una cadena de consulta codificada en URL.

Después de buscar en SO y en la web 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 Microsoft.AspNetCore.WebUtilities.QueryHelpersclase, que simplifica esto en gran medida.

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

Código de muestra:

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));
John Bledsoe
fuente
66
Probablemente podría crear un método de extensión llamado ToURLQueryString para la interfaz IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker
65
Este método no cumple con el estándar para caracteres multibyte. Los codificará como% uXXXX en lugar de% XX% XX. Las cadenas de consulta resultantes pueden ser interpretadas incorrectamente por los servidores web. Esto incluso se documenta en la clase de marco interno HttpValueCollection que devuelve HttpUtility.ParseQueryString (). El comentario dice que mantienen este comportamiento por razones de compatibilidad con versiones anteriores.
alex
25
Tenga en cuenta que hay una diferencia importante entre HttpUtilityPraseQueryString ("") y el nuevo NameValueCollection (): solo el resultado de HttpUtility anulará ToString () para producir una cadena de consulta adecuada
Frank Schwieterman
77
¿Qué sucede con los casos en los que desea varias instancias de un nombre en la cadena de consulta? Por ejemplo, "type = 10 & type = 21".
Finster
77
@Finster Puede agregar varias instancias de un nombre a la cadena de consulta utilizando el Addmétodo Es decir, queryString.Add("type", "1"); queryString.Add("type", "2"); usar el Addmétodo es probablemente una mejor manera de hacerlo todo el tiempo en realidad.
jeremysawesome
94

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 compatible con los estándares

Como señalaron varias personas, httpValueCollection.ToString()codifica los 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 HttpUtility.UrlEncodemétodo en lugar del método en desuso HttpUtility.UrlEncodeUnicode.

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
fuente
3
Perfecto. Agregado a mi biblioteca interna. :)
Andy
1
También debe codificar la URL del valor. queryString.Add (nombre, Uri.EscapeDataString (valor));
Ufuk Hacıoğulları
2
Gracias por mejorar esta respuesta. Se solucionó el problema con los caracteres multibyte.
Ufuk Hacıoğulları
99
Nota al margen, esto no funciona con las URL relativas porque no puede crear una instancia del UriBuilder de un Uri relativo.
Yuriy Faktorovich
1
Agregué una clave de eliminación para que no se pueda agregar un duplicado. dotnetfiddle.net/hTlyAd
Paul Totzke
29

Respondí una pregunta similar hace un tiempo. Básicamente, la mejor manera sería usar la clase HttpValueCollection, que ASP.NETRequest.QueryString realidad es propiedad , desafortunadamente es interna en el marco .NET. Puede usar Reflector para agarrarlo (y colocarlo en su clase de Utils). De esta forma, podría manipular la cadena de consulta como una NameValueCollection, pero con todos los problemas de codificación / decodificación de URL atendidos.

HttpValueCollectionse extiende NameValueCollectiony tiene un constructor que toma una cadena de consulta codificada (símbolos y signos de interrogación incluidos), y anula unToString() método 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
Igal Tabachnik
fuente
Gracias ... me di cuenta de que NameValueCollection que devuelve tiene un ToString () que actúa de manera diferente pero no puede entender por qué.
calebt
httpValueCollection.ToString()en realidad llama httpValueCollection.ToString(true)así que trueno se requiere agregar la explicidad.
dav_i
55
HttpValueCollection es una clase interna, por lo tanto, no puede crear una instancia.
ozba
29

Aquí hay una forma fluida / lambda-ish como método de extensión (combinando conceptos en publicaciones anteriores) que admite múltiples valores para la misma clave. Mi preferencia personal son las extensiones sobre los contenedores para la capacidad de descubrimiento por parte de otros miembros del equipo para cosas como esta. Tenga en cuenta que existe una controversia sobre los métodos de codificación, muchas publicaciones al respecto en Stack Overflow (una de esas publicaciones ) y bloggers de MSDN (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());
}

editar: con soporte nulo, aunque probablemente deba adaptarlo para su 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;
}
Alfredo
fuente
1
Esto falla si alguno de los valores es nulo
Josh Noe
esto está mal, genera muchas cadenas de consulta para cada par de valores clave
Gayan
@GayanRanasinghe: ¿Qué significa eso?
Matti Virkkunen
22

Flurl [divulgació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!"
});

La biblioteca complementaria Flurl.Http opcional le permite hacer llamadas HTTP directamente desde la misma cadena de llamadas fluida, extendiéndola a un cliente REST completo:

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

El paquete completo está disponible en NuGet:

PM> Install-Package Flurl.Http

o solo el creador de URL independiente:

PM> Install-Package Flurl

Todd Menier
fuente
20

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

Esta versión presenta:

  • Uso de StringBuilder solamente. No hay llamadas ToArray () u otros métodos de extensión. No se ve tan bonito como algunas de las otras respuestas, pero considero que esta 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 oculta las ineficiencias.

  • Maneja múltiples valores por clave. (No lo necesitaba yo solo, pero 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
DSO
fuente
Me gusta que esto no use HttpUtility, que está bajo System.Web y no está disponible en todas partes.
Kugel
+1 por no usar linq y no usar HttpUtility. Crearía un sb vacío y abandonaría la variable "bool first" y luego en el bucle simplemente tendría sb.Append (sb.Length == 0? "?": "&") Antes de sb.AppendFormat (). Ahora, si nvc está vacío, el método devolverá una cadena vacía en lugar de un solitario "?".
Mathew Leger
Esta respuesta maneja parámetros únicos con múltiples valores. por ejemplo? id = 1 & id = 3 & id = 2 & id = 9
Mathemats
12

¿Qué tal crear métodos de extensión que le permitan 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 un 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));
}
LukeH
fuente
: +1: para el método de extensión simple basado en cadenas. Algunas de las otras respuestas pueden cubrir más casos extremos, pero esto es suficiente para mi situación, y que no requiere de mí para construir una NameValueCollection, HttpValueCollectiono una Uriprimera. ¡Gracias!
Stanley G.
11

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, usé System.Net.Http.FormUrlEncodedContentasí:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
Hortman
fuente
Esta es la técnica que uso, y la he mencionado en otra pregunta http://stackoverflow.com/a/26744471/2108310 La única diferencia es que uso una matriz de pares KeyValue ... además de necesitar la referencia al Sistema. Net (que es PCL disponible como usted dijo), esta es la manera más simple de hacerlo en mi humilde opinión sin incluir algún paquete de terceros, o tratando de coquetear un poco de desorden casero de espagueti.
Rostov
9
    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
fuente
1
¡Agradable! Pero no necesitas el .ToArray()s.
mpen
7

Agrega esta clase a tu proyecto

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

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"
Gian Marco Gherardi
fuente
Ahora, esta debería ser la respuesta aceptada; funciona perfectamente para matrices como "foo [] = 1, foo [] = 2" y también mantiene el orden de los parámetros, lo cual es muy importante por cierto.
Soroush Falahati
6

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
dav_i
fuente
Prefiero su enfoque simple y elegante, sin embargo, HttpUtility requiere System.Web
Ody
5

No probado, pero creo que algo en este sentido funcionaría bastante bien

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
fuente
muy bien encapsulados, pero ese formato en es tipo de innecesariamente caro :) "{0}?"
annakata
cambió el nombre de la clase a QueryString .. La consulta es un poco ambigua
Nick Allen
4

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

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

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

Martin Harris
fuente
3

Suponiendo que desea reducir las dependencias a otros ensamblados 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 final de ampersand debe salir del ciclo.

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

Thomas Bratt
fuente
3

Combinó las respuestas principales para crear una versión de objeto anónimo :

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

Eso genera esto:

clave2 = valor2 y clave1 = valor1

Aquí está el código:

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
fuente
2

Igual que la solución aceptada, pero transferida a la sintaxis LINQ "punto" ...

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
fuente
2

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

  • Acepta objetos anónimos: uri.WithQuery(new { name = "value" })
  • Acepta colecciones de string/stringpares (por ejemplo, Diccionario`2 ).
  • Acepta colecciones de string/objectpares (por ejemplo, RouteValueDictionary ).
  • Acepta NameValueCollection s.
  • Ordena los valores de la consulta por clave para que los mismos valores produzcan URI iguales.
  • Admite múltiples valores por clave, conservando 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<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);
}

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<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
fuente
2

Clase de contenedor 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();
kroehre
fuente
1

Agregué el siguiente método a mi clase de 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);
Mike Cole
fuente
1

Escribí algunos métodos de extensión que he encontrado muy útiles al trabajar con QueryStrings. A menudo quiero comenzar con el QueryString actual y modificarlo 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 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.

ccook
fuente
1

Solo quería tirar 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<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))));
    }
}

Los documentos dicen que uri.Querycomenzará con un ?si no está vacío y debe recortarlo si va a modificarlo.

Tenga en cuenta que HttpUtility.UrlEncodese encuentra en System.Web.

Uso:

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

Si bien no es elegante, opté por una versión más simple que no usa NameValueCollecitons, solo un patrón de construcción envuelto 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
}

Según las respuestas existentes, me aseguré de usar HttpUtility.UrlEncodellamadas. Se usa así:

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

Salida:

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

El código; todos ustedes pueden agradecerme en alguna parte, de alguna manera: 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
fuente
1

Fui con la solución propuesta por DSO (respondida el 2 de agosto de 11 a las 7:29), su solución no requiere el uso de HttpUtility. Sin embargo, según un artículo publicado en Dotnetpearls , usar un Diccionario es más rápido (en rendimiento) que usar NameValueCollection. Aquí está la solución de DSO modificada para usar Dictionary en lugar de 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
fuente
1

La cadena de consulta se puede agregar a una URL mediante:

  1. crear un objeto de colección de valor de nombre
  2. agregue los elementos de cadena de consulta y sus valores a este objeto
  3. codifique este objeto de colección de valor de nombre a la url, el código se proporciona en el siguiente enlace

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);
}
trabajo
fuente
0

Escribí un ayudante para mi proyecto de maquinilla de afeitar utilizando algunas de las sugerencias de otras respuestas.

El negocio ParseQueryString es necesario porque no se nos permite alterar el objeto QueryString de la solicitud actual.

@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 = '[email protected]("var-name", "var-value")';

Si desea que tome más de un valor, simplemente cambie los parámetros a un Diccionario y agregue los pares a la cadena de consulta.

LOAS
fuente
0

El siguiente código se elimina de la HttpValueCollectionimplementación de ToString, a través de ILSpy, que le da una cadena de consulta de nombre = valor.

Lamentablemente, HttpValueCollection es una clase interna que solo volverá a obtener si la usa HttpUtility.ParseQueryString(). Eliminé todas las partes del estado de vista y codifica de manera predeterminada:

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
fuente
0

Esta es la respuesta idéntica a la aceptada, excepto un poco más compacta:

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

Solo para aquellos que necesitan la versión VB.NET de la respuesta superior:

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

Y la versión sin 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

Y la versión C # sin 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
fuente