Comment construire une chaîne de requête pour une URL en C #?

473

Une tâche courante lors de l'appel de ressources Web à partir d'un code consiste à créer une chaîne de requête pour inclure tous les paramètres nécessaires. Bien que ce ne soit absolument pas une science de fusée, il y a quelques détails astucieux dont vous devez vous occuper, comme l'ajout d'un &sinon le premier paramètre, l'encodage des paramètres, etc.

Le code pour le faire est très simple, mais un peu fastidieux:

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

C'est une tâche si courante que l'on s'attendrait à ce qu'il existe une classe d'utilité qui la rende plus élégante et lisible. En scannant MSDN, je n'ai pas réussi à en trouver un, ce qui m'amène à la question suivante:

Quelle est la façon la plus propre et la plus élégante de faire ce qui précède?

Boaz
la source
26
Il est un peu triste de constater que, même à l'heure actuelle, il ne semble pas y avoir de moyen simple de traiter les chaînes de querystrings. Et par simple, je veux dire une classe de cadre OOB, non interne, conforme aux normes. Ou peut-être que je manque quelque chose?
Grimace of Despair
5
Vous ne manquez de rien. La construction de Querystring est une lacune majeure dans le cadre que j'ai essayé de combler avec Flurl .
Todd Menier
Vous venez de me faire penser que je devrais en construire un .. nouveau UrlBuilder (existant) .AddQuery ("clé", "valeur"). ToString ()
Demetris Leptos

Réponses:

293

Si vous regardez sous le capot, la propriété QueryString est un NameValueCollection. Quand j'ai fait des choses similaires, j'ai généralement été intéressé par la sérialisation ET la désérialisation, donc ma suggestion est de construire un NameValueCollection et de passer ensuite à:

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

J'imagine qu'il y a une manière super élégante de faire ça dans LINQ aussi ...

annakata
la source
22
La spécification HTTP (RFC 2616) ne dit rien sur ce que les chaînes de requête peuvent contenir. La RFC 3986 non plus, qui définit le format URI générique. Le format de paire clé / valeur couramment utilisé est appelé application/x-www-form-urlencodedet est en fait défini par HTML, dans le but de soumettre des données de formulaire dans le cadre d'une GETdemande. HTML 5 n'interdit pas plusieurs valeurs par clé dans ce format, et en fait il nécessite que le navigateur produise plusieurs valeurs par clé dans le cas où la page contient (incorrectement) plusieurs champs avec le même nameattribut. Voir goo.gl/uk1Ag
Daniel Cassidy
14
@annakata: Je sais que mon commentaire remonte à plus d'un an (et la réponse à plus de deux ans!), mais NameValueCollection prend très en charge plusieurs valeurs par clé, en utilisant la méthode GetValues ​​(clé).
Mauricio Scheffer
4
@MauricioScheffer: Mais NameValueCollection ne se transforme pas en chaîne de requête "correctement". Par exemple, si vous définissez le paramètre QueryString sur WebClient où la même clé est présente plusieurs fois, il se transforme en «chemin? Clé = valeur1, valeur2» au lieu de «chemin? Clé = valeur1 & clé = valeur2», ce qui est courant (standard ?) modèle.
David Pope
8
En ce qui concerne plusieurs valeurs par clé, je pense qu'en HTML, si vous avez une zone de liste à sélection multiple avec plusieurs éléments sélectionnés et soumis, ils sont envoyés dans le format à valeurs multiples mentionné par David.
Sam
10
Vous voudrez peut-être utiliser Uri.EscapeDataString au lieu de HttpUtility.UrlEncode qui est plus portable. Voir stackoverflow.com/questions/2573290/…
PEK
688

Vous pouvez créer une nouvelle instance accessible en écriture de HttpValueCollectionen appelant System.Web.HttpUtility.ParseQueryString(string.Empty), puis l'utiliser comme n'importe quel autre NameValueCollection. Une fois que vous avez ajouté les valeurs souhaitées, vous pouvez appeler ToStringla collection pour obtenir une chaîne de requête, comme suit:

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

