web-dev-qa-db-fra.com

Construire une chaîne de requête pour System.Net.HttpClient get

Si je souhaite soumettre une requête http à l'aide de System.Net.HttpClient, il semble qu'il n'y ait aucune API pour ajouter des paramètres, est-ce correct? 

Existe-t-il une simple API disponible pour construire la chaîne de requête qui ne nécessite pas la construction d'une collection de valeurs de nom et leur codage url puis leur concaténation? )

132
NeoDarque

Si je souhaite soumettre une requête http get à l'aide de System.Net.HttpClient il semble n'y avoir aucune api pour ajouter des paramètres, est-ce correct?

Oui.

Existe-t-il une simple API disponible pour construire la chaîne de requête qui n'implique pas la construction d'une collection de valeurs de nom et d'un encodage d'URL ceux-ci et enfin les concaténer?

Sûr:

var query = HttpUtility.ParseQueryString(string.Empty);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
string queryString = query.ToString();

vous donnera le résultat attendu:

foo=bar%3c%3e%26-baz&bar=bazinga

Vous pouvez également trouver la classe UriBuilder utile:

var builder = new UriBuilder("http://example.com");
builder.Port = -1;
var query = HttpUtility.ParseQueryString(builder.Query);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();

vous donnera le résultat attendu:

http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga

que vous pouvez plus qu'en toute sécurité nourrir votre méthode HttpClient.GetAsync.

233
Darin Dimitrov

Pour ceux qui ne souhaitent pas inclure System.Web dans les projets qui ne l'utilisent pas déjà, vous pouvez utiliser FormUrlEncodedContent from System.Net.Http et effectuer les opérations suivantes:

version keyvaluepair

string query;
using(var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{
    new KeyValuePair<string, string>("ham", "Glazed?"),
    new KeyValuePair<string, string>("x-men", "Wolverine + Logan"),
    new KeyValuePair<string, string>("Time", DateTime.UtcNow.ToString()),
})) {
    query = content.ReadAsStringAsync().Result;
}

version du dictionnaire

string query;
using(var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
    { "ham", "Glaced?"},
    { "x-men", "Wolverine + Logan"},
    { "Time", DateTime.UtcNow.ToString() },
})) {
    query = content.ReadAsStringAsync().Result;
}
69
Rostov

TL; DR: n'utilise pas la version acceptée car elle est complètement endommagée par la gestion des caractères unicode et n'utilise jamais d'API interne

J'ai effectivement trouvé un étrange problème de double encodage avec la solution acceptée:

Donc, si vous avez affaire à des caractères qui doivent être encodés, la solution acceptée conduit à un double encodage:

  • les paramètres de requête sont automatiquement codés à l'aide de l'indexeur NameValueCollection (et ceci utilise UrlEncodeUnicode, pas une valeur régulière attendue UrlEncode (!))
  • Ensuite, lorsque vous appelez uriBuilder.Uri, il crée une nouvelle variable Uri à l'aide du constructeur qui effectue l'encodage une fois de plus (encodage d'URL normal)
  • Cela ne peut pas être évité en faisant uriBuilder.ToString() (même si cela renvoie Uri correct lequel IMO est au moins incohérent, peut-être un bogue, mais c'est une autre question) et en utilisant ensuite la méthode HttpClient acceptant string - client crée toujours Uri votre chaîne passée comme ceci: new Uri(uri, UriKind.RelativeOrAbsolute)

Petit, mais complet repro:

var builder = new UriBuilder
{
    Scheme = Uri.UriSchemeHttps,
    Port = -1,
    Host = "127.0.0.1",
    Path = "app"
};

NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);

query["cyrillic"] = "кирилиця";

builder.Query = query.ToString();
Console.WriteLine(builder.Query); //query with cyrillic stuff UrlEncodedUnicode, and that's not what you want

var uri = builder.Uri; // creates new Uri using constructor which does encode and messes cyrillic parameter even more
Console.WriteLine(uri);

// this is still wrong:
var stringUri = builder.ToString(); // returns more 'correct' (still `UrlEncodedUnicode`, but at least once, not twice)
new HttpClient().GetStringAsync(stringUri); // this creates Uri object out of 'stringUri' so we still end up sending double encoded cyrillic text to server. Ouch!

Sortie:

?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f

https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f

Comme vous pouvez le constater, peu importe si vous utilisez uribuilder.ToString() + httpClient.GetStringAsync(string) ou uriBuilder.Uri + httpClient.GetStringAsync(Uri), vous finissez par envoyer un paramètre à double codage.

Exemple fixe pourrait être:

var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);

Mais ceci utilise le constructeur obsoleteUri

P.S sur mon dernier .NET sur Windows Server, le constructeur Uri avec le commentaire bool doc dit "obsolète, dontEscape est toujours faux", mais fonctionne réellement comme prévu (évite les sauts)

