Вопрос по .net, url, query-string, c# – Как построить строку запроса для URL в C #?

432

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

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

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

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

Вы ничего не пропускаете. Создание Querystring - это серьезный пробел в структуре, которую я пытался заполнитьFlurl. Todd Menier
Немного грустно, что даже в текущий момент временипростой способ справиться со строками запроса. И под простым понятием я подразумеваю внеклассный, не соответствующий стандартам стандарт. Или может я что-то упускаю? Grimace of Despair
Вы только что заставили меня подумать, что я должен создать один .. новый UrlBuilder (существующий) .AddQuery ("ключ", "значение"). ToString () Demetris Leptos

Ваш Ответ

30   ответов
0

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

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

Но этот класс более эффективен, потому что он перебирает все ключи только один раз: когда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();
    }
}
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();
Обратите внимание, что это не допускает нескольких значений на ключ ... Mauricio Scheffer
изменил имя класса на QueryString .. Query немного двусмысленно Nick Allen
Да, согласился LOL, я положу редактирование в Nick Allen
красиво инкапсулирован, но этот формат в "? {0}" излишне дорогой :) annakata
275

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

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

я использовал простой метод расширения класса 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() кодирует символы Юникода внеэталоны-совместимый путь. Это вариант того же метода расширения, который обрабатывает такие символы, вызывая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;
}
Спасибо за улучшение этого ответа. Исправлена проблема с многобайтовыми символами. Ufuk Hacıoğulları
Вы также должны URL кодировать значение. queryString.Add (name, Uri.EscapeDataString (value)); Ufuk Hacıoğulları
Отлично. Добавлено в мою внутреннюю библиотеку. :) Andy
Хороший и чистый способ! +1 Osman Turan
Примечание: это не работает с относительными URL, потому что вы не можете создать экземпляр UriBuilder из относительного Uri. Yuriy Faktorovich
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());
    }
Приятно! Но вам не нужно.ToArray()s. mpen
просто и элегантно SpoiledTechie.com
0

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

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

2

Uri это:

Принимает анонимные объекты:uri.WithQuery(new { name = "value" })Принимает коллекцииstring/string пары (например,Dictionary`2).Принимает коллекцииstring/object пары (например,RouteValueDictionary).ПринимаетNameValueCollections.Сортирует значения запроса по ключу, поэтому одинаковые значения дают одинаковые 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"));
0

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
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
Я предпочитаю твой подход Simple and Elegant, однако для HttpUtility требуется System.Web Ody
0

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

точка" ...

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

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

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

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

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

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);
        }
    }
}
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", а также поддерживает порядок параметров, что, кстати, очень важно. Soroush Falahati
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 минимальна (я думаю,Джефф Этвуд выложил что нибудь по этой теме).

21

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

{
    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

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

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

аналогичный вопрос Некоторое время назад В принципе, лучшим способом было бы использовать классHttpValueCollectionкакие ASP.NETRequest.QueryString свойство на самом деле, к сожалению, это внутреннее в .NET Framework. Вы можете использовать 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 (), который действует по-другому, но не может понять, почему. calebt
HttpValueCollection является внутренним классом, поэтому вы не можете создать его экземпляр. ozba
httpValueCollection.ToString() на самом деле звонкиhttpValueCollection.ToString(true) так что добавлениеtrue простота не требуется. dav_i
@ozba, что если он является членом команды .NET и не осознает этого? AgentFire
630

Вы можете создать новый доступный для записи экземпляр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/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers

@Finster Вы можете добавить несколько экземпляров имени в строку запроса, используяAdd метод. т.е.queryString.Add("type", "1"); queryString.Add("type", "2"); С помощьюAdd метод, вероятно, лучший способ делать это все время на самом деле. jeremysawesome
Как насчет случаев, когда вам нужно несколько экземпляров имени в строке запроса? Например, «type = 10 & type = 21». Finster
Этот метод неСтандарт-совместимый для многобайтовых символов. Он будет кодировать их как% uXXXX вместо% XX% XX. Результирующие строки запроса могут быть неправильно интерпретированы веб-серверами. Это даже задокументировано во внутреннем каркасном классе HttpValueCollection, возвращаемом HttpUtility.ParseQueryString (). Комментарий говорит, что они сохраняют это поведение по причинам обратной совместимости. alex
Возможно, вы могли бы создать метод расширения с именем ToURLQueryString для интерфейса IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... } Roy Tinker
Обратите внимание, что между HttpUtilityPraseQueryString ("") и новым NameValueCollection () есть важное различие - только результат HttpUtility будет переопределять ToString () для создания правильной строки запроса Frank Schwieterman
28

нятий в предыдущих статьях), который поддерживает несколько значений для одного и того же ключа. Мое личное предпочтение - расширение над оболочками для возможности обнаружения другими членами команды для подобных вещей. Обратите внимание, что существуют противоречия по поводу методов кодирования, об этом написано множество сообщений о переполнении стека (один такойПочта) и блоггеры 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;
}
это неправильно, он генерирует много строк запроса для каждой пары ключ-значение Gayan
Это терпит неудачу, если любое из значений является нулем Josh Noe
@GayanRanasinghe: Что это вообще значит? Matti Virkkunen
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
Мне нравится, что это не использует HttpUtility, который находится под System.Web и не доступен везде. Kugel
Этот ответ обрабатывает отдельные параметры с несколькими значениями. например? id = 1 & id = 3 & id = 2 & id = 9 Mathemats
+1 за то, что не использовал linq и не использовал HttpUtility. Я бы создал пустой sb и исключил бы переменную «bool first», а затем в цикле просто имел бы sb.Append (sb.Length == 0? "?": "&") Перед sb.AppendFormat (). Теперь, если nvc пуст, метод вернет пустую строку вместо одиночного «?». Mathew Leger
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();

Похожие вопросы