Le HttpValueCollectionest interne et vous ne pouvez donc pas construire directement une instance. Cependant, une fois que vous avez obtenu une instance, vous pouvez l'utiliser comme une autre NameValueCollection. Étant donné que l'objet réel avec lequel vous travaillez est un HttpValueCollection, l'appel à la méthode ToString appellera la méthode remplacée HttpValueCollection, qui formate la collection en tant que chaîne de requête codée URL.

Après avoir recherché SO et le Web pour une réponse à un problème similaire, c'est la solution la plus simple que j'ai pu trouver.

.NET Core

Si vous travaillez dans .NET Core, vous pouvez utiliser la Microsoft.AspNetCore.WebUtilities.QueryHelpersclasse, ce qui simplifie considérablement cela.

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

Exemple de code:

const string url = "https://customer-information.azure-api.net/customers/search/taxnbr";
var param = new Dictionary<string, string>() { { "CIKey", "123456789" } };

var newUrl = new Uri(QueryHelpers.AddQueryString(url, param));
John Bledsoe
la source
6
Vous pourriez probablement créer une méthode d'extension appelée ToURLQueryString pour l'interface IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker
65
Cette méthode n'est pas conforme aux normes pour les caractères multi - octets. Il les codera en% uXXXX au lieu de% XX% XX. Les chaînes de requête résultantes peuvent être mal interprétées par les serveurs Web. Cela est même documenté dans la classe de structure interne HttpValueCollection qui est retournée par HttpUtility.ParseQueryString (). Le commentaire indique qu'ils conservent ce comportement pour des raisons de compatibilité descendante.
alex
25
Notez qu'il existe une différence importante entre HttpUtilityPraseQueryString ("") et le nouveau NameValueCollection () - seul le résultat HttpUtility remplacera ToString () pour produire une chaîne de requête appropriée
Frank Schwieterman
7
Qu'en est-il des cas où vous souhaitez plusieurs instances d'un nom dans la chaîne de requête? Par exemple, "type = 10 & type = 21".
Finster
7
@Finster Vous pouvez ajouter plusieurs instances d'un nom à la chaîne de requête à l'aide de la Addméthode. C'est-à-dire que l' queryString.Add("type", "1"); queryString.Add("type", "2"); utilisation de la Addméthode est probablement une meilleure façon de le faire tout le temps en fait.
jeremysawesome
94

Avec l'inspiration du commentaire de Roy Tinker, j'ai fini par utiliser une méthode d'extension simple sur la classe Uri qui garde mon code concis et propre:

using System.Web;

public static class HttpExtensions
{
    public static Uri AddQuery(this Uri uri, string name, string value)
    {
        var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

        httpValueCollection.Remove(name);
        httpValueCollection.Add(name, value);

        var ub = new UriBuilder(uri);
        ub.Query = httpValueCollection.ToString();

        return ub.Uri;
    }
}

Usage:

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

Modifier - Variante conforme aux normes

Comme plusieurs personnes l'ont souligné, httpValueCollection.ToString()code les caractères Unicode d'une manière non conforme aux normes . Il s'agit d'une variante de la même méthode d'extension qui gère ces caractères en appelantHttpUtility.UrlEncode méthode au lieu de la HttpUtility.UrlEncodeUnicodeméthode obsolète .

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
la source
3
Parfait. Ajouté à ma bibliothèque interne. :)
Andy
1
Vous devez également coder l'URL de la valeur. queryString.Add (nom, Uri.EscapeDataString (valeur));
Ufuk Hacıoğulları
2
Merci d'avoir amélioré cette réponse, cela a résolu le problème des caractères multi-octets.
Ufuk Hacıoğulları
9
Remarque: cela ne fonctionne pas avec les URL relatives, car vous ne pouvez pas instancier UriBuilder à partir d'un Uri relatif.
Yuriy Faktorovich
1
J'ai ajouté une clé de suppression afin qu'aucun doublon ne puisse être ajouté. dotnetfiddle.net/hTlyAd
Paul Totzke
29

