如何在C#中为URL构建查询字符串?

Boa*_*oaz 459 .net c# url query-string

从代码调用Web资源时的常见任务是构建查询字符串以包含所有必需参数.虽然无论如何都没有火箭科学,但是你需要注意一些漂亮的细节,&如果不是第一个参数,编码参数等.

这样做的代码非常简单,但有点单调乏味:

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")); }
}
Run Code Online (Sandbox Code Playgroud)

这是一个常见的任务,人们期望实用程序类存在,使其更加优雅和可读.扫描MSDN,我找不到一个 - 这让我想到了以下问题:

你知道做上述事情最干净的方式是什么?

Joh*_*soe 658

您可以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
Run Code Online (Sandbox Code Playgroud)

HttpValueCollection是内部的,因此您无法直接构造实例.但是,一旦获得实例,就可以像使用任何其他实例一样使用它NameValueCollection.由于您正在使用的实际对象是一个HttpValueCollection,因此调用ToString方法将调用重写的方法HttpValueCollection,该方法将该集合格式化为URL编码的查询字符串.

在搜索SO和网络以寻找类似问题的答案之后,这是我能找到的最简单的解决方案.

.NET核心

如果您在.NET Core中工作,则可以使用Microsoft.AspNetCore.WebUtilities.QueryHelpers该类,这极大地简化了这一过程.

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

  • 对于多字节字符,此方法不符合[标准兼容](http://en.wikipedia.org/wiki/Percent-encoding#Non-standard_implementations).它将它们编码为%uXXXX而不是%XX%XX.Web服务器可能会错误地解释生成的查询字符串.这甚至记录在由HttpUtility.ParseQueryString()返回的内部框架类HttpValueCollection中.评论说他们出于向后兼容的原因保留此行为. (62认同)
  • 注意HttpUtilityPraseQueryString("")和新的NameValueCollection()之间存在重要区别 - 只有HttpUtility结果会覆盖ToString()以生成正确的查询字符串 (25认同)
  • @Finster您可以使用`Add`方法将多个名称实例添加到查询字符串中.即`queryString.Add("type","1"); queryString.Add("type","2");`使用`Add`方法实际上可能是一种更好的方法. (7认同)
  • 您可以为IDictionary接口创建一个名为ToURLQueryString的扩展方法:`public static string ToURLQueryString(this IDictionary dict){...}` (6认同)
  • 在查询字符串中需要多个名称实例的情况如何?例如,"type = 10&type = 21". (5认同)
  • 绝招!我在 [Flurl](https://github.com/tmenier/Flurl) 中使用了它,并在源代码中注明了你。谢谢! (2认同)

ann*_*ata 286

如果你看一下,QueryString属性是一个NameValueCollection.当我做了类似的事情时,我通常对序列化和反序列化感兴趣所以我的建议是建立一个NameValueCollection,然后传递给:

using System.Linq;
using System.Web;
using System.Collections.Specialized;

private string ToQueryString(NameValueCollection nvc)
{
    var array = (
        from key in nvc.AllKeys
        from value in nvc.GetValues(key)
            select string.Format(
                "{0}={1}",
                HttpUtility.UrlEncode(key),
                HttpUtility.UrlEncode(value))
        ).ToArray();
    return "?" + string.Join("&", array);
}
Run Code Online (Sandbox Code Playgroud)

可能我可以格式化更好:)

我想在LINQ中有一个非常优雅的方式来做到这一点......

  • HTTP规范(RFC 2616)没有说明查询字符串可以包含的内容.RFC 3986也没有定义通用URI格式.常用的键/值对格式称为`application/x-www-form-urlencoded`,实际上是由HTML定义的,目的是将表单数据作为`GET`请求的一部分提交.HTML 5不会以这种格式禁止每个键的多个值,实际上它要求浏览器在页面(错误地)包含具有相同"name"属性的多个字段的情况下为每个键生成多个值.见http://goo.gl/uk1Ag (20认同)
  • @annakata:我知道我的评论超过一年(并且答案超过两年!),但NameValueCollection通过使用GetValues(key)方法非常支持每个键的多个值. (14认同)
  • 您可能希望使用Uri.EscapeDataString而不是更便携的HttpUtility.UrlEncode.见http://stackoverflow.com/questions/2573290/httputility-urlencode-in-windows-phone-7 (10认同)
  • 关于每个键的多个值,我相信在HTML中,如果您选择并提交了多个项目的多选列表框,它们将以David提到的多值格式发送. (8认同)
  • @MauricioScheffer:但NameValueCollection没有"正确"转换为查询字符串.例如,如果在WebClient上设置QueryString参数,其中多次出现相同的键,则会变为"path?key = value1,value2"而不是"path?key = value1&key = value2",这是常见的(标准) ?)模式. (4认同)

