web-dev-qa-db-fra.com

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

Une tâche courante lors de l'appel de ressources Web à partir d'un code consiste à créer une chaîne de requête incluant tous les paramètres nécessaires. Bien que, bien sûr, aucune science de fusée, il y a quelques détails astucieux dont vous devez vous occuper, comme ajouter un & sinon le premier paramètre, coder les 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 qu’on pourrait s’attendre à ce qu’une classe d’utilitaires existe, la rendant 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 méthode la plus élégante et la plus propre que vous connaissez?

447
Boaz

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

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'aurais peut-être pu mieux formater ça :)

J'imagine qu'il existe aussi une manière très élégante de faire cela dans LINQ ...

281
annakata

Vous pouvez créer une nouvelle instance inscriptible de HttpValueCollection en appelant System.Web.HttpUtility.ParseQueryString(string.Empty), puis l’utiliser comme n’importe quel NameValueCollection. Une fois que vous avez ajouté les valeurs souhaitées, 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

La HttpValueCollection est 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 n'importe quel autre NameValueCollection. Étant donné que l'objet réel que vous utilisez est un HttpValueCollection, l'appel de la méthode ToString appellera la méthode remplacée sur HttpValueCollection, qui formate la collection en tant que chaîne de requête codée par URL.

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

. NET Core

Si vous travaillez dans .NET Core, vous pouvez utiliser la classe Microsoft.AspNetCore.WebUtilities.QueryHelpers, ce qui simplifie grandement cette opération.

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

651
John Bledsoe

Inspiré par le commentaire de Roy Tinker, j'ai finalement utilisé 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");

Éditer - Variante conforme aux normes

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

using System.Web;

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

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

    var ub = new UriBuilder(uri);

    // this code block is taken from httpValueCollection.ToString() method
    // and modified so it encodes strings with HttpUtility.UrlEncode
    if (httpValueCollection.Count == 0)
        ub.Query = String.Empty;
    else
    {
        var sb = new StringBuilder();

        for (int i = 0; i < httpValueCollection.Count; i++)
        {
            string text = httpValueCollection.GetKey(i);
            {
                text = HttpUtility.UrlEncode(text);

                string val = (text != null) ? (text + "=") : string.Empty;
                string[] vals = httpValueCollection.GetValues(i);

                if (sb.Length > 0)
                    sb.Append('&');

                if (vals == null || vals.Length == 0)
                    sb.Append(val);
                else
                {
                    if (vals.Length == 1)
                    {
                        sb.Append(val);
                        sb.Append(HttpUtility.UrlEncode(vals[0]));
                    }
                    else
                    {
                        for (int j = 0; j < vals.Length; j++)
                        {
                            if (j > 0)
                                sb.Append('&');

                            sb.Append(val);
                            sb.Append(HttpUtility.UrlEncode(vals[j]));
                        }
                    }
                }
            }
        }

        ub.Query = sb.ToString();
    }

    return ub.Uri;
}
87
Vedran

J'ai répondu à un question similaire il y a un moment. Fondamentalement, le meilleur moyen serait d'utiliser la classe HttpValueCollection, propriété de la propriété Request.QueryString d'ASP.NET, elle est malheureusement interne au Cadre .NET. Vous pouvez utiliser Reflector pour le saisir (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 (ampersands et points d'interrogation inclus) et remplace une méthode 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
30
Igal Tabachnik

Voici une méthode fluide/lambda-ish en tant que méthode d'extension (combinant les concepts des publications précédentes) qui prend en charge plusieurs valeurs pour la même clé. Personnellement, je préfère les extensions aux wrappers pour permettre aux autres membres de l’équipe de découvrir des capacités comme celle-ci. Notez qu'il existe une controverse autour des méthodes d'encodage, ainsi que de nombreux articles à ce sujet sur Stack Overflow (un tel post ) 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, bien que vous deviez 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;
}
28
Alfred

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 associée optionnelle Flurl.Http vous permet d'effectuer des appels HTTP directement à partir de la même chaîne d'appels, 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 constructeur d'URL autonome:

PM> Install-Package Flurl

21
Todd Menier

Voici mon entrée tardive. Je n'aimais aucune des autres pour diverses raisons, alors j'ai écrit la mienne.

Cette version comporte:

  • Utilisation de StringBuilder uniquement. Pas d'appels ToArray () ou d'autres méthodes d'extension. Cela n’a pas l’air aussi joli que d’autres réponses, mais j’estime qu’il s’agit d’une fonction essentielle; l’efficacité est donc plus importante que le code "fluide", "one-liner", qui masque les inefficiences.

  • Gère plusieurs valeurs par clé. (Je n'en ai pas eu 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);

Sortie

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

Pourquoi ne pas créer des méthodes d’extension permettant 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));
}
11
LukeH

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, je ne peux donc pas utiliser ParseQueryString.

Au lieu de cela, j'ai utilisé System.Net.Http.FormUrlEncodedContent comme ceci:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
11
hortman
    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());
    }
9
Jay Douglass

Ajouter 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"
7
Gian Marco Gherardi

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
6
dav_i

Non testé, mais je pense que quelque chose dans ce sens fonctionnerait assez 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();
5
Nick Allen

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

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.

4
Martin Harris

En supposant que vous souhaitiez réduire les dépendances vis-à-vis des 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 avec les boucles aussi. La suppression de l'esperluette finale doit aller en dehors 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 posté quelque chose sur ce sujet).

3
Thomas Bratt

Combiné les meilleures réponses pour créer un 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();
    }
}
3
Luis Perez

[Également entrée tardive]

Classe wrapper pouvant être chaînée 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();
2
kroehre

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