J'ai répondu à une question similaire il y a quelque temps. Fondamentalement, la meilleure façon serait d'utiliser la classe HttpValueCollection, qui est ASP.NETRequest.QueryString est réellement la propriété , malheureusement elle est interne dans le framework .NET. Vous pouvez utiliser Reflector pour l'attraper (et le placer dans votre classe Utils). De cette façon, vous pouvez manipuler la chaîne de requête comme une NameValueCollection, mais avec tous les problèmes de codage / décodage d'URL pris en charge pour vous.

HttpValueCollectionétend NameValueCollection, et a un constructeur qui prend une chaîne de requête codée (esperluette et points d'interrogation inclus), et il remplace unToString() méthode pour reconstruire ultérieurement la chaîne de requête à partir de la collection sous-jacente.

Exemple:

  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
la source
Merci ... j'ai remarqué que le NameValueCollection qu'il renvoie a un ToString () qui agit différemment mais n'a pas pu comprendre pourquoi.
calebt
httpValueCollection.ToString()appelle en fait, httpValueCollection.ToString(true)il truen'est donc pas nécessaire d' ajouter l' explicité.
dav_i
5
HttpValueCollection est une classe interne, vous ne pouvez donc pas l'instancier.
ozba
29

Voici un moyen fluide / lambda-ish comme méthode d'extension (combinant les concepts dans les articles précédents) qui prend en charge plusieurs valeurs pour la même clé. Ma préférence personnelle est les extensions sur les wrappers pour que les autres membres de l'équipe puissent les découvrir pour des choses comme ça. Notez qu'il ya une controverse autour de méthodes de codage, beaucoup de messages à ce sujet sur le débordement de pile (un tel poste ) et les blogueurs MSDN (comme celui - ci ).

public static string ToQueryString(this NameValueCollection source)
{
    return String.Join("&", source.AllKeys
        .SelectMany(key => source.GetValues(key)
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))))
        .ToArray());
}

edit: avec un support nul, mais vous devrez probablement l'adapter à votre situation particulière

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;
}
Alfred
la source
1
Cela échoue si l'une des valeurs est nulle
Josh Noe
c'est faux, il génère de nombreuses chaînes de requête pour chaque paire de valeurs clés
Gayan
@GayanRanasinghe: Qu'est-ce que cela signifie même?
Matti Virkkunen
22

Flurl [divulgation: je suis l'auteur] prend en charge la création de chaînes de requête via des objets anonymes (entre autres):

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 bibliothèque complémentaire Flurl.Http en option vous permet d'effectuer des appels HTTP directement à partir de la même chaîne d'appels fluide, en l'étendant dans un client REST complet:

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

Le package complet est disponible sur NuGet:

PM> Install-Package Flurl.Http

ou simplement le générateur d'URL autonome:

PM> Install-Package Flurl

Todd Menier
la source
20

Voici mon entrée tardive. Je n'aimais aucun des autres pour diverses raisons, alors j'ai écrit le mien.

Cette version comprend:

  • Utilisation de StringBuilder uniquement. Aucun appel ToArray () ou autres méthodes d'extension. Cela ne semble pas aussi joli que certaines des autres réponses, mais je considère que c'est une fonction de base, donc l'efficacité est plus importante que d'avoir un code "fluide", "à une ligne" qui cache les inefficacités.

  • Gère plusieurs valeurs par clé. (Je n'en avais pas besoin moi-même mais juste pour faire taire 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();
    }

Exemple d'utilisation

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

Production

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26
DSO
la source
J'aime que cela n'utilise pas HttpUtility qui est sous System.Web et non disponible partout.
Kugel
+1 pour ne pas utiliser linq et ne pas utiliser HttpUtility. Je créerais un sb vide et j'abandonnerais la variable "bool first", puis dans la boucle j'aurais simplement sb.Append (sb.Length == 0? "?": "&") Avant le sb.AppendFormat (). Maintenant, si nvc est vide, la méthode retournera une chaîne vide au lieu d'un "?" Solitaire.
Mathew Leger
Cette réponse gère des paramètres uniques avec plusieurs valeurs. par exemple? id = 1 & id = 3 & id = 2 & id = 9
Mathemats
12

Que diriez-vous de créer des méthodes d'extension qui vous permettent d'ajouter les paramètres dans un style fluide comme celui-ci?

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

Voici la surcharge qui utilise 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);
}

