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

473

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

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

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, я не смог найти ни одного, что приводит меня к следующему вопросу:

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

Боаз
источник
26
Немного грустно, что даже в текущий момент времени, кажется, нет простого способа справиться со строками запроса. И под простым понятием я подразумеваю внеклассный, не соответствующий стандартам стандарт. А может я что-то упускаю?
Гримаса Отчаяния
5
Вы ничего не пропускаете. Создание Querystring - это серьезный пробел в структуре, которую я пытался заполнить Flurl .
Тодд Менье
Вы только что заставили меня подумать, что я должен создать один .. новый UrlBuilder (существующий) .AddQuery ("ключ", "значение"). ToString ()
Деметрис

Ответы:

293

Если вы заглянете внутрь, свойство 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 тоже есть супер элегантный способ сделать это ...

annakata
источник
22
Спецификация HTTP (RFC 2616) ничего не говорит о том, что могут содержать строки запроса. Также не RFC 3986, который определяет общий формат URI. Формат пары ключ / значение, который обычно используется, называется application/x-www-form-urlencodedи фактически определяется HTML с целью отправки данных формы как части GETзапроса. HTML 5 не запрещает использование нескольких значений на ключ в этом формате и фактически требует, чтобы браузер выдавал несколько значений на ключ в случае, если страница (неправильно) содержит несколько полей с одним и тем же nameатрибутом. Смотрите goo.gl/uk1Ag
Даниэль Кэссиди
14
@annakata: я знаю, что моему комментарию более года (а ответу более двух лет!), но NameValueCollection очень поддерживает несколько значений на ключ, используя метод GetValues ​​(ключ).
Маурисио Шеффер
4
@MauricioScheffer: Но NameValueCollection не преобразуется в строку запроса «правильно». Например, если вы установите параметр QueryString в WebClient, где один и тот же ключ присутствует несколько раз, он превращается в «путь? Ключ = значение1, значение2» вместо «путь? Ключ = значение1 и ключ = значение2», что является распространенным (стандартным ?) шаблон.
Дэвид Папа
8
Что касается нескольких значений на ключ, я полагаю, что в HTML, если у вас есть список с несколькими вариантами выбора с несколькими выбранными и отправленными элементами, они отправляются в формате с несколькими значениями, упомянутым Дэвидом.
Сэм
10
Возможно, вы захотите использовать Uri.EscapeDataString вместо HttpUtility.UrlEncode, который является более переносимым. См. Stackoverflow.com/questions/2573290/…
PEK
689

Вы можете создать новый доступный для записи экземпляр 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));
Джон Бледсо
источник
6
Возможно, вы могли бы создать метод расширения с именем ToURLQueryString для интерфейса IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Рой Тинкер
65
Этот метод не соответствует стандарту для многобайтовых символов. Он будет кодировать их как% uXXXX вместо% XX% XX. Результирующие строки запроса могут быть неправильно интерпретированы веб-серверами. Это даже задокументировано во внутреннем каркасном классе HttpValueCollection, возвращаемом HttpUtility.ParseQueryString (). Комментарий говорит, что они сохраняют это поведение по причинам обратной совместимости.
Алекс
25
Обратите внимание, что между HttpUtilityPraseQueryString ("") и новым NameValueCollection () есть важное различие - только результат HttpUtility переопределит ToString () для создания правильной
строки
7
Как насчет случаев, когда вам нужно несколько экземпляров имени в строке запроса? Например, «type = 10 & type = 21».
Финстер
7
@Finster Вы можете добавить несколько экземпляров имени в строку запроса, используя Addметод. Т.е. queryString.Add("type", "1"); queryString.Add("type", "2"); использование Addметода, вероятно, является лучшим способом сделать это все время на самом деле.
jeremysawesome
94