Ved*_*ran 88

在Roy Tinker的评论的启发下,我最终在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

编辑 - 符合标准的变体

正如一些人所指出的那样,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;
}
Run Code Online (Sandbox Code Playgroud)

  • 注意,这不适用于相对URL,因为您无法从相对Uri实例化UriBuilder. (9认同)
  • 完善.添加到我的内部库中.:) (3认同)
  • 感谢您改进此答案.它修复了多字节字符的问题. (2认同)

Met*_*ymy 62

奇怪的是没有人提到 AspNet.Core 中的 QueryBuilder。

当您有一个带有重复键的查询时,这会很有帮助,例如?filter=a&filter=b

var qb = new QueryBuilder();
qb.Add("filter", new string[] {"A", "B"});
Run Code Online (Sandbox Code Playgroud)

然后您只需将 qb 添加到 URI,它会自动转换为字符串。

https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.extensions.querybuilder?view=aspnetcore-5.0

  • 这里有很多答案,花了一段时间才找到适合当前使用 .NET Core 开发的答案。 (4认同)
  • 这是*正确的*现代解决方案(花了一段时间才找到)。此外,还可以使用“Microsoft.AspNetCore.Http”命名空间中的“QueryString”类:https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.querystring.create?view=aspnetcore -6.0 (2认同)

Iga*_*nik 29

我刚才回答了类似的问题.基本上,最好的方法是使用HttpValueCollectionASP.NET的Request.QueryString属性实际上是类,不幸的是它是.NET框架内部的.您可以使用Reflector来抓取它(并将其放入Utils类中).这样您就可以像NameValueCollection那样操纵查询字符串,但是所有的url编码/解码问题都会得到照顾.

HttpValueCollectionextends 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
Run Code Online (Sandbox Code Playgroud)

  • HttpValueCollection是一个内部类,因此您无法实例化它. (4认同)

Alf*_*red 28

这是一种流畅/ lambda-ish方式作为扩展方法(结合以前帖子中的概念),支持同一个键的多个值.我个人的偏好是对其他团队成员发现能力的包装器的扩展.请注意,围绕编码方法存在争议,在Stack Overflow(一个这样的帖子)和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());
}
Run Code Online (Sandbox Code Playgroud)

编辑:支持null,但您可能需要根据您的具体情况进行调整

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;
}
Run Code Online (Sandbox Code Playgroud)


Tod*_*ier 22

Flurl [披露:我是作者]支持通过匿名对象(以及其他方式)构建查询字符串:

var url = "http://www.some-api.com".SetQueryParams(new
{
    api_key = ConfigurationManager.AppSettings["SomeApiKey"],
    max_results = 20,
    q = "Don't worry, I'll get encoded!"
});
Run Code Online (Sandbox Code Playgroud)

可选的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>();
Run Code Online (Sandbox Code Playgroud)

NuGet提供完整的软件包:

PM> Install-Package Flurl.Http

或者只是独立的URL构建器:

PM> Install-Package Flurl


DSO*_*DSO 19

这是我迟到的条目.我出于各种原因不喜欢其他任何人,所以我写了自己的.

此版本的特点:

  • 仅使用StringBuilder.没有ToArray()调用或其他扩展方法.它看起来并不像其他一些反应那么漂亮,但我认为这是一个核心功能,所以效率比拥有"流畅","单线"代码隐藏低效率更重要.

  • 处理每个键的多个值.(我自己不需要它,只是为了让Mauricio保持沉默;)

    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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