Et voici la surcharge qui utilise 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
la source
: +1: pour la méthode d'extension basée sur des chaînes simples. Certains des autres réponses peuvent couvrir plus de cas de pointe, mais cela suffit pour ma situation, et il ne me demande pas de construire un NameValueCollection, HttpValueCollectionou une Uripremière. Merci!
Stanley G.
11

J'avais besoin de résoudre le même problème pour une bibliothèque de classes portable (PCL) sur laquelle je travaille. Dans ce cas, je n'ai pas accès à System.Web donc je ne peux pas utiliser ParseQueryString.

Au lieu de cela, j'ai utilisé System.Net.Http.FormUrlEncodedContentcomme ça:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
hortman
la source
C'est la technique que j'utilise et je l'ai référencée dans une autre question http://stackoverflow.com/a/26744471/2108310 La seule différence est que j'utilise un tableau de paires KeyValue ... autre que d'avoir besoin de la référence au système. Net (qui est disponible PCL comme vous l'avez dit), c'est à mon humble avis le moyen le plus simple de le faire sans inclure un paquet tiers ou essayer de clopiner ensemble un désordre de 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());
    }
Jay Douglass
la source
1
Agréable! Mais vous n'avez pas besoin du .ToArray()s.
mpen
7

Ajoutez cette classe à votre projet

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

Et utilisez-le comme ceci:

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
la source
Maintenant, cela devrait être la réponse acceptée; fonctionne parfaitement pour les tableaux comme "foo [] = 1, foo [] = 2" ainsi que de garder l'ordre des paramètres qui est très important en passant.
Soroush Falahati
6

Mon offre:

public static Uri AddQuery(this Uri uri, string name, string value)
{
    // this actually returns HttpValueCollection : NameValueCollection
    // which uses unicode compliant encoding on ToString()
    var query = HttpUtility.ParseQueryString(uri.Query);

    query.Add(name, value);

    var uriBuilder = new UriBuilder(uri)
    {
        Query = query.ToString()
    };

    return uriBuilder.Uri;
}

Usage:

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

// http://stackoverflow.com?such=method&wow=soFluent
dav_i
la source
Je préfère votre approche simple et élégante, cependant, HttpUtility nécessite System.Web
Ody
5

Non testé, mais je pense que quelque chose dans ce sens fonctionnerait très 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
la source
bien encapsulé mais ce format sur "? {0}" est en quelque sorte inutilement cher :)
annakata
a changé le nom de la classe en QueryString .. La requête est un peu ambiguë
Nick Allen
4

Une version basée sur une méthode d'extension rapide:

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

Vous pouvez utiliser une clause where pour sélectionner les paramètres à ajouter à la chaîne.

Martin Harris
la source
3

En supposant que vous souhaitiez réduire les dépendances à d'autres assemblys et garder les choses simples, vous pouvez faire:

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

Cela fonctionne bien aussi avec les boucles. La suppression définitive de l'esperluette doit sortir de la boucle.

Notez que l'opérateur de concaténation est utilisé pour améliorer la lisibilité. Le coût de son utilisation par rapport au coût d'utilisation d'un StringBuilder est minime (je pense que Jeff Atwood a publié quelque chose sur ce sujet).

Thomas Bratt
la source
3

Combiné les meilleures réponses pour créer une version d'objet anonyme :

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

Cela génère ceci:

clé2 = valeur2 & clé1 = valeur1

Voici le code:

