592 votes

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

Une tâche courante lorsqu'on appelle des ressources web à partir d'un code est de construire une chaîne de requête incluant tous les paramètres nécessaires. Bien qu'il ne s'agisse pas d'un travail de pionnier, il y a quelques détails dont il faut s'occuper, comme l'ajout d'une balise de type & s'il ne s'agit pas du premier paramètre, codage 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")); }
}

Il s'agit d'une tâche tellement courante que l'on pourrait s'attendre à ce qu'il existe une classe utilitaire qui la rende plus élégante et plus lisible. En parcourant MSDN, je n'en ai pas trouvé, ce qui m'amène à la question suivante :

Quel est le moyen le plus élégant et le plus propre que vous connaissez pour faire ce qui précède ?

32 votes

C'est un peu triste que même à l'heure actuelle, il ne semble pas y avoir de simple pour traiter les chaînes de requêtes. Et par simple, j'entends une classe de cadre OOB, non interne, conforme aux normes. Ou peut-être que je passe à côté de quelque chose ?

6 votes

Il ne vous manque rien. La construction de querystring est une lacune majeure dans le cadre de travail que j'ai essayé de combler avec Flurl .

839voto

John Bledsoe Points 7507

Vous pouvez créer une nouvelle écriture de l'instance de HttpValueCollection en appelant System.Web.HttpUtility.ParseQueryString(string.Empty), et ensuite l'utiliser comme tout NameValueCollection. Une fois que vous avez ajouté les valeurs que vous voulez, vous pouvez appeler ToString sur la collection pour obtenir une chaîne de requête, comme suit:

NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

queryString["key1"] = "value1";
queryString["key2"] = "value2";

return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded

Le HttpValueCollection est à l'intérieur et vous ne pouvez donc pas directement la construction d'une instance. Cependant, une fois que vous obtenir une instance vous pouvez l'utiliser comme n'importe quel autre NameValueCollection. Depuis l'objet réel vous travaillez avec est un HttpValueCollection, l'appel de méthode ToString qui fera appel à la méthode redéfinie sur HttpValueCollection, quels sont les formats de la collection une URL-encodé de la chaîne de requête.

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

6 votes

Vous pourriez probablement créer une méthode d'extension appelée ToURLQueryString pour l'interface IDictionary : public static string ToURLQueryString(this IDictionary dict) { ... }

73 votes

Cette méthode n'est pas conforme à la norme pour les caractères à plusieurs octets. Il les codera comme %uXXXX au lieu de %XX%XX. Les chaînes de requête qui en résultent peuvent être mal interprétées par les serveurs web. Ceci est même documenté dans la classe interne du framework HttpValueCollection qui est retournée par HttpUtility.ParseQueryString() . Le commentaire indique qu'ils conservent ce comportement pour des raisons de compatibilité ascendante.

28 votes

Notez qu'il y a une différence importante entre HttpUtilityPraseQueryString("") et new NameValueCollection() -- seul le résultat de HttpUtility surmontera ToString() pour produire une chaîne de requête correcte

319voto

annakata Points 42676

Si vous regardez sous le capot, la propriété QueryString est une NameValueCollection. Lorsque j'ai fait des choses similaires, j'ai généralement été intéressé par la sérialisation ET la désérialisation. Je suggère donc de construire une NameValueCollection et de la passer à :

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 cela dans LINQ aussi...

24 votes

La spécification HTTP (RFC 2616) ne dit rien sur ce que peuvent contenir les chaînes de requête. La RFC 3986, qui définit le format générique des URI, ne le dit pas non plus. Le format de paire clé/valeur couramment utilisé s'appelle application/x-www-form-urlencoded et est en fait défini par le HTML, dans le but de soumettre des données de formulaire dans le cadre d'un processus d'authentification. GET demande. Le HTML 5 n'interdit pas les valeurs multiples par clé dans ce format. En fait, il exige que le navigateur produise plusieurs valeurs par clé dans le cas où la page contient (à tort) plusieurs champs avec la même name attribut. Voir goo.gl/uk1Ag

15 votes

@annakata : Je sais que mon commentaire date de plus d'un an (et la réponse de plus de deux ans !), mais NameValueCollection supporte très bien les valeurs multiples par clé, en utilisant la méthode GetValues(key).

4 votes

@MauricioScheffer : Mais NameValueCollection ne se transforme pas "correctement" en une chaîne de requête. 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 "path?key=value1,value2" au lieu de "path?key=value1&key=value2", qui est un modèle commun (standard ?).

113voto

Vedran Points 2107

En m'inspirant du commentaire de Roy Tinker, j'ai fini par utiliser une méthode d'extension simple sur la classe Uri qui permet de garder un 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;
    }
}

Utilisation :

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

Edit - Variante conforme aux normes

Comme plusieurs personnes l'ont souligné, httpValueCollection.ToString() encode les caractères Unicode dans un non conforme aux normes façon. Il s'agit d'une variante de la même méthode d'extension qui traite de tels caractères en invoquant HttpUtility.UrlEncode au lieu de la méthode dépréciée HttpUtility.UrlEncodeUnicode méthode.

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

4 votes

Parfait. Ajouté à ma bibliothèque interne :)

1 votes

Vous devez également coder l'URL de la valeur. queryString.Add(name, Uri.EscapeDataString(value)) ;

0 votes

Vérifiez ceci ligne . / n'est pas encodé.

32voto

Igal Tabachnik Points 15160

J'ai répondu à un question similaire il y a quelques temps. En gros, le meilleur moyen serait d'utiliser la classe HttpValueCollection que l'application ASP.NET Request.QueryString Malheureusement, cette propriété est interne au cadre .NET. Vous pourriez utiliser Reflector pour la récupérer (et la placer dans votre classe Utils). De cette façon, vous pourriez manipuler la chaîne de requête comme une NameValueCollection, mais avec tous les problèmes d'encodage/décodage d'url pris en charge pour vous.

HttpValueCollection étend NameValueCollection et possède un constructeur qui prend un encodé (y compris les esperluettes et les points d'interrogation), et il remplace une chaîne de requête ToString() 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

0 votes

Merci... J'ai remarqué que la NameValueCollection qu'il renvoie a un ToString() qui agit différemment mais je n'ai pas pu comprendre pourquoi.

0 votes

httpValueCollection.ToString() appelle effectivement httpValueCollection.ToString(true) donc en ajoutant le true L'explicitation n'est pas requise.

8 votes

HttpValueCollection est une classe interne, vous ne pouvez donc pas l'instancier.

31voto

Alfred Points 505

Voici une méthode fluide/lambda comme méthode d'extension (combinant les concepts des messages précédents) qui prend en charge plusieurs valeurs pour la même clé. Ma préférence personnelle va aux extensions plutôt qu'aux wrappers, pour des raisons de facilité de découverte par d'autres membres de l'équipe pour ce genre de choses. Notez qu'il y a une controverse autour des méthodes d'encodage, beaucoup d'articles à ce sujet sur Stack Overflow (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());
}

éditer : 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;
}

1 votes

Cela échoue si l'une des valeurs est nulle.

0 votes

C'est faux, cela génère plusieurs chaînes de requête pour chaque paire clé-valeur.

0 votes

@GayanRanasinghe : Qu'est-ce que ça veut dire ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X