Как построить строку запроса для URL в C #?

Обычной задачей при вызове веб-ресурсов из кода является построение строки запроса, включающей все необходимые параметры. Несмотря на то, что ни в коем случае не ракетостроение, есть некоторые изящные детали, о которых вам нужно позаботиться, добавив, &если не первый параметр, кодируя параметры и т. Д.

Код для этого очень прост, но немного утомителен:

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")); }
}

Это такая распространенная задача, что можно ожидать существования служебного класса, который делает его более элегантным и читаемым. Сканируя MSDN, я не смог найти ни одного, что приводит меня к следующему вопросу:

Какой самый элегантный и чистый способ сделать это?

6.05.2009 11:02:17
Немного грустно, что даже в текущий момент времени не существует простого способа справиться со строками запросов. И под простым понятием я подразумеваю OOB, не внутренний, совместимый со стандартами каркасный класс. Или, может быть, я что-то упускаю?
Grimace of Despair 1.12.2013 21:27:50
Вы ничего не пропускаете. Создание Querystring - основной пробел в структуре, которую я пытался заполнить Flurl .
Todd Menier 7.05.2014 14:37:57
Вы только что заставили меня подумать, что я должен создать один .. новый UrlBuilder (существующий) .AddQuery ("ключ", "значение"). ToString ()
Demetris Leptos 22.06.2017 13:48:06
30 ОТВЕТОВ
РЕШЕНИЕ

Если вы загляните внутрь, свойство QueryString будет NameValueCollection. Когда я делал подобные вещи, я обычно интересовался сериализацией и десериализацией, поэтому я предлагаю создать коллекцию NameValueCollection и затем перейти к:

using System.Linq;
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);
}

Я думаю, что в LINQ тоже есть супер элегантный способ сделать это ...

289
22.09.2019 22:09:01
Спецификация HTTP (RFC 2616) ничего не говорит о том, что могут содержать строки запроса. Также не RFC 3986, который определяет общий формат URI. Формат пары ключ / значение, который обычно используется, называется application/x-www-form-urlencodedи фактически определяется HTML с целью отправки данных формы как части GETзапроса. HTML 5 не запрещает использование нескольких значений на ключ в этом формате и фактически требует, чтобы браузер выдавал несколько значений на ключ в случае, если страница (неправильно) содержит несколько полей с одним и тем же nameатрибутом. Смотрите goo.gl/uk1Ag
Daniel Cassidy 22.03.2011 17:18:55
@annakata: я знаю, что моему комментарию более года (а ответу более двух лет!), но NameValueCollection очень поддерживает несколько значений на ключ, используя метод GetValues ​​(ключ).
Mauricio Scheffer 25.07.2011 02:50:34
@MauricioScheffer: Но NameValueCollection не преобразуется в строку запроса «правильно». Например, если вы установите параметр QueryString в WebClient, где один и тот же ключ присутствует несколько раз, он превращается в «путь? Ключ = значение1, значение2» вместо «путь? Ключ = значение1 и ключ = значение2», что является распространенным (стандартным ?) шаблон.
David Pope 4.11.2011 18:24:41
Что касается нескольких значений на ключ, я полагаю, что в HTML, если у вас есть список с несколькими вариантами выбора с несколькими выбранными и отправленными элементами, они отправляются в формате с несколькими значениями, упомянутым Дэвидом.
Sam 10.02.2012 20:38:15
Возможно, вы захотите использовать Uri.EscapeDataString вместо HttpUtility.UrlEncode, который является более переносимым. См. Stackoverflow.com/questions/2573290/…
PEK 21.07.2014 07:09:09

Не проверено, но я думаю, что что-то в этом роде будет работать очень хорошо

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
6.05.2009 11:41:33
красиво инкапсулирован, но этот формат в "? {0}" излишне дорогой :)
annakata 6.05.2009 11:37:58
изменил имя класса на QueryString .. Query немного двусмысленно
Nick Allen 6.05.2009 11:43:06

РЕДАКТИРОВАТЬ - как указано в комментариях, это не путь.

Есть такой класс - URI Class. «Предоставляет объектное представление унифицированного идентификатора ресурса (URI) и легкий доступ к частям URI». (Документы Microsoft).

В следующем примере создается экземпляр класса Uri и используется его для создания экземпляра WebRequest.

Пример C #

Uri siteUri = new Uri (" http://www.contoso.com/ ");

