Строить строку запроса для System.Net.HttpClient get
Если я хочу отправить запрос http get с помощью System.Net.HttpClient, похоже, нет api для добавления параметров, это правильно?
Существует ли какая-либо простая api для построения строки запроса, которая не связана с созданием коллекции значений имени и кодировкой url, а затем, наконец, их конкатенацией?
Я надеялся использовать что-то вроде RestSharp api (например, AddParameter (..))
Ответы
Ответ 1
Если я хочу отправить запрос http get с помощью System.Net.HttpClient кажется, нет api для добавления параметров, это правильно?
Да.
Существует ли простой api для построения строки запроса, которая не связано с созданием коллекции значений имен и кодирования URL-адресов те, а затем, наконец, конкатенировать их?
Конечно:
var query = HttpUtility.ParseQueryString(string.Empty);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
string queryString = query.ToString();
даст вам ожидаемый результат:
foo=bar%3c%3e%26-baz&bar=bazinga
Вы также можете найти класс UriBuilder
полезным:
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();
даст вам ожидаемый результат:
http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga
чтобы вы могли более безопасно подавать ваш метод HttpClient.GetAsync
.
Ответ 2
Для тех, кто не хочет включать System.Web
в проекты, которые его еще не используют, вы можете использовать FormUrlEncodedContent
из System.Net.Http
и сделать что-то вроде следующего:
версия ключевой пары
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;
}
Ответ 3
TL; DR: не использовать принятую версию, поскольку она полностью нарушена в отношении обработки символов Unicode и никогда не использует внутренний API
Я действительно нашел странную проблему с двойным кодированием с принятым решением:
Итак, если вы имеете дело с символами, которые должны быть закодированы, принятое решение приводит к двойному кодированию:
- параметры запроса автоматически кодируются с помощью индексатора
NameValueCollection
(, и это использует UrlEncodeUnicode
, а не регулярное ожидаемое UrlEncode
(!))
- Затем, когда вы вызываете
uriBuilder.Uri
, он создает новый Uri
, используя конструктор , который кодирует еще раз (обычная кодировка url)
- Этого нельзя избежать, выполнив
uriBuilder.ToString()
(хотя это возвращает правильный Uri
, который IMO по крайней мере несогласован, может быть, ошибка, но этот другой вопрос), а затем используя HttpClient
метод принятия строки - клиент все еще создает Uri
из вашей переданной строки следующим образом: new Uri(uri, UriKind.RelativeOrAbsolute)
Маленький, но полный текст:
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 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!
Вывод:
?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f
https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f
Как вы можете видеть, независимо от того, выполняете ли вы uriBuilder.ToString()
+ httpClient.GetStringAsync(string)
или uriBuilder.Uri
+ httpClient.GetStringAsync(Uri)
, вы отправляете двойной закодированный параметр
Исправлен пример:
var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);
Но для этого используется устаревший Uri
конструктор
P.S на моем последнем .NET на Windows Server, конструктор Uri
с комментарием bool doc говорит: "Устаревшее, dontEscape всегда ложно", но фактически работает как ожидалось (пропускает экранирование)
Итак, это похоже на другую ошибку...
И даже это просто неправильно - он отправляет UrlEncodedUnicode на сервер, а не только UrlEncoded, какой сервер ожидает
Обновление: еще одна вещь: NameValueCollection на самом деле делает UrlEncodeUnicode, который больше не предполагается использовать и несовместим с обычным url.encode/decode (см. NameValueCollection для URL-запроса?).
Итак, нижняя строка: никогда не используйте этот хак с NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
, так как это испортит ваши параметры запроса юникода. Просто создайте запрос вручную и назначьте его UriBuilder.Query
, который сделает необходимую кодировку, а затем получит Uri, используя uriBuilder.Uri
.
Типичный пример причинения вреда себе, используя код, который не предполагается использовать таким образом
Ответ 4
В проекте ASP.NET Core вы можете использовать класс QueryHelpers.
// using Microsoft.AspNetCore.WebUtilities;
var query = new Dictionary<string, string>
{
["foo"] = "bar",
["foo2"] = "bar2",
// ...
};
var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query));
Ответ 5
Возможно, вы захотите проверить Flurl [раскрытие: я автор], беглый конструктор URL с опциональной дополнительной библиотекой, которая расширяет его до полноценного 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>();
Проверьте документы для более подробной информации. Полный пакет доступен на NuGet:
PM> Install-Package Flurl.Http
или просто автономный построитель URL:
PM> Install-Package Flurl
Ответ 6
Дарин предложил интересное и умное решение, и вот что может быть другим вариантом:
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();
}
}
поэтому при его использовании вы можете сделать это:
var parms = new ParameterCollection();
parms.Add("key", "value");
var url = ...
url += "?" + parms;
Ответ 7
Или просто используя расширение 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"}
});
Результат
http://www.example.com/index.php?Bill=Gates&Steve=Jobs
Ответ 8
Библиотека шаблонов RFC 6570 URI Я разрабатываю возможность выполнять эту операцию. Вся кодировка обрабатывается для вас в соответствии с этим RFC. На момент написания этой статьи была доступна бета-версия, и единственная причина, по которой она не считалась стабильной версией 1.0, - это то, что документация не полностью соответствует моим ожиданиям (см. Вопросы # 17, # 18, # 32, # 43).
Вы можете либо построить строку запроса самостоятельно:
UriTemplate template = new UriTemplate("{?params*}");
var parameters = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri relativeUri = template.BindByName(parameters);
Или вы можете создать полный URI:
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);
Ответ 9
Так как я должен повторно использовать это несколько раз, я придумал этот класс, который просто помогает абстрагироваться от того, как формируется строка запроса.
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;
}
}
}
Использование будет упрощено примерно так:
var builder = new UriBuilderExt("http://example.com/");
builder.AddParameter("foo", "bar<>&-baz");
builder.AddParameter("bar", "second");
var uri = builder.Uri;
который вернет uri:
http://example.com/?foo=bar%3c%3e%26-baz&bar=second
Ответ 10
Вы всегда можете использовать IEnterprise.Easy-HTTP, потому что он имеет встроенный построитель запросов:
await new RequestBuilder<ExampleObject>()
.SetHost("https://httpbin.org")
.SetContentType(ContentType.Application_Json)
.SetType(RequestType.Get)
.ContinueToQuery()
.SetQuery("/get")
.ParseModelToQuery(dto)
.Build()
.Build()
.Execute();
Ответ 11
Хорошая часть принятого ответа, измененная для использования UriBuilder.Uri.ParseQueryString() вместо 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();
Ответ 12
Благодаря "Дарину Димитрову", это методы расширения.
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();
}
}
Ответ 13
Это мое решение для .Net Core, основанного на ответе Романа Ратске. Тип NameValueCollection был удален в .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);
Результат
http://www.example.com/index.php?Bill=Gates&Steve=Jobs
Ответ 14
Чтобы избежать проблемы с двойным кодированием, описанной в taras.roshko, и чтобы иметь возможность легко работать с параметрами запроса, вы можете использовать uriBuilder.Uri.ParseQueryString()
вместо HttpUtility.ParseQueryString()
.
Ответ 15
Если вы не хотите включать ссылку на System.Web
в свой проект, вы можете использовать FormDataCollection
из System.Net.Http.Formatting
и сделать что-то вроде следующего:
var parameters = new Dictionary<string, string>()
{
{ "ham", "Glaced?" },
{ "x-men", "Wolverine + Logan" },
{ "Time", DateTime.UtcNow.ToString() },
};
var query = new FormDataCollection(parameters).ReadAsNameValueCollection().ToString();
Ответ 16
Я не мог найти лучшего решения, кроме создания метода расширения для преобразования Словаря в QueryStringFormat. Решение, предложенное Валидом А.К. также хорошо.
Следуйте моему решению:
Создайте метод расширения:
public static class DictionaryExt
{
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
{
return ToQueryString<TKey, TValue>(dictionary, "?");
}
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, string startupDelimiter)
{
string result = string.Empty;
foreach (var item in dictionary)
{
if (string.IsNullOrEmpty(result))
result += startupDelimiter; // "?";
else
result += "&";
result += string.Format("{0}={1}", item.Key, item.Value);
}
return result;
}
}
И их:
var param = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
param.ToQueryString(); //By default will add (?) question mark at begining
//"?param1=value1¶m2=value2"
param.ToQueryString("&"); //Will add (&)
//"¶m1=value1¶m2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1¶m2=value2"