Donc, cela ressemble à un autre bug ...

Et même cela est tout à fait faux - cela envoie UrlEncodedUnicode au serveur, pas seulement UrlEncoded à quoi le serveur s'attend

Mise à jour: encore une chose, NameValueCollection utilise en fait UrlEncodeUnicode, qui n’est plus censé être utilisé et qui est incompatible avec url.encode/decode (voir NameValueCollection to URL Query? ).

La ligne de fond est donc: n'utilisez jamais ce hack avec NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); car cela dérangerait vos paramètres de requête unicode. Construisez simplement la requête manuellement et assignez-la à UriBuilder.Query qui fera l’encodage nécessaire puis obtiendra Uri en utilisant UriBuilder.Uri.

Premier exemple de vous faire mal en utilisant du code qui n'est pas censé être utilisé comme ceci

32
illegal-immigrant

Vous voudrez peut-être consulter Flurl [divulgation: je suis l'auteur], un générateur d'URL fluide avec la bibliothèque compagnon facultative qui l'étend dans un client à part entière REST.

var result = await "https://api.com"
    // basic URL building:
    .AppendPathSegment("endpoint")
    .SetQueryParams(new {
        api_key = ConfigurationManager.AppSettings["SomeApiKey"],
        max_results = 20,
        q = "Don't worry, I'll get encoded!"
    })
    .SetQueryParams(myDictionary)
    .SetQueryParam("q", "overwrite q!")

    // extensions provided by Flurl.Http:
    .WithOAuthBearerToken("token")
    .GetJsonAsync<TResult>();

Consultez la documentation pour plus de détails. Le package complet est disponible sur NuGet:

PM> Install-Package Flurl.Http

ou simplement le constructeur d'URL autonome:

PM> Install-Package Flurl

20
Todd Menier

Dans un projet ASP.NET Core, vous pouvez utiliser la classe QueryHelpers. 

// using Microsoft.AspNetCore.WebUtilities;
var query = new Dictionary<string, string>
{
    ["foo"] = "bar",
    ["foo2"] = "bar2",
    // ...
};

var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query));
16
Magu

Darin a proposé une solution intéressante et astucieuse. Voici une autre option:

public class ParameterCollection
{
    private Dictionary<string, string> _parms = new Dictionary<string, string>();

    public void Add(string key, string val)
    {
        if (_parms.ContainsKey(key))
        {
            throw new InvalidOperationException(string.Format("The key {0} already exists.", key));
        }
        _parms.Add(key, val);
    }

    public override string ToString()
    {
        var server = HttpContext.Current.Server;
        var sb = new StringBuilder();
        foreach (var kvp in _parms)
        {
            if (sb.Length > 0) { sb.Append("&"); }
            sb.AppendFormat("{0}={1}",
                server.UrlEncode(kvp.Key),
                server.UrlEncode(kvp.Value));
        }
        return sb.ToString();
    }
}

et ainsi en l'utilisant, vous pourriez faire ceci:

var parms = new ParameterCollection();
parms.Add("key", "value");

var url = ...
url += "?" + parms;
3
Mike Perrenoud

Ou simplement en utilisant mon extension Uri

Code

public static Uri AttachParameters(this Uri uri, NameValueCollection parameters)
{
    var stringBuilder = new StringBuilder();
    string str = "?";
    for (int index = 0; index < parameters.Count; ++index)
    {
        stringBuilder.Append(str + parameters.AllKeys[index] + "=" + parameters[index]);
        str = "&";
    }
    return new Uri(uri + stringBuilder.ToString());
}

Usage

Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection
                                                                           {
                                                                               {"Bill", "Gates"},
                                                                               {"Steve", "Jobs"}
                                                                           });

Résultat

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

2
Roman Ratskey

La bibliothèque de modèles d'URI RFC 6570 que je développe est capable d'effectuer cette opération. Tout l'encodage est traité pour vous conformément à cette RFC. Au moment d'écrire ces lignes, une version bêta est disponible et la seule raison pour laquelle elle n'est pas considérée comme une version 1.0 stable est que la documentation ne répond pas pleinement à mes attentes (voir les numéros # 17 , # 18 , # 32 , # 43 ).

Vous pouvez soit créer une chaîne de requête seule:

UriTemplate template = new UriTemplate("{?params*}");
var parameters = new Dictionary<string, string>
  {
    { "param1", "value1" },
    { "param2", "value2" },
  };
Uri relativeUri = template.BindByName(parameters);

Ou vous pouvez construire un URI complet:

UriTemplate template = new UriTemplate("path/to/item{?params*}");
var parameters = new Dictionary<string, string>
  {
    { "param1", "value1" },
    { "param2", "value2" },
  };
