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

408

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

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

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

Какой самый элегантный способ, которым вы знаете, что вы делаете выше?

  • 24
    Немного грустно, что даже в текущий момент времени, кажется, нет простого способа справиться со строками запроса. И под простым понятием я подразумеваю внеклассный, не соответствующий стандартам стандарт. А может я что-то упускаю?
  • 4
    Вы ничего не пропускаете. Создание Querystring - основной пробел в структуре, которую я пытался заполнить Flurl .
Показать ещё 2 комментария
Теги:
url
query-string

34 ответа

270
Лучший ответ

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

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 тоже есть элегантный способ сделать это...

  • 0
    отличное решение! +1
  • 18
    Спецификация HTTP (RFC 2616) ничего не говорит о том, что могут содержать строки запроса. Также не RFC 3986, который определяет общий формат URI. Формат пары ключ / значение, который обычно используется, называется application/x-www-form-urlencoded и фактически определяется HTML с целью отправки данных формы как части запроса GET . HTML 5 не запрещает использование нескольких значений на ключ в этом формате и фактически требует, чтобы браузер выдавал несколько значений на ключ в случае, если страница (неправильно) содержит несколько полей с одним и тем же атрибутом name . Смотрите goo.gl/uk1Ag
Показать ещё 10 комментариев
605

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

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

queryString["key1"] = "value1";
queryString["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/aspnet/core/api/microsoft.aspnetcore.webutilities.queryhelpers

  • 6
    Возможно, вы могли бы создать метод расширения с именем ToURLQueryString для интерфейса IDictionary: public static string ToURLQueryString(this IDictionary dict) { ... }
  • 60
    Этот метод не соответствует стандарту для многобайтовых символов. Он будет кодировать их как% uXXXX вместо% XX% XX. Результирующие строки запроса могут быть неправильно интерпретированы веб-серверами. Это даже задокументировано во внутреннем каркасном классе HttpValueCollection, возвращаемом HttpUtility.ParseQueryString (). Комментарий говорит, что они сохраняют это поведение по причинам обратной совместимости.
Показать ещё 8 комментариев
76

С вдохновением от комментария Роя Тинкера, я закончил использовать простой метод расширения на классе 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;
}
  • 4
    Хороший и чистый способ! +1
  • 3
    Отлично. Добавлено в мою внутреннюю библиотеку. :)
Показать ещё 12 комментариев
31

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

HttpValueCollection extends 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
  • 0
    Спасибо ... я заметил, что NameValueCollection, который он возвращает, имеет ToString (), который действует по-другому, но не может понять, почему.
  • 0
    httpValueCollection.ToString() фактически вызывает httpValueCollection.ToString(true) поэтому добавление true httpValueCollection.ToString(true) не требуется.
Показать ещё 2 комментария
29

Здесь свободный /lambda -ish способ как метод расширения (объединяющий понятия в предыдущих сообщениях), который поддерживает несколько значений для одного и того же ключа. Мое личное предпочтение - это расширения над оболочками для возможностей обнаружения другими членами команды для таких вещей. Обратите внимание, что существуют споры вокруг методов кодирования, множество сообщений об этом в Qaru (один такой post) и блогеры 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());
}

изменить: с нулевой поддержкой, хотя вам, вероятно, придется адаптировать его для конкретной ситуации

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
    Это терпит неудачу, если любое из значений является нулем
  • 0
    это неправильно, он генерирует много строк запроса для каждой пары ключ-значение
Показать ещё 1 комментарий
18

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 companion lib позволяет вам делать HTTP-звонки с одной и той же безнадежной цепочкой вызовов, расширяя ее до полномасштабного клиента REST:

await "https://api.mysite.com"
    .AppendPathSegment("person")
    .SetQueryParams(new { ap_key = "my-key" })
    .WithOAuthBearerToken("MyToken")
    .PostJsonAsync(new { first_name = firstName, last_name = lastName });

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

PM> Install-Package Flurl.Http

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

PM> Install-Package Flurl

18

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

Эта версия поддерживает:

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

Мне нужно было решить ту же проблему для переносимой библиотеки классов (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;
  • 0
    Это метод, который я использую, и на который я ссылался в другом вопросе http://stackoverflow.com/a/26744471/2108310 Единственное отличие состоит в том, что я использую массив пар KeyValue ... кроме необходимости ссылки на System. Net (который, как вы заявили, доступен на PCL), это ИМХО самый простой способ сделать это без включения какого-либо стороннего пакета или попытки собрать воедино какой-то доморощенный спагетти-бардак.
10

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

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));
}
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() s.
  • 0
    просто и элегантно
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
  • 0
    Я предпочитаю твой подход Simple and Elegant, однако для HttpUtility требуется System.Web
6

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

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"
  • 0
    Теперь это должен быть принятый ответ; отлично работает для массивов типа "foo [] = 1, foo [] = 2", а также поддерживает порядок параметров, что, кстати, очень важно.
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
    красиво инкапсулирован, но этот формат в "? {0}" излишне дорогой :)
  • 0
    Да, согласился LOL, я положу редактирование в
Показать ещё 2 комментария
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

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

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

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

Объедините верхние ответы, чтобы создать версию анонимного объекта:

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

То же самое, что и принятое решение, но трансфинировано для синтаксиса LINK "точка"...

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

[Также поздняя запись]

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

Просто хотел бросить мои 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")
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

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

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

В то время как я не элегантный, я выбрал более простую версию, которая не использует 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
0

Это еще один способ (возможно избыточный: -]).

Концепции совпадают с ответом Vedran на этой странице (посмотрите здесь).

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

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

ex: { {"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

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

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

Это совпадает с принятым ответом, кроме немного более компактного:

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

Ниже приведен код реализации HttpValueCollection ToString, через ILSpy, который дает вам имя = значение querystring.

К сожалению, 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

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

Бизнес 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 = '[email protected]("var-name", "var-value")';

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

-2

Вот реализация, в которой используются очень простые языковые функции. Это часть класса, который мы должны переносить и поддерживать в Objective 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
    Обратите внимание, что это не допускает нескольких значений на ключ.
-4
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();
}
  • 2
    Обратите внимание, что это не допускает нескольких значений на ключ.
-4

EDIT - как указано в комментариях, это не путь.

Существует такой класс - класс URI. "Предоставляет объектное представление единого идентификатора ресурса (URI) и легкий доступ к частям URI". (Документы Microsoft).

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

Пример С#

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

WebRequest wr = WebRequest.Create(siteUri);

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

  • 1
    -1: он спросил о строке запроса.
  • 0
    класс Uri не имеет методов для управления строкой запроса, кроме получения и установки (и я не уверен насчет последнего)
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню