Come creare una stringa di query per un URL in C #?


473

Un'attività comune quando si chiamano risorse Web da un codice è la creazione di una stringa di query per includere tutti i parametri necessari. Sebbene non ci sia affatto scienza missilistica, ci sono alcuni dettagli ingegnosi di cui devi occuparti di come, aggiungere un &se non il primo parametro, codificare i parametri ecc.

Il codice per farlo è molto semplice, ma un po 'noioso:

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

Questo è un compito così comune che ci si aspetterebbe che esista una classe di utilità che la renda più elegante e leggibile. Scansionando MSDN, non sono riuscito a trovarne uno, il che mi porta alla seguente domanda:

Qual è il modo più elegante e pulito che conosci di fare quanto sopra?


26
È un po 'triste che anche al momento attuale, non sembra esserci un modo semplice per gestire le stringhe di querys. E per semplice intendo una classe quadro OOB, non interna, conforme agli standard. O forse mi sto perdendo qualcosa?
Smorfia di disperazione,

5
Non ti stai perdendo nulla. La costruzione di querystring è un grande divario nel quadro che ho cercato di colmare con Flurl .
Todd Menier,


Mi hai appena fatto pensare che dovrei costruirne uno .. nuovo UrlBuilder (esistente) .AddQuery ("chiave", "valore"). ToString ()
Demetris Leptos il

Risposte:


293

Se guardi sotto il cofano, la proprietà QueryString è NameValueCollection. Quando ho fatto cose simili di solito sono stato interessato a serializzare E deserializzare, quindi il mio suggerimento è quello di costruire un NameValueCollection e poi passare 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);
}

Immagino che ci sia un modo super elegante per farlo anche in LINQ ...


22
La specifica HTTP (RFC 2616) non dice nulla su ciò che le stringhe di query possono contenere. Nemmeno RFC 3986, che definisce il formato URI generico. Il formato di coppia chiave / valore che viene comunemente utilizzato viene chiamato application/x-www-form-urlencodeded è in realtà definito da HTML, allo scopo di inviare i dati del modulo come parte di una GETrichiesta. HTML 5 non proibisce più valori per chiave in questo formato e in effetti richiede che il browser produca più valori per chiave nel caso in cui la pagina (erroneamente) contenga più campi con lo stesso nameattributo. Vedi goo.gl/uk1Ag
Daniel Cassidy,

14
@annakata: So che il mio commento ha più di un anno (e la risposta più di due anni!), ma NameValueCollection supporta moltissimo valori multipli per chiave, usando il metodo GetValues ​​(chiave).
Mauricio Scheffer,

4
@MauricioScheffer: Ma NameValueCollection non si trasforma in una stringa di querys "correttamente". Ad esempio, se si imposta il parametro QueryString su WebClient in cui la stessa chiave è presente più volte, si trasforma in "percorso? Chiave = valore1, valore2" anziché "percorso? Chiave = valore1 & chiave = valore2", che è un comune (standard ?) modello.
David Pope,

8
Per quanto riguarda più valori per chiave, credo che in HTML, se si dispone di una casella di riepilogo a selezione multipla con più elementi selezionati e inviati, questi vengono inviati nel formato a più valori indicato da David.
Sam,

10
Potresti voler usare Uri.EscapeDataString invece di HttpUtility.UrlEncode che è più portabile. Vedi stackoverflow.com/questions/2573290/…
PEK

687

È possibile creare una nuova istanza scrivibile di HttpValueCollectionchiamando System.Web.HttpUtility.ParseQueryString(string.Empty)e quindi utilizzarlo come qualsiasi NameValueCollection. Dopo aver aggiunto i valori desiderati, è possibile chiamare ToStringla raccolta per ottenere una stringa di query, come indicato di seguito:

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

Il HttpValueCollectionè interno e quindi non si può costruire direttamente un'istanza. Tuttavia, una volta ottenuta un'istanza, è possibile utilizzarla come qualsiasi altra NameValueCollection. Poiché l'oggetto reale con cui stai lavorando è un HttpValueCollection, chiamando il metodo ToString chiamerai il metodo overrideHttpValueCollection , che formatta la raccolta come una stringa di query con codifica URL.

Dopo aver cercato SO e il web per una risposta a un problema simile, questa è la soluzione più semplice che ho trovato.

.NET Core

Se lavori in .NET Core, puoi usare Microsoft.AspNetCore.WebUtilities.QueryHelpers classe, il che semplifica notevolmente.

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

Codice di esempio:

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

6
Probabilmente potresti creare un metodo di estensione chiamato ToURLQueryString per l'interfaccia IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker

65
Questo metodo non è conforme agli standard per i caratteri multibyte. Li codificherà come% uXXXX anziché% XX% XX. Le stringhe di query risultanti potrebbero essere interpretate in modo errato dai server Web. Questo è persino documentato nella classe di framework interna HttpValueCollection che viene restituita da HttpUtility.ParseQueryString (). Il commento afferma che mantengono questo comportamento per motivi di retrocompatibilità.
alex,

25
Nota che c'è una differenza importante tra HttpUtilityPraseQueryString ("") e il nuovo NameValueCollection () - solo il risultato HttpUtility avrà la precedenza su ToString () per produrre una corretta querystring
Frank Schwieterman,

7
Che dire dei casi in cui si desidera più istanze di un nome nella stringa di query? Ad esempio, "tipo = 10 e tipo = 21".
Finster,

7
@Finster È possibile aggiungere più istanze di un nome alla stringa di query utilizzando il Addmetodo Vale a dire queryString.Add("type", "1"); queryString.Add("type", "2"); usare il Addmetodo è probabilmente un modo migliore per farlo tutto il tempo in realtà.
jeremysawesome,

94

Con l'ispirazione dal commento di Roy Tinker, ho finito per usare un semplice metodo di estensione sulla classe Uri che mantiene il mio codice conciso e pulito:

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

Modifica: variante conforme agli standard

Come hanno sottolineato diverse persone, httpValueCollection.ToString()codifica i caratteri Unicode in modo non conforme agli standard . Questa è una variante dello stesso metodo di estensione che gestisce tali caratteri invocando il HttpUtility.UrlEncodemetodo anziché il HttpUtility.UrlEncodeUnicodemetodo deprecato .

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

3
Perfetto. Aggiunto alla mia biblioteca interna. :)
Andy,

1
Dovresti anche codificare l'URL del valore. queryString.Add (name, Uri.EscapeDataString (value));
Ufuk Hacıoğulları,

2
Grazie per aver migliorato questa risposta. Ha risolto il problema con i caratteri multibyte.
Ufuk Hacıoğulları,

9
Nota a margine, questo non funziona con gli URL relativi perché non è possibile creare un'istanza di UriBuilder da un Uri relativo.
Yuriy Faktorovich, il

1
Ho aggiunto una chiave di rimozione in modo che non sia possibile aggiungere un duplicato. dotnetfiddle.net/hTlyAd
Paul Totzke

29

Ho risposto a una domanda simile qualche tempo fa. Fondamentalmente, il modo migliore sarebbe usare la classe HttpValueCollection, che Request.QueryStringin realtà è la proprietà di ASP.NET , sfortunatamente è interna al framework .NET. Puoi usare Reflector per afferrarlo (e inserirlo nella tua classe Utils). In questo modo è possibile manipolare la stringa di query come NameValueCollection, ma con tutti i problemi di codifica / decodifica dell'URL risolti.

HttpValueCollectionsi estende NameValueCollectione ha un costruttore che accetta una stringa di query codificata (e commerciali e punti interrogativi inclusi) e sostituisce unToString() metodo per ricostruire successivamente la stringa di query dalla raccolta sottostante.

Esempio:

  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

Grazie ... ho notato che il NameValueCollection che restituisce ha un ToString () che agisce in modo diverso ma non è riuscito a capire il perché.
calebt,

httpValueCollection.ToString()in realtà chiama httpValueCollection.ToString(true)quindi truenon è necessaria l'aggiunta di esplicito.
davi

5
HttpValueCollection è una classe interna, pertanto non è possibile crearne un'istanza.
Ozba,

29

Ecco un modo fluente / lambda-ish come metodo di estensione (che combina concetti nei post precedenti) che supporta più valori per la stessa chiave. La mia preferenza personale è l'estensione rispetto ai wrapper per la capacità di scoperta di altri membri del team per cose come questa. Nota che ci sono polemiche sui metodi di codifica, molti post su Stack Overflow (uno di questi post ) e blogger MSDN (come questo ).

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