WebRequest wr = WebRequest.Create (siteUri);

Проверьте это, есть много методов в этом классе.

-6
6.05.2009 11:58:31
класс Uri не имеет методов для управления строкой запроса, кроме получения и установки ее (и я не уверен насчет последнего)
Guss 6.05.2009 11:32:43
Класс Uri хорош, если у вас есть встроенный URI, включая запрос. Ури является неизменным, поэтому вы не можете добавить к нему, как только он будет создан. Есть класс UriBuilder, но у IIRC нет метода для строки запроса; это все еще оставлено на усмотрение программиста. Класс Uri хорош, если вы его создали для таких вещей, как правильное побег.
ageektrapped 6.05.2009 11:32:55

Версия на основе метода быстрого расширения:

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);
    }
}

Вы можете использовать предложение where, чтобы выбрать, какие параметры будут добавлены в строку.

4
6.05.2009 11:27:44

Я ответил на аналогичный вопрос некоторое время назад. По сути, лучшим способом было бы использовать класс HttpValueCollection, которым на Request.QueryStringсамом деле является свойство ASP.NET , к сожалению, оно является внутренним в среде .NET. Вы можете использовать Reflector, чтобы захватить его (и поместить в свой класс Utils). Таким образом, вы можете манипулировать строкой запроса, как NameValueCollection, но со всеми проблемами кодирования / декодирования URL позаботились о вас.

HttpValueCollectionрасширяется NameValueCollectionи имеет конструктор, который принимает закодированную строку запроса (включая амперсанды и вопросительные знаки) и переопределяет ToString()метод для последующего перестроения строки запроса из базовой коллекции.

Пример:

  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
29
23.05.2017 11:55:02
Спасибо ... я заметил, что NameValueCollection, который он возвращает, имеет ToString (), который действует по-другому, но не может понять, почему.
calebt 3.08.2009 16:52:41
httpValueCollection.ToString()на самом деле звонки, httpValueCollection.ToString(true)так что добавление trueпростоты не требуется.
dav_i 10.12.2013 09:58:22
HttpValueCollection является внутренним классом, поэтому вы не можете создать его экземпляр.
ozba 11.01.2016 10:19:31

Как насчет создания методов расширения, которые позволяют вам добавлять параметры в свободном стиле, как это?

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(); 

Вот перегрузка, которая использует 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);
}

И вот перегрузка, которая использует 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));
}
12
6.05.2009 13:33:09
: +1: для простого метода расширения на основе строк. Некоторые другие ответы могут охватывать более крайние случаи, но это достаточно для моей ситуации, и это не требует , чтобы я построить NameValueCollection, HttpValueCollectionили Uriпервый. Спасибо!
Stanley G. 4.12.2019 20:07:45

Предполагая, что вы хотите уменьшить зависимости от других сборок и упростить задачу, вы можете сделать:

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();

Это хорошо работает и с петлями. Окончательное удаление амперсанда должно выходить за пределы цикла.

Обратите внимание, что оператор конкатенации используется для улучшения читабельности. Стоимость его использования по сравнению со стоимостью использования StringBuilder минимальна (я думаю, что Джефф Этвуд опубликовал что-то на эту тему).

3
6.05.2009 12:19:43

Я добавил следующий метод в свой класс 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());
    }

Звонить:

NameValueCollection querystrings = new NameValueCollection();    
querystrings.Add("language", "en");
querystrings.Add("id", "134");
this.Redirect("http://www.mypage.com", querystrings);
1
6.05.2009 13:34:58

Вы можете создать новый доступный для записи экземпляр HttpValueCollection, вызвав его System.Web.HttpUtility.ParseQueryString(string.Empty), а затем использовать его как любой NameValueCollection. После того, как вы добавили нужные значения, вы можете вызвать ToStringколлекцию, чтобы получить строку запроса, как показано ниже:

NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

queryString.Add("key1", "value1");
queryString.Add("key2", "value2");

return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded

Он HttpValueCollectionявляется внутренним, и поэтому вы не можете напрямую создать экземпляр. Однако, как только вы получите экземпляр, вы можете использовать его, как и любой другой NameValueCollection. Поскольку фактический объект, с которым вы работаете, - это HttpValueCollectionметод, вызов метода ToString вызовет переопределенный метод HttpValueCollection, который форматирует коллекцию как строку запроса в кодировке URL.

После поиска SO и в Интернете ответа на похожую проблему, это самое простое решение, которое я мог найти.

.NET Core

Если вы работаете в .NET Core, вы можете использовать этот Microsoft.AspNetCore.WebUtilities.QueryHelpersкласс, что значительно упрощает это.

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

Образец кода:

const string url = "https://customer-information.azure-api.net/customers/search/taxnbr";
var param = new Dictionary<string, string>() { { "CIKey", "123456789" } };

var newUrl = new Uri(QueryHelpers.AddQueryString(url, param));
680
15.01.2020 18:04:08
Возможно, вы могли бы создать метод расширения с именем ToURLQueryString для интерфейса IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker 10.03.2011 03:28:16
Этот метод не соответствует стандарту для многобайтовых символов. Он будет кодировать их как% uXXXX вместо% XX% XX. Результирующие строки запроса могут быть неправильно интерпретированы веб-серверами. Это даже задокументировано во внутреннем каркасном классе HttpValueCollection, который возвращается HttpUtility.ParseQueryString (). Комментарий говорит, что они сохраняют это поведение по причинам обратной совместимости.
alex 26.09.2012 14:42:29
Обратите внимание, что между HttpUtilityPraseQueryString ("") и новым NameValueCollection () есть важное различие - только результат HttpUtility будет переопределять ToString () для создания правильной
Frank Schwieterman 19.02.2013 05:58:13
Как насчет случаев, когда вам нужно несколько экземпляров имени в строке запроса? Например, «type = 10 & type = 21».
Finster 31.03.2014 21:29:23
@Finster Вы можете добавить несколько экземпляров имени в строку запроса, используя Addметод. Т.е. queryString.Add("type", "1"); queryString.Add("type", "2"); использование Addметода, вероятно, является лучшим способом сделать это все время на самом деле.
jeremysawesome 18.06.2014 15:11:38

Вот реализация, которая использует очень простые языковые функции. Это часть класса, который мы должны портировать и поддерживать в Objective C, поэтому мы решили иметь больше строк кода, но их легче переносить и понимать программисту, который не очень знаком с C #.

        /// <summary>
        /// Builds a complete http url with query strings.
        /// </summary>
        /// <param name="pHostname"></param>
        /// <param name="pPort"></param>
        /// <param name="pPage">ex "/index.html" or index.html</param>
        /// <param name="pGetParams">a Dictionary<string,string> collection containing the key value pairs.  Pass null if there are none.</param>
        /// <returns>a string of the form: http://[pHostname]:[pPort/[pPage]?key1=val1&key2=val2...</returns>

  static public string buildURL(string pHostname, int pPort, string pPage, Dictionary<string,string> pGetParams)
        {
            StringBuilder sb = new StringBuilder(200);
            sb.Append("http://");
            sb.Append(pHostname);
            if( pPort != 80 ) {
                sb.Append(pPort);
            }
            // Allows page param to be passed in with or without leading slash.
            if( !pPage.StartsWith("/") ) {
                sb.Append("/");
            }
            sb.Append(pPage);

            if (pGetParams != null && pGetParams.Count > 0)
            {
                sb.Append("?");
                foreach (KeyValuePair<string, string> kvp in pGetParams)
                {
                    sb.Append(kvp.Key);
                    sb.Append("=");
                    sb.Append( System.Web.HttpUtility.UrlEncode(kvp.Value) );
                    sb.Append("&");
                }
                sb.Remove(sb.Length - 1, 1); // Remove the final '&'
            }
            return sb.ToString();
        }
-2
21.03.2012 16:05:47
public string UrlQueryStr(object data)
{
    if (data == null)
        return string.Empty;

    object val;
    StringBuilder sb = new StringBuilder();

    foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(data))
    {
        if ((val = prop.GetValue(data)) != null)
        {
            sb.AppendFormat("{0}{1}={2}", sb.Length == 0 ? '?' : '&',
                HttpUtility.UrlEncode(prop.Name), HttpUtility.UrlEncode(val.ToString()));
        }
    }
    return sb.ToString();
}
-3
15.02.2010 19:39:38
    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
1.03.2011 18:06:46
Ницца! Но вам не нужны .ToArray()с.
mpen 12.11.2013 23:07:50

Вот моя поздняя запись. Мне не нравились другие по разным причинам, поэтому я написал свою.

Эта версия имеет:

  • Использование только StringBuilder. Нет вызовов ToArray () или других методов расширения. Он выглядит не так красиво, как некоторые другие ответы, но я считаю, что это основная функция, поэтому эффективность важнее, чем наличие «свободного», «однострочного» кода, который скрывает неэффективность.

  • Обрабатывает несколько значений на ключ. (Не нужно было это самому, но только чтобы замолчать Маурисио;)

    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();
    }

Пример использования

        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);

Вывод

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26
19
2.08.2011 07:54:19
Мне нравится, что это не использует HttpUtility, который находится под System.Web и не доступен везде.
Kugel 13.03.2014 01:50:24
+1 за то, что вы не используете linq и не используете HttpUtility. Я бы создал пустой sb и исключил бы переменную «bool first», а затем в цикле просто имел sb.Append (sb.Length == 0? "?": "&") Перед sb.AppendFormat (). Теперь, если nvc пуст, метод возвратит пустую строку вместо одиночного «?».
Mathew Leger 15.09.2016 01:52:00
Этот ответ обрабатывает отдельные параметры с несколькими значениями. например? id = 1 & id = 3 & id = 2 & id = 9
Mathemats 9.06.2017 04:08:22

Цепной класс-оболочка для 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();
        }
    }
}

Пример использования:

QueryStringBuilder parameters = new QueryStringBuilder()
    .Add("view", ViewBag.PageView)
    .Add("page", ViewBag.PageNumber)
    .Add("size", ViewBag.PageSize);
string queryString = parameters.ToString();
2
9.11.2019 22:10:30

Я написал помощника для моего бритвенного проекта, используя некоторые подсказки из других ответов.

Бизнес ParseQueryString необходим, потому что нам не разрешено вмешиваться в объект QueryString текущего запроса.

@helper GetQueryStringWithValue(string key, string value) {
    var queryString = System.Web.HttpUtility.ParseQueryString(HttpContext.Current.Request.QueryString.ToString());
    queryString[key] = value;
    @Html.Raw(queryString.ToString())
}

Я использую это так:

location.search = '?@Helpers.GetQueryStringWithValue("var-name", "var-value")';

Если вы хотите, чтобы оно принимало более одного значения, просто измените параметры на словарь и добавьте пары в строку запроса.

0
11.10.2011 08:32:43

Вот беглый / лямбда-выход в качестве метода расширения (объединение понятий в предыдущих статьях), который поддерживает несколько значений для одного и того же ключа. Мое личное предпочтение - это расширение над оболочками для возможности обнаружения другими членами команды для подобных вещей. Обратите внимание, что существуют разногласия по поводу методов кодирования, об этом много публикаций о переполнении стека (одна такая публикация ) и блоггерах MSDN (как эта ).

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: с нулевой поддержкой, хотя вам, вероятно, придется адаптировать ее для вашей конкретной ситуации

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;
}
29
23.05.2017 12:26:36
Это терпит неудачу, если любое из значений является нулем
Josh Noe 19.08.2012 17:56:57
это неправильно, он генерирует много строк запроса для каждой пары ключ-значение
Gayan 24.07.2014 05:03:42
@GayanRanasinghe: Что это вообще значит?
Matti Virkkunen 12.02.2015 17:44:08

Я написал несколько методов расширения, которые я нашел очень полезными при работе с QueryStrings. Часто я хочу начать с текущей строки QueryString и изменить ее перед использованием. Что-то вроде,

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

Для получения дополнительной информации и источника: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

Это простой, но мне нравится стиль.

1
18.03.2012 20:45:59

Вдохновленный комментариями Роя Тинкера, я использовал простой метод расширения класса Uri, который делает мой код лаконичным и чистым:

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;
    }
}

Применение:

Uri url = new Uri("http://localhost/rest/something/browse").
          AddQuery("page", "0").
          AddQuery("pageSize", "200");

Редактировать - Соответствующий стандартам вариант

Как отметили несколько человек, httpValueCollection.ToString()кодирует символы Unicode нестандартным образом. Это вариант того же метода расширения, который обрабатывает такие символы, вызывая HttpUtility.UrlEncodeметод вместо устаревшего 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;
}
93
8.11.2017 07:13:12
Отлично. Добавлено в мою внутреннюю библиотеку. :)
Andy 17.06.2013 14:06:51
Вы также должны URL кодировать значение. queryString.Add (name, Uri.EscapeDataString (value));
Ufuk Hacıoğulları 1.08.2013 10:25:16
Спасибо за улучшение этого ответа. Исправлена ​​проблема с многобайтовыми символами.
Ufuk Hacıoğulları 13.09.2013 07:57:07
Примечание: это не работает с относительными URL, потому что вы не можете создать экземпляр UriBuilder из относительного Uri.
Yuriy Faktorovich 26.12.2013 23:13:21
Я добавил ключ удаления, чтобы дубликат не мог быть добавлен. dotnetfiddle.net/hTlyAd
Paul Totzke 7.11.2017 23:24:39