Uri baseAddress = new Uri("http://www.example.com");
Uri relativeUri = template.BindByName(baseAddress, parameters);
2
Sam Harwell

Vous pouvez toujours utiliser IEnterprise.Easy-HTTP car il possède un générateur de requêtes intégré:

await new RequestBuilder<ExampleObject>()
.SetHost("https://httpbin.org")
.SetContentType(ContentType.Application_Json)
.SetType(RequestType.Get)
.ContinueToQuery()
    .SetQuery("/get")
    .ParseModelToQuery(dto)
    .Build()
.Build()
.Execute();
1
Nikolay Hristov

Bonne partie de la réponse acceptée, modifiée pour utiliser UriBuilder.Uri.ParseQueryString () au lieu de HttpUtility.ParseQueryString ():

var builder = new UriBuilder("http://example.com");
var query = builder.Uri.ParseQueryString();
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();
1
Jpsy

Étant donné que je dois réutiliser ces quelques temps, j'ai créé cette classe qui aide simplement à résumer la composition de la chaîne de requête.

public class UriBuilderExt
{
    private NameValueCollection collection;
    private UriBuilder builder;

    public UriBuilderExt(string uri)
    {
        builder = new UriBuilder(uri);
        collection = System.Web.HttpUtility.ParseQueryString(string.Empty);
    }

    public void AddParameter(string key, string value) {
        collection.Add(key, value);
    }

    public Uri Uri{
        get
        {
            builder.Query = collection.ToString();
            return builder.Uri;
        }
    }

}

L'utilisation sera simplifiée à quelque chose comme ceci:

var builder = new UriBuilderExt("http://example.com/");
builder.AddParameter("foo", "bar<>&-baz");
builder.AddParameter("bar", "second");
var uri = builder.Uri;

qui renverra l'URI: http://example.com/?foo=bar%3c%3e%26-baz&bar=second

1
Jaider

Pour éviter le problème de double codage décrit dans la réponse de taras.roshko et conserver la possibilité de travailler facilement avec les paramètres de requête, vous pouvez utiliser uriBuilder.Uri.ParseQueryString() au lieu de HttpUtility.ParseQueryString().

0
Valeriy Lyuchyn

Merci à "Darin Dimitrov", ce sont les méthodes d'extension.

 public static partial class Ext
{
    public static Uri GetUriWithparameters(this Uri uri,Dictionary<string,string> queryParams = null,int port = -1)
    {
        var builder = new UriBuilder(uri);
        builder.Port = port;
        if(null != queryParams && 0 < queryParams.Count)
        {
            var query = HttpUtility.ParseQueryString(builder.Query);
            foreach(var item in queryParams)
            {
                query[item.Key] = item.Value;
            }
            builder.Query = query.ToString();
        }
        return builder.Uri;
    }

    public static string GetUriWithparameters(string uri,Dictionary<string,string> queryParams = null,int port = -1)
    {
        var builder = new UriBuilder(uri);
        builder.Port = port;
        if(null != queryParams && 0 < queryParams.Count)
        {
            var query = HttpUtility.ParseQueryString(builder.Query);
            foreach(var item in queryParams)
            {
                query[item.Key] = item.Value;
            }
            builder.Query = query.ToString();
        }
        return builder.Uri.ToString();
    }
}
0
Waleed A.K.

Ceci est ma solution pour .Net Core basée sur la réponse de Roman Ratskey. Le type NameValueCollection a été supprimé dans .Net Core.

Code

public static class UriExtensions
    {
        public static string AttachParameters(this string uri, Dictionary<string, string> parameters)
        {
            var stringBuilder = new StringBuilder();
            string str = "?";

            foreach (KeyValuePair<string, string> parameter in parameters)
            {
                stringBuilder.Append(str + parameter.Key + "=" + parameter.Value);
                str = "&";
            }
            return uri + stringBuilder;
        }
    }

Usage

 var parameters = new Dictionary<string, string>();
            parameters.Add("Bill", "Gates");
            parameters.Add("Steve", "Jobs");

string uri = "http://www.example.com/index.php".AttachParameters(parameters);

Résultat

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

0
Ângelo Polotto

Dans le même ordre d'idées que le message de Rostov, si vous ne souhaitez pas inclure de référence à _System.Web_ dans votre projet, vous pouvez utiliser FormDataCollection de _System.Net.Http.Formatting_ et procéder comme suit:

Utilisation de System.Net.Http.Formatting.FormDataCollection

_var parameters = new Dictionary<string, string>()
{
    { "ham", "Glaced?" },
    { "x-men", "Wolverine + Logan" },
    { "Time", DateTime.UtcNow.ToString() },
}; 
var query = new FormDataCollection(parameters).ReadAsNameValueCollection().ToString();
_
0
cwills