modifica: con supporto null, anche se probabilmente dovrai adattarlo alla tua situazione particolare

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

1
Ciò fallisce se uno qualsiasi dei valori è nullo
Josh Noe,

questo è sbagliato genera molte stringhe di query per ogni coppia di valori chiave
Gayan

@GayanRanasinghe: che cosa significa?
Matti Virkkunen,

22

Flurl [divulgazione: sono l'autore] supporta la creazione di stringhe di query tramite oggetti anonimi (tra l'altro):

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 libreria opzionale Flurl.Http ti consente di effettuare chiamate HTTP direttamente dalla stessa catena di chiamate fluente, estendendola in un client 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>();

Il pacchetto completo è disponibile su NuGet:

PM> Install-Package Flurl.Http

o solo il generatore di URL autonomo:

PM> Install-Package Flurl


20

Ecco la mia entrata in ritardo. Non mi è piaciuto nessuno degli altri per vari motivi, quindi ho scritto il mio.

Questa versione include:

  • Uso solo di StringBuilder. Nessuna chiamata ToArray () o altri metodi di estensione. Non sembra carino come alcune delle altre risposte, ma considero questa una funzione fondamentale, quindi l'efficienza è più importante che avere un codice "fluente", "one-liner" che nasconda le inefficienze.

  • Gestisce più valori per chiave. (Non ne avevo bisogno da solo, ma solo per mettere a tacere 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();
    }

Esempio di utilizzo

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

Produzione

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

Mi piace che questo non usi HttpUtility che si trova in System.Web e non è disponibile ovunque.
Kugel,

+1 per non usare linq e non usare HttpUtility. Creerei un sb vuoto e abbandonerei la variabile "bool first" e poi nel loop avrei semplicemente sb.Append (sb.Length == 0? "?": "&") Prima di sb.AppendFormat (). Ora, se nvc è vuoto, il metodo restituirà una stringa vuota anziché un "?" Solitario.
Mathew Leger,

Questa risposta gestisce singoli parametri con più valori. ad es.? id = 1 & id = 3 & id = 2 & id = 9
Mathemats

12

Che ne dici di creare metodi di estensione che ti permettano di aggiungere i parametri in uno stile fluente come questo?

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

Ecco il sovraccarico che utilizza 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);
}

Ed ecco il sovraccarico che 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));
}

: +1: per il semplice metodo di estensione basato su String. Alcune delle altre risposte possono riguardare casi limite, ma questo è sufficiente per la mia situazione, e non richiede me per costruire una NameValueCollection, HttpValueCollectiono una Uriprima. Grazie!
Stanley G.

11

Avevo bisogno di risolvere lo stesso problema per una libreria di classi portatile (PCL) su cui sto lavorando. In questo caso, non ho accesso a System.Web, quindi non posso usare ParseQueryString.

Invece ho usato System.Net.Http.FormUrlEncodedContentcosì:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;

Questa è la tecnica che uso e la ho citata in un'altra domanda http://stackoverflow.com/a/26744471/2108310 L'unica differenza è che utilizzo una matrice di coppie KeyValue ... oltre a non aver bisogno del riferimento a System. Net (che è disponibile PCL come hai affermato) questo è IMHO il modo più semplice per farlo senza includere un pacchetto di terze parti o cercare di mettere insieme qualche pasticcio di spaghetti homebrew.
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());
    }

1
Bello! Ma non hai bisogno della .ToArray()s.
mpen

7

Aggiungi questa classe al tuo progetto

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

E usalo in questo modo:

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"

Ora, questa dovrebbe essere la risposta accettata; funziona perfettamente per array come "foo [] = 1, foo [] = 2" e mantiene l'ordine dei parametri che è molto importante tra l'altro.
Soroush Falahati,

6

La mia offerta:

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

Preferisco il tuo approccio Semplice ed elegante, tuttavia HttpUtility richiede System.Web
Ody

5

Non testato, ma penso che qualcosa del genere funzionerebbe abbastanza bene

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

ben incapsulato ma quel formato su "? {0}" è un po 'inutilmente costoso :)
annakata,

ha cambiato il nome della classe in QueryString .. La query è un po 'ambigua
Nick Allen,

4

Una versione basata sul metodo di estensione rapida:

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

È possibile utilizzare una clausola where per selezionare i parametri da aggiungere alla stringa.