private string ToQueryString(NameValueCollection nvc)
{
    if (nvc == null) return String.Empty;
    var queryParams = 
          string.Join("&", nvc.AllKeys.Select(key => 
              string.Join("&", nvc.GetValues(key).Select(v => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(v))))));
    return "?" + queryParams;
}
2
Tomino

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

  • Accepte les objets anonymes: uri.WithQuery(new { name = "value" })
  • Accepte les collections de string/string paires (par exemple, Dictionary`2 ).
  • Accepte les collections de string/object paires (par exemple, RouteValueDictionary ).
  • Accepte NameValueCollection s.
  • Trie les valeurs de la 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"));
2
Şafak Gür
// 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"));
}

Sortie:

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);
        }
    }
}
1
Demetris Leptos

J'ai écrit des méthodes d'extension que j'ai trouvées très utiles lorsque je travaille avec QueryStrings. Souvent, je veux commencer avec le QueryString actuel et le 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.

1
ccook

Je suis allé avec la solution proposée par DSO (réponse le 2 août à 7h29), sa solution ne nécessite pas d'utiliser HttpUtility. Cependant, selon un article publié dans Dotnetpearls , utiliser un dictionnaire est plus rapide (en termes de performances) que d'utiliser 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();
    }
1
Ticus

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);
1
Mike Cole

Je voulais juste mettre dans 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 docs dites que uri.Query commencera par un ? s'il n'est pas vide et vous devriez le couper si vous voulez le modifier.

Notez que HttpUtility.UrlEncode se trouve dans System.Web.

Utilisation:

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

C’est une autre manière ( peut-être redondante: -] ) de le faire.

Les concepts sont les mêmes que Vedran répondez dans cette page (jetez un oeil ici ) .

Mais cette classe est plus efficace, car elle ne parcoure toutes les clés qu'une seule fois: lorsque ToString est appelé.

Le code de formatage est également simplifié et amélioré.

J'espère que cela pourrait être utile.

public sealed class QueryStringBuilder
{
    public QueryStringBuilder()
    {
        this.inner = HttpUtility.ParseQueryString(string.Empty);
    }

    public QueryStringBuilder(string queryString)
    {
        this.inner = HttpUtility.ParseQueryString(queryString);
    }

    public QueryStringBuilder(string queryString, Encoding encoding)
    {
        this.inner = HttpUtility.ParseQueryString(queryString, encoding);
    }

    private readonly NameValueCollection inner;

    public QueryStringBuilder AddKey(string key, string value)
    {
        this.inner.Add(key, value);
        return this;
    }

    public QueryStringBuilder RemoveKey(string key)
    {
        this.inner.Remove(key);
        return this;
    }

    public QueryStringBuilder Clear()
    {
        this.inner.Clear();
        return this;
    }

    public override String ToString()
    {
        if (this.inner.Count == 0)
            return string.Empty;

        var builder = new StringBuilder();

        for (int i = 0; i < this.inner.Count; i++)
        {
            if (builder.Length > 0)
                builder.Append('&');

            var key = this.inner.GetKey(i);
            var values = this.inner.GetValues(i);

            if (key == null || values == null || values.Length == 0)
                continue;

            for (int j = 0; j < values.Length; j++)
            {
                if (j > 0)
                    builder.Append('&');

                builder.Append(HttpUtility.UrlEncode(key));
                builder.Append('=');
                builder.Append(HttpUtility.UrlEncode(values[j]));
            }
        }

        return builder.ToString();
    }
}
0
T-moty

Juste pour ceux qui ont besoin de la version VB.NET de la réponse 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

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
0
Stefan Steiger

Fonctionne pour plusieurs valeurs par clé dans NameValueCollection.

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

/// <summary>
/// Get query string for name value collection.
/// </summary>
public static string ToQueryString(this NameValueCollection collection,
    bool prefixQuestionMark = true)
{
    collection.NullArgumentCheck();
    if (collection.Keys.Count == 0)
    {
        return "";
    }
    var buffer = new StringBuilder();
    if (prefixQuestionMark)
    {
        buffer.Append("?");
    }
    var append = false;
    for (int i = 0; i < collection.Keys.Count; i++)
    {
        var key = collection.Keys[i];
        var values = collection.GetValues(key);
        key.NullCheck();
        values.NullCheck();
        foreach (var value in values)
        {
            if (append)
            {
                buffer.Append("&");
            }
            append = true;
            buffer.AppendFormat("{0}={1}", key.UrlEncode(), value.UrlEncode());
        }
    }
    return buffer.ToString();
}
0
hIpPy

Le code ci-dessous est retiré de l'implémentation HttpValueCollection de ToString, via ILSpy, ce qui vous donne une chaîne de requête name = value.

Malheureusement, HttpValueCollection est une classe interne que vous ne récupérez que si vous utilisez HttpUtility.ParseQueryString(). J'ai enlevé toutes les parties viewstate, et celui-ci 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();
    }
}
0
Chris S

C'est la même chose que la réponse acceptée, à l'exception d'un peu plus compacte:

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

J'ai écrit un assistant pour mon projet de rasoir en utilisant certaines des astuces d'autres réponses.

L'activité ParseQueryString est nécessaire car nous ne sommes pas autorisés à modifier 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 ça:

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

Si vous voulez qu'il prenne plus d'une valeur, changez simplement les paramètres en un dictionnaire et ajoutez les paires à la chaîne de requête.

0
LOAS

Bien que n'étant pas élégant, j'ai opté pour une version plus simple qui n'utilise pas NameValueCollecitons, mais simplement un motif de construction encapsulé autour de 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 appels HttpUtility.UrlEncode. C'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
0
Andrew Gray