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? )
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
.
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:
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;
}
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;
}
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:
NameValueCollection
(et ceci utilise UrlEncodeUnicode
, pas une valeur régulière attendue UrlEncode
(!))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)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
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
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));
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;
Ou simplement en utilisant mon extension Uri
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());
}
Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection
{
{"Bill", "Gates"},
{"Steve", "Jobs"}
});
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);
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();
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();
É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
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()
.
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();
}
}
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.
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;
}
}
var parameters = new Dictionary<string, string>();
parameters.Add("Bill", "Gates");
parameters.Add("Steve", "Jobs");
string uri = "http://www.example.com/index.php".AttachParameters(parameters);
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:
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();
_