示例用法

        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);
Run Code Online (Sandbox Code Playgroud)

产量

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26
Run Code Online (Sandbox Code Playgroud)


Luk*_*keH 11

如何创建允许您以这样流畅的方式添加参数的扩展方法?

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(); 
Run Code Online (Sandbox Code Playgroud)

这是使用的重载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);
}
Run Code Online (Sandbox Code Playgroud)

这是使用的重载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));
}
Run Code Online (Sandbox Code Playgroud)


hor*_*man 11

我需要为我正在处理的可移植类库(PCL)解决同样的问题.在这种情况下,我无法访问System.Web,因此我无法使用ParseQueryString.

相反,我这样使用System.Net.Http.FormUrlEncodedContent:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
Run Code Online (Sandbox Code Playgroud)


red*_*alx 10

在 dotnet core QueryHelpers.AddQueryString() 将接受键值对的 IDictionary<string,string> 。为了节省一些内存分配和 CPU 周期,您可以使用 SortedList<,> 而不是 Dictionary<,>,并按排序顺序添加适当的容量和项目...

var queryParams = new SortedList<string,string>(2);
queryParams.Add("abc", "val1");
queryParams.Add("def", "val2");

string requestUri = QueryHelpers.AddQueryString("https://localhost/api", queryParams);
Run Code Online (Sandbox Code Playgroud)


Jay*_*ass 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());
    }
Run Code Online (Sandbox Code Playgroud)


Gia*_*rdi 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()))));
    }
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

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"
Run Code Online (Sandbox Code Playgroud)


dav*_*v_i 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;
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

// http://stackoverflow.com?such=method&wow=soFluent
Run Code Online (Sandbox Code Playgroud)


Lui*_*rez 6

结合最佳答案来创建匿名对象版本

var queryString = HttpUtility2.BuildQueryString(new
{
    key2 = "value2",
    key1 = "value1",
});
Run Code Online (Sandbox Code Playgroud)

这会产生这个:

键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();
    }
}
Run Code Online (Sandbox Code Playgroud)


b-z*_*urg 6

这里有很多好的答案,但对于那些使用现代 C# 的人来说,这可能是一个值得保留的不错的实用程序类。

public class QueryParamBuilder
{
    private readonly Dictionary<string, string> _fields = new();
    public QueryParamBuilder Add(string key, string value)
    {
        _fields.Add(key, value);
        return this;
    }
    public string Build()
    {
        return $"?{String.Join("&", _fields.Select(pair => $"{HttpUtility.UrlEncode(pair.Key)}={HttpUtility.UrlEncode(pair.Value)}"))}";
    }
    public static QueryParamBuilder New => new();
}
Run Code Online (Sandbox Code Playgroud)

我在这里使用内部是Dictionary因为字典在内部是可枚举的键值对,这使得迭代它们比NameValueCollection.

那么查询字符串本身就是一个带有连接的简单内插字符串。

此外,我在构造函数中提供了一个静态接口,使新构建器的构造变得非常容易,并且只允许一种公开的方法Add添加新的查询参数值。最后,您终止链以Build()实际获得最终的字符串。

这是其用法的示例

var queryString = QueryParamBuilder.New
     .Add("id", "0123")
     .Add("value2", 1234.ToString())
     .Add("valueWithSpace","value with spa12!@#@!ce")
     .Build();
Run Code Online (Sandbox Code Playgroud)

结果正如预期的那样

?id=0123&value2=1234&valueWithSpace=value+with+spa12!%40%23%40!ce
Run Code Online (Sandbox Code Playgroud)

希望你们中的一些人会发现这美好而优雅。


Nic*_*len 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();
Run Code Online (Sandbox Code Playgroud)


Mar*_*ris 5

基于快速扩展方法的版本:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用 where 子句来选择将哪些参数添加到字符串中。


归档时间:

查看次数:

305174 次

最近记录:

5 年,10 月 前