Вдохновленный комментариями Роя Тинкера, я использовал простой метод расширения класса 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;
}
Ведран
источник
3
Отлично. Добавлено в мою внутреннюю библиотеку. :)
Энди
1
Вы также должны URL кодировать значение. queryString.Add (name, Uri.EscapeDataString (value));
Ufuk Hacıoğulları
2
Спасибо за улучшение этого ответа. Исправлена ​​проблема с многобайтовыми символами.
Ufuk Hacıoğulları
9
Примечание: это не работает с относительными URL, потому что вы не можете создать экземпляр UriBuilder из относительного Uri.
Юрий Факторович
1
Я добавил ключ удаления, чтобы дубликат не мог быть добавлен. dotnetfiddle.net/hTlyAd
Пол Тоцке
29

Я ответил на аналогичный вопрос некоторое время назад. По сути, лучшим способом было бы использовать класс 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
Игаль табачник
источник
Спасибо ... я заметил, что NameValueCollection, который он возвращает, имеет ToString (), который действует по-другому, но не может понять, почему.
Калебт
httpValueCollection.ToString()на самом деле звонки, httpValueCollection.ToString(true)так что добавление trueпростоты не требуется.
Дав_и
5
HttpValueCollection является внутренним классом, поэтому вы не можете создать его экземпляр.
Озба
29

Вот беглый / лямбда-выход в качестве метода расширения (объединение понятий в предыдущих статьях), который поддерживает несколько значений для одного и того же ключа. Мое личное предпочтение - расширение над оболочками для возможности обнаружения другими членами команды для подобных вещей. Обратите внимание, что существуют противоречия по поводу методов кодирования, об этом много публикаций о переполнении стека (одна такая публикация ) и блоггерах 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;
}
Альфред
источник
1
Это терпит неудачу, если любое из значений является нулем
Джош Ноу
это неправильно, он генерирует много строк запроса для каждой пары ключ-значение
Гаян
@GayanRanasinghe: Что это вообще значит?
Матти Вирккунен
22

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

Тодд менье
источник
20

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

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

  • Использование только 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
DSO
источник
Мне нравится, что это не использует HttpUtility, который находится под System.Web и не доступен везде.
Кугель
+1 за то, что не использовал linq и не использовал HttpUtility. Я бы создал пустой sb и исключил бы переменную «bool first», а затем в цикле просто имел бы sb.Append (sb.Length == 0? "?": "&") Перед sb.AppendFormat (). Теперь, если nvc пуст, метод возвратит пустую строку вместо одиночного «?».
Мэтью Леже
Этот ответ обрабатывает отдельные параметры с несколькими значениями. например? id = 1 & id = 3 & id = 2 & id = 9
Математики
12

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

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));
}
LukeH
источник
: +1: для простого метода расширения на основе строк. Некоторые другие ответы могут охватывать более крайние случаи, но это достаточно для моей ситуации, и это не требует , чтобы я построить NameValueCollection, HttpValueCollectionили Uriпервый. Спасибо!
Стэнли Дж.
11

Мне нужно было решить ту же проблему для переносимой библиотеки классов (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;
hortman
источник
Это метод, который я использую, и на который я ссылался в другом вопросе http://stackoverflow.com/a/26744471/2108310 Единственное отличие состоит в том, что я использую массив пар KeyValue ... кроме необходимости ссылки на System. Net (который доступен на PCL, как вы заявили), это ИМХО самый простой способ сделать это, не включая какой-либо пакет стороннего производителя, или не пытаясь собрать воедино какой-то домашний беспорядок спагетти.
Ростов
9
    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());
    }
Джей Дуглас
источник
1
Ницца! Но вам не нужны .ToArray()с.
mpen
7

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

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"
Джан Марко Герарди
источник
Теперь это должен быть принятый ответ; отлично работает для массивов типа "foo [] = 1, foo [] = 2", а также поддерживает порядок параметров, что, кстати, очень важно.
Сооруш Фалахати
6

Моя жертва:

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
dav_i
источник
Я предпочитаю ваш подход Простой и Элегантный, однако, HttpUtility требует System.Web
Ody
5

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

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();
Ник Аллен
источник
красиво инкапсулирован, но этот формат в "? {0}" излишне дорогой :)
annakata
изменил имя класса на QueryString .. Query немного двусмысленно
Ник Аллен
4

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

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, чтобы выбрать, какие параметры будут добавлены в строку.

Мартин Харрис
источник
3

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

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

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

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();
    }
}
Луис Перес
источник
2