3

Supponendo che si desideri ridurre le dipendenze da altri assiemi e mantenere le cose semplici, è possibile fare:

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

Funziona bene anche con i loop. La rimozione finale della e commerciale deve andare fuori dal ciclo.

Si noti che l'operatore di concatenazione viene utilizzato per migliorare la leggibilità. Il costo dell'utilizzo rispetto al costo dell'utilizzo di StringBuilder è minimo (penso che Jeff Atwood abbia pubblicato qualcosa su questo argomento).


3

Combina le risposte migliori per creare una versione di oggetto anonimo :

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

Questo genera questo:

chiave2 = valore2 & chiave1 = valore1

Ecco il codice:

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

2

Come la soluzione accettata, ma trasferita nella sintassi 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;
}

2

Ho un metodo di estensione per Uri che:

  • Accetta oggetti anonimi: uri.WithQuery(new { name = "value" })
  • Accetta raccolte di string/stringcoppie (ad es. Dizionario`2 ).
  • Accetta raccolte di string/objectcoppie (ad esempio RouteValueDictionary ).
  • Accetta NameValueCollection s.
  • Ordina i valori della query in base alla chiave in modo che gli stessi valori producano URI uguali.
  • Supporta più valori per chiave, preservandone l'ordine originale.

La versione documentata è disponibile qui .

L'estensione:

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

Il parser di query:

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

Ecco i test:

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

2

Classe wrapper in grado di eseguire la catena per 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();
        }
    }
}

Esempio di utilizzo:

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

1

Ho aggiunto il seguente metodo alla mia classe 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());
    }

Chiamare:

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

1

Ho scritto alcuni metodi di estensione che ho trovato molto utili lavorando con QueryStrings. Spesso voglio iniziare con l'attuale QueryString e modificarlo prima di usarlo. Qualcosa di simile a,

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

Per di più e la fonte: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

È semplice, ma mi piace lo stile.


1

Volevo solo buttare nei miei 2 centesimi:

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

I documenti dicono che uri.Queryinizierà con un ?se non è vuoto e dovresti tagliarlo se lo modificherai.

Si noti che HttpUtility.UrlEncodesi trova in System.Web.

Uso:

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

1

Anche se non elegante, ho optato per una versione più semplice che non utilizza NameValueCollecitons- solo un modello di generatore avvolto 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 le risposte esistenti, mi sono assicurato di utilizzare le HttpUtility.UrlEncodechiamate. È usato così:

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

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

Produzione:

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

Il codice; puoi ringraziarmi da qualche parte, in qualche modo: 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);
        }
    }
}

1

Sono andato con la soluzione proposta da DSO (risposta il 2 agosto 11 alle 7:29), la sua soluzione non richiede l'utilizzo di HttpUtility. Tuttavia, come da un articolo pubblicato in Dotnetpearls , l'utilizzo di un dizionario è più rapido (in termini di prestazioni) rispetto all'utilizzo di NameValueCollection. Ecco la soluzione di DSO modificata per utilizzare il Dizionario al posto di 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();
    }

1

La stringa di query può essere aggiunta a un URL tramite:

  1. creare un oggetto raccolta valore nome
  2. aggiungere gli elementi della stringa di query e i loro valori a questo oggetto
  3. codificare questo oggetto di raccolta valore nome nell'URL il codice è fornito nel collegamento seguente

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

0

Ho scritto un aiuto per il mio progetto di rasoio usando alcuni dei suggerimenti di altre risposte.

Il business ParseQueryString è necessario perché non ci è permesso manomettere l'oggetto QueryString della richiesta corrente.

@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 così:

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

Se vuoi che prenda più di un valore, modifica i parametri in un dizionario e aggiungi le coppie alla stringa della query.


0

Il codice seguente viene rimosso HttpValueCollectiondall'implementazione di ToString, tramite ILSpy, che fornisce un querystring name = value.

Sfortunatamente HttpValueCollection è una classe interna che puoi recuperare solo se lo usi HttpUtility.ParseQueryString(). Ho rimosso tutte le parti viewstate e codifica per impostazione predefinita:

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

0

Questa è identica alla risposta accettata tranne leggermente più compatta:

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

0

Solo per coloro che necessitano della versione VB.NET della risposta principale:

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

E la versione senza 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

E la versione C # senza 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.