Строить строку запроса для 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 и сделать что-то вроде следующего:

Использование 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();

Ответ 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&param2=value2"
param.ToQueryString("&"); //Will add (&)
//"&param1=value1&param2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1&param2=value2"