public static class HttpUtility2
{
    public static string BuildQueryString<T>(T obj)
    {
        var queryString = HttpUtility.ParseQueryString(string.Empty);

        foreach (var property in TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>())
        {
            var value = (property.GetValue(obj) ?? "").ToString();
            queryString.Add(property.Name, value);
        }

        return queryString.ToString();
    }
}
Luis Perez
la source
2

Identique à la solution acceptée, mais transfred en syntaxe LINQ "dot" ...

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
la source
2

J'ai une méthode d'extension pour Uri qui:

  • Accepte les objets anonymes: uri.WithQuery(new { name = "value" })
  • Accepte les collections de string/stringpaires (par exemple Dictionary`2 ).
  • Accepte les collections de string/objectpaires (par exemple RouteValueDictionary ).
  • Accepte NameValueCollection s.
  • Trie les valeurs de requête par clé afin que les mêmes valeurs produisent des URI égaux.
  • Prend en charge plusieurs valeurs par clé, en préservant leur ordre d'origine.

La version documentée peut être trouvée ici .

L'extension:

public static Uri WithQuery(this Uri uri, object values)
{
    if (uri == null)
        throw new ArgumentNullException(nameof(uri));

    if (values != null)
    {
        var query = string.Join(
            "&", from p in ParseQueryValues(values)
                 where !string.IsNullOrWhiteSpace(p.Key)
                 let k = HttpUtility.UrlEncode(p.Key.Trim())
                 let v = HttpUtility.UrlEncode(p.Value)
                 orderby k
                 select string.IsNullOrEmpty(v) ? k : $"{k}={v}");

        if (query.Length != 0 || uri.Query.Length != 0)
            uri = new UriBuilder(uri) { Query = query }.Uri;
    }

    return uri;
}

L'analyseur de requête:

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

Voici les tests:

var uri = new Uri("https://stackoverflow.com/yo?oldKey=oldValue");

// Test with a string/string dictionary.
var q = uri.WithQuery(new Dictionary<string, string>
{
    ["k1"] = string.Empty,
    ["k2"] = null,
    ["k3"] = "v3"
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2&k3=v3"));

// Test with a string/object dictionary.
q = uri.WithQuery(new Dictionary<string, object>
{
    ["k1"] = "v1",
    ["k2"] = new[] { "v2a", "v2b" },
    ["k3"] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3"));

// Test with a name/value collection.
var nvc = new NameValueCollection()
{
    ["k1"] = string.Empty,
    ["k2"] = "v2a"
};

nvc.Add("k2", "v2b");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2=v2a&k2=v2b"));

// Test with any dictionary.
q = uri.WithQuery(new Dictionary<int, HashSet<string>>
{
    [1] = new HashSet<string> { "v1" },
    [2] = new HashSet<string> { "v2a", "v2b" },
    [3] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?1=v1&2=v2a&2=v2b&3"));

// Test with an anonymous object.
q = uri.WithQuery(new
{
    k1 = "v1",
    k2 = new[] { "v2a", "v2b" },
    k3 = new List<string> { "v3" },
    k4 = true,
    k5 = null as Queue<string>
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3=v3&k4=True&k5"));

// Keep existing query using a name/value collection.
nvc = HttpUtility.ParseQueryString(uri.Query);
nvc.Add("newKey", "newValue");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?newKey=newValue&oldKey=oldValue"));

// Merge two query objects using the RouteValueDictionary.
var an1 = new { k1 = "v1" };
var an2 = new { k2 = "v2" };

q = uri.WithQuery(
    new RouteValueDictionary(an1).Concat(
        new RouteValueDictionary(an2)));

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2"));
Şafak Gür
la source
2

Classe d'encapsuleur chaînable pour 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();
        }
    }
}

Exemple d'utilisation:

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

J'ai ajouté la méthode suivante à ma 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());
    }

Appeler:

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

J'ai écrit quelques méthodes d'extension que j'ai trouvées très utiles lorsque je travaillais avec QueryStrings. Souvent, je veux commencer par la chaîne de requête actuelle et la modifier avant de l'utiliser. Quelque chose comme,

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

Pour plus d'informations et la source: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

C'est basique, mais j'aime le style.

ccook
la source
1

Je voulais juste jeter mes 2 cents:

public static class HttpClientExt
{
    public static Uri AddQueryParams(this Uri uri, string query)
    {
        var ub = new UriBuilder(uri);
        ub.Query = string.IsNullOrEmpty(uri.Query) ? query : string.Join("&", uri.Query.Substring(1), query);
        return ub.Uri;
    }

    public static Uri AddQueryParams(this Uri uri, IEnumerable<string> query)
    {
        return uri.AddQueryParams(string.Join("&", query));
    } 

    public static Uri AddQueryParams(this Uri uri, string key, string value)
    {
        return uri.AddQueryParams(string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)));
    }

    public static Uri AddQueryParams(this Uri uri, params KeyValuePair<string,string>[] kvps)
    {
        return uri.AddQueryParams(kvps.Select(kvp => string.Join("=", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))));
    }

    public static Uri AddQueryParams(this Uri uri, IDictionary<string, string> kvps)
    {
        return uri.AddQueryParams(kvps.Select(kvp => string.Join("=", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))));
    }

    public static Uri AddQueryParams(this Uri uri, NameValueCollection nvc)
    {
        return uri.AddQueryParams(nvc.AllKeys.SelectMany(nvc.GetValues, (key, value) => string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))));
    }
}

Les documents disent que uri.Querycela commencera par un ?s'il n'est pas vide et que vous devez le couper si vous souhaitez le modifier.

Notez que cela HttpUtility.UrlEncodese trouve dans System.Web.

Usage:

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

Bien que n'étant pas élégant, j'ai opté pour une version plus simple qui n'utilise pas NameValueCollecitons- juste un modèle de constructeur enroulé autour 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
}

Selon les réponses existantes, je me suis assuré d'utiliser les HttpUtility.UrlEncodeappels. Il est utilisé comme ça:

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

Production:

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

Le code; vous pouvez tous me remercier quelque part, en quelque sorte: 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
la source
1

Je suis allé avec la solution proposée par DSO (réponse le 2 août 11 à 7:29), sa solution ne nécessite pas d'utiliser HttpUtility. Cependant, selon un article publié dans Dotnetpearls , l'utilisation d'un dictionnaire est plus rapide (en termes de performances) que l'utilisation de NameValueCollection. Voici la solution de DSO modifiée pour utiliser Dictionary à la place 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
la source
1

La chaîne de requête peut être ajoutée à une URL par:

  1. créer un objet de collection de valeurs de nom
  2. ajouter les éléments de chaîne de requête et leurs valeurs à cet objet
  3. encoder cet objet de collection de valeurs de nom à l'url le code est fourni dans le lien ci-dessous

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);
}
emploi
la source
0

J'ai écrit une aide pour mon projet de rasoir en utilisant certains des indices d'autres réponses.

L'activité ParseQueryString est nécessaire car nous ne sommes pas autorisés à falsifier l'objet QueryString de la demande en cours.

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

Je l'utilise comme ceci:

location.search = '[email protected]("var-name", "var-value")';

Si vous souhaitez qu'il prenne plusieurs valeurs, modifiez simplement les paramètres en Dictionnaire et ajoutez les paires à la chaîne de requête.

LOAS
la source
0

Le code ci-dessous est retiré de l' HttpValueCollectionimplémentation de ToString, via ILSpy, qui vous donne une chaîne de nom = valeur.

Malheureusement, HttpValueCollection est une classe interne que vous ne récupérez que si vous l'utilisez HttpUtility.ParseQueryString(). Je lui ai supprimé toutes les parties du viewstate, et il code par défaut:

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
la source
0

Ceci est identique à la réponse acceptée sauf légèrement plus compact:

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

Juste pour ceux qui ont besoin de la version VB.NET de la première réponse:

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

Et la version sans 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

Et la version C # sans 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
la source