То же, что и принятое решение, но переведено в синтаксис 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;
}
Tomino
источник
2

У меня есть метод расширения для Uri, который:

  • Принимает анонимные объекты: uri.WithQuery(new { name = "value" })
  • Принимает коллекции string/stringпар (например, Dictionary`2 ).
  • Принимает коллекции string/objectпар (например, RouteValueDictionary ).
  • Принимает NameValueCollection s.
  • Сортирует значения запроса по ключу, поэтому одинаковые значения дают одинаковые URI.
  • Поддерживает несколько значений на ключ, сохраняя их первоначальный порядок.

Документированная версия может быть найдена здесь .

Расширение:

public static Uri WithQuery(this Uri uri, object values)
{
    if (uri == null)
        throw new ArgumentNullException(nameof(uri));

    if (values != null)
    {
        var query = string.Join(
            "&", from p in ParseQueryValues(values)
                 where !string.IsNullOrWhiteSpace(p.Key)
                 let k = HttpUtility.UrlEncode(p.Key.Trim())
                 let v = HttpUtility.UrlEncode(p.Value)
                 orderby k
                 select string.IsNullOrEmpty(v) ? k : $"{k}={v}");

        if (query.Length != 0 || uri.Query.Length != 0)
            uri = new UriBuilder(uri) { Query = query }.Uri;
    }

    return uri;
}

Парсер запросов:

private static IEnumerable<KeyValuePair<string, string>> ParseQueryValues(object values)
{
    // Check if a name/value collection.
    var nvc = values as NameValueCollection;
    if (nvc != null)
        return from key in nvc.AllKeys
               from val in nvc.GetValues(key)
               select new KeyValuePair<string, string>(key, val);

    // Check if a string/string dictionary.
    var ssd = values as IEnumerable<KeyValuePair<string, string>>;
    if (ssd != null)
        return ssd;

    // Check if a string/object dictionary.
    var sod = values as IEnumerable<KeyValuePair<string, object>>;
    if (sod == null)
    {
        // Check if a non-generic dictionary.
        var ngd = values as IDictionary;
        if (ngd != null)
            sod = ngd.Cast<dynamic>().ToDictionary<dynamic, string, object>(
                p => p.Key.ToString(), p => p.Value as object);

        // Convert object properties to dictionary.
        if (sod == null)
            sod = new RouteValueDictionary(values);
    }

    // Normalize and return the values.
    return from pair in sod
           from val in pair.Value as IEnumerable<string>
            ?? new[] { pair.Value?.ToString() }
           select new KeyValuePair<string, string>(pair.Key, val);
}

Вот тесты:

var uri = new Uri("https://stackoverflow.com/yo?oldKey=oldValue");

// Test with a string/string dictionary.
var q = uri.WithQuery(new Dictionary<string, string>
{
    ["k1"] = string.Empty,
    ["k2"] = null,
    ["k3"] = "v3"
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2&k3=v3"));

// Test with a string/object dictionary.
q = uri.WithQuery(new Dictionary<string, object>
{
    ["k1"] = "v1",
    ["k2"] = new[] { "v2a", "v2b" },
    ["k3"] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3"));

// Test with a name/value collection.
var nvc = new NameValueCollection()
{
    ["k1"] = string.Empty,
    ["k2"] = "v2a"
};

nvc.Add("k2", "v2b");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2=v2a&k2=v2b"));

// Test with any dictionary.
q = uri.WithQuery(new Dictionary<int, HashSet<string>>
{
    [1] = new HashSet<string> { "v1" },
    [2] = new HashSet<string> { "v2a", "v2b" },
    [3] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?1=v1&2=v2a&2=v2b&3"));

// Test with an anonymous object.
q = uri.WithQuery(new
{
    k1 = "v1",
    k2 = new[] { "v2a", "v2b" },
    k3 = new List<string> { "v3" },
    k4 = true,
    k5 = null as Queue<string>
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3=v3&k4=True&k5"));

// Keep existing query using a name/value collection.
nvc = HttpUtility.ParseQueryString(uri.Query);
nvc.Add("newKey", "newValue");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?newKey=newValue&oldKey=oldValue"));

// Merge two query objects using the RouteValueDictionary.
var an1 = new { k1 = "v1" };
var an2 = new { k2 = "v2" };

q = uri.WithQuery(
    new RouteValueDictionary(an1).Concat(
        new RouteValueDictionary(an2)));

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2"));
Шафак Гюр
источник
2

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

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

Я написал несколько методов расширения, которые я нашел очень полезными при работе с 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

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

ccook
источник
1

Просто хотел скинуть 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начнется с? если он не пустой, и вы должны обрезать его, если вы собираетесь изменить его.

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

Применение:

var uri = new Uri("https://api.del.icio.us/v1/posts/suggest").AddQueryParam("url","http://stackoverflow.com")
mpen
источник
1

Хотя это и не элегантно, я выбрал более простую версию, которая не использует NameValueCollecitons- просто шаблон построителя, обернутый вокруг StringBuilder.

public class UrlBuilder
{
    #region Variables / Properties

    private readonly StringBuilder _builder;

    #endregion Variables / Properties

    #region Constructor

    public UrlBuilder(string urlBase)
    {
        _builder = new StringBuilder(urlBase);
    }

    #endregion Constructor

    #region Methods

    public UrlBuilder AppendParameter(string paramName, string value)
    {
        if (_builder.ToString().Contains("?"))
            _builder.Append("&");
        else
            _builder.Append("?");

        _builder.Append(HttpUtility.UrlEncode(paramName));
        _builder.Append("=");
        _builder.Append(HttpUtility.UrlEncode(value));

        return this;
    }

    public override string ToString()
    {
        return _builder.ToString();
    }

    #endregion Methods
}

Согласно существующим ответам, я убедился, что использовал HttpUtility.UrlEncodeзвонки. Используется так:

string url = new UrlBuilder("http://www.somedomain.com/")
             .AppendParameter("a", "true")
             .AppendParameter("b", "muffin")
             .AppendParameter("c", "muffin button")
             .ToString();
// Result: http://www.somedomain.com?a=true&b=muffin&c=muffin%20button
Эндрю Грей
источник
1
// USAGE
[TestMethod]
public void TestUrlBuilder()
{
    Console.WriteLine(
        new UrlBuilder("http://www.google.com?A=B")
            .AddPath("SomePathName")
            .AddPath("AnotherPathName")
            .SetQuery("SomeQueryKey", "SomeQueryValue")
            .AlterQuery("A", x => x + "C"));
}

Вывод:

http://www.google.com/SomePathName/AnotherPathName?A=BC&SomeQueryKey=SomeQueryValue

Код; Вы все можете поблагодарить меня где-нибудь, как-то: D

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

// By Demetris Leptos
namespace TheOperator.Foundation.Web
{
    public class UrlBuilder
    {
        public string Scheme { get; set; }

        public string Host { get; set; }

        public int? Port { get; set; }

        public List<string> Paths { get; set; }

        public SortedDictionary<string, string> QueryPairs { get; set; }

        public UrlBuilder(string url)
        {
            this.Paths = new List<string>();
            this.QueryPairs = new SortedDictionary<string, string>();

            string path = null;
            string query = null;
            Uri relativeUri = null;
            if (!Uri.TryCreate(url, UriKind.Relative, out relativeUri))
            {
                var uriBuilder = new UriBuilder(url);
                this.Scheme = uriBuilder.Scheme;
                this.Host = uriBuilder.Host;
                this.Port = uriBuilder.Port;
                path = uriBuilder.Path;
                query = uriBuilder.Query;
            }
            else
            {
                var queryIndex = url.IndexOf('?');
                if (queryIndex >= 0)
                {
                    path = url.Substring(0, queryIndex);
                    query = url.Substring(queryIndex + 1);
                }
                else
                {
                    path = url;
                }
            }
            this.Paths.AddRange(path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries));
            if (query != null)
            {
                var queryKeyValuePairs = HttpUtility.ParseQueryString(query);
                foreach (var queryKey in queryKeyValuePairs.AllKeys)
                {
                    this.QueryPairs[queryKey] = queryKeyValuePairs[queryKey];
                }
            }
        }

        public UrlBuilder AddPath(string value)
        {
            this.Paths.Add(value);
            return this;
        }

        public UrlBuilder SetQuery(string key, string value)
        {
            this.QueryPairs[key] = value;
            return this;
        }

        public UrlBuilder RemoveQuery(string key)
        {
            this.QueryPairs.Remove(key);
            return this;
        }

        public UrlBuilder AlterQuery(string key, Func<string, string> alterMethod, bool removeOnNull = false)
        {
            string value;
            this.QueryPairs.TryGetValue(key, out value);
            value = alterMethod(value);
            if (removeOnNull && value == null)
            {
                return this.RemoveQuery(key);
            }
            else
            {
                return this.SetQuery(key, value);
            }
        }

        public override string ToString()
        {
            var path = !string.IsNullOrWhiteSpace(this.Host)
                ? string.Join("/", this.Host, string.Join("/", this.Paths))
                : string.Join("/", this.Paths);
            var query = string.Join("&", this.QueryPairs.Select(x => string.Concat(x.Key, "=", HttpUtility.UrlEncode(x.Value))));
            return string.Concat(
                !string.IsNullOrWhiteSpace(this.Scheme) ? string.Concat(this.Scheme, "://") : null,
                path,
                !string.IsNullOrWhiteSpace(query) ? string.Concat("?", query) : null);
        }
    }
}
Деметрис Лептос
источник
1

Я пошел с решением, предложенным DSO (ответ 2 августа 2011 года в 7:29), его решение не требует использования HttpUtility. Однако, согласно статье, опубликованной в Dotnetpearls , использование словаря быстрее (по производительности), чем использование NameValueCollection. Вот решение DSO, измененное для использования словаря вместо NameValueCollection.

    public static Dictionary<string, string> QueryParametersDictionary()
    {
        var dictionary = new Dictionary<string, string>();
        dictionary.Add("name", "John Doe");
        dictionary.Add("address.city", "Seattle");
        dictionary.Add("address.state_code", "WA");
        dictionary.Add("api_key", "5352345263456345635");

        return dictionary;
    }

    public static string ToQueryString(Dictionary<string, string> nvc)
    {
        StringBuilder sb = new StringBuilder();

        bool first = true;

        foreach (KeyValuePair<string, string> pair in nvc)
        {
                if (!first)
                {
                    sb.Append("&");
                }

                sb.AppendFormat("{0}={1}", Uri.EscapeDataString(pair.Key), Uri.EscapeDataString(pair.Value));

                first = false;
        }

        return sb.ToString();
    }
Ticus
источник
1

Строка запроса может быть добавлена ​​в URL:

  1. создать объект коллекции значения имени
  2. добавить элементы строки запроса и их значения к этому объекту
  3. закодируйте этот объект коллекции значений имени в URL, код которого приведен в ссылке ниже

https://blog.codingnovice.com/blog

public ActionResult Create()
{
    //declaring name value collection object
    NameValueCollection collection = new NameValueCollection();

    //adding new value to the name value collection object
    collection.Add("Id1", "wwe323");
    collection.Add("Id2", "454w");
    collection.Add("Id3", "tyt5656");
    collection.Add("Id4", "343wdsd");

    //generating query string
    string url = GenerateQueryString(collection);

    return View();
}

private string GenerateQueryString(NameValueCollection collection)
{
    var querystring = (
        from key in collection.AllKeys
        from value in collection.GetValues(key)
        select string.Format("{0}={1}",
            HttpUtility.UrlEncode(key),
            HttpUtility.UrlEncode(value))
    ).ToArray();
    return "?" + string.Join("&", querystring);
}
работа
источник
0

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

Бизнес 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")';

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

Loas
источник
0

Приведенный ниже код снят с 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

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

private string ToQueryString(NameValueCollection nvc)
{
    return "?" + string.Join("&", nvc.AllKeys.Select(k => string.Format("{0}={1}", 
        HttpUtility.UrlEncode(k), 
        HttpUtility.UrlEncode(nvc[k]))));
}
Этот парень
источник
0

Просто для тех, кому нужна 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
Стефан Штайгер
источник