Приведенный ниже код снят с HttpValueCollectionреализации ToStringчерез ILSpy, который дает вам строку запроса name = value.

К сожалению, HttpValueCollection является внутренним классом, который вы можете получить только в случае использования HttpUtility.ParseQueryString(). Я удалил все части viewstate к нему, и он кодирует по умолчанию:

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
28.07.2012 19:19:55

Это идентично принятому ответу, за исключением чуть более компактного:

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

Просто для тех, кому нужна VB.NET-версия топ-ответа:

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

И версия без 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

И версия C # без 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
7.06.2013 09:07:32

Добавьте этот класс в ваш проект

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()))));
    }
}

И используйте это так:

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
8.06.2015 13:48:15
Теперь это должен быть принятый ответ; отлично работает для массивов типа "foo [] = 1, foo [] = 2", а также поддерживает порядок параметров, что, кстати, очень важно.
Soroush Falahati 29.10.2018 00:45:31

Просто хотел скинуть 2 моих цента

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))));
    }
}

Документы говорят, что uri.Queryначнется с a, ?если он не пустой, и вы должны его обрезать, если вы собираетесь изменить его.

Обратите внимание, что HttpUtility.UrlEncodeнаходится в System.Web.

Применение:

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

Моя жертва:

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;
}

Применение:

var uri = new Uri("http://stackoverflow.com").AddQuery("such", "method")
                                             .AddQuery("wow", "soFluent");

// http://stackoverflow.com?such=method&wow=soFluent
6
21.02.2014 17:55:06
Я предпочитаю твой подход Simple and Elegant, однако, HttpUtility требует System.Web
Ody 3.03.2017 14:09:50

Flurl [раскрытие: я автор] поддерживает создание строк запроса через анонимные объекты (среди прочих способов):

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!"
});

Необязательная сопутствующая библиотека Flurl.Http позволяет вам выполнять HTTP-вызовы сразу из той же цепочки быстрых вызовов, расширяя ее до полноценного REST-клиента:

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>();

Полный пакет доступен на NuGet:

PM> Install-Package Flurl.Http

или просто автономный построитель URL:

PM> Install-Package Flurl

22
29.09.2018 12:33:59

Работает для нескольких значений на ключ в NameValueCollection.

пример: { {"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
21.04.2014 19:29:36

То же, что и принятое решение, но переведено в синтаксис 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
4.07.2014 11:10:38

Мне нужно было решить ту же проблему для переносимой библиотеки классов (PCL), над которой я работаю. В этом случае у меня нет доступа к System.Web, поэтому я не могу использовать ParseQueryString.

Вместо этого я использовал System.Net.Http.FormUrlEncodedContentтак:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
11
31.07.2014 12:23:12
Это метод, который я использую, и на который я ссылался в другом вопросе http://stackoverflow.com/a/26744471/2108310 Единственное отличие состоит в том, что я использую массив пар KeyValue ... кроме необходимости ссылки на System. Net (который доступен на PCL, как вы заявили), это ИМХО самый простой способ сделать это, не включая какой-либо пакет стороннего производителя, или пытаясь собрать воедино какой-то доморощенный беспорядок спагетти.
Rostov 17.05.2016 18:02:33

Это еще один ( может быть, избыточный: -] ) способ сделать это.

Концептуальные понятия совпадают с ответами Ведрана на этой странице (посмотрите здесь ).

Но этот класс более эффективен, потому что он перебирает все ключи только один раз: когда ToStringвызывается.

Код форматирования также упрощен и улучшен.

Надеюсь, что это может быть полезно.

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
23.05.2017 12:10:45

Комбинировал верхние ответы для создания анонимной версии объекта :

var queryString = HttpUtility2.BuildQueryString(new
{
    key2 = "value2",
    key1 = "value1",
});

Это создает это:

ключ2 = значение2 & ключ1 = значение1

Вот код:

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
26.04.2016 22:37:17