关于.net:如何在C#中为URL构建查询字符串?

How to build a query string for a URL in C#?

从代码调用Web资源时的一个常见任务是构建一个查询字符串,以包含所有必需的参数。尽管绝对没有火箭科学,但你需要注意一些漂亮的细节,比如,如果不是第一个参数,附加一个&,编码参数等。

要做的代码非常简单,但有点乏味:

1
2
3
4
5
6
7
8
9
10
11
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时,我找不到一个—,这使我遇到了以下问题:

你所知道的最优雅、最干净的方法是什么?


您可以通过调用System.Web.HttpUtility.ParseQueryString(string.Empty)来创建HttpValueCollection的新的可写实例,然后将其用作任何NameValueCollection。添加所需值后,可以对集合调用ToString以获取查询字符串,如下所示:

1
2
3
4
5
6
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和Web上类似问题的答案之后,这是我能找到的最简单的解决方案。

网络核心

如果您在.NET核心中工作,那么可以使用Microsoft.AspNetCore.WebUtilities.QueryHelpers类,这大大简化了这一过程。

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


如果您查看引擎盖下的QueryString属性是一个NameValueCollection。当我做过类似的事情时,我通常对序列化和反序列化感兴趣,因此我的建议是建立一个NameValueCollection,然后传递给:

1
2
3
4
5
6
7
8
9
10
11
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);
}

也许我可以把它格式化得更好。)

我想在林肯也有一个非常优雅的方式来做到这一点…


在Roy Tinker的评论的启发下,我最终在URI类上使用了一个简单的扩展方法,使代码简洁明了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
    }
}

用途:

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

编辑-符合标准的变体

正如一些人指出的,httpValueCollection.ToString()以一种不符合标准的方式编码Unicode字符。这是同一扩展方法的变体,它通过调用HttpUtility.UrlEncode方法而不是不推荐使用的HttpUtility.UrlEncodeUnicode方法来处理这些字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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;
}


我刚才回答了一个类似的问题。基本上,最好的方法是使用类HttpValueCollection,ASP.NET的Request.QueryString属性实际上是,不幸的是它在.NET框架中是内部的。您可以使用Reflector来获取它(并将其放入您的utils类中)。通过这种方式,您可以像处理NameValueCollection一样处理查询字符串,但要注意所有的URL编码/解码问题。

HttpValueCollection扩展了NameValueCollection并具有一个采用编码查询字符串(包括符号和问号)的构造函数,它重写ToString()方法,以便稍后从基础集合重新生成查询字符串。

例子:

1
2
3
4
5
6
7
8
9
  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


这里有一个fluent/lambda风格的扩展方法(结合前面文章中的概念),它支持同一个键的多个值。我个人的偏好是扩展包装器,使其他团队成员能够发现类似的东西。请注意,关于编码方法存在争议,在StackOverflow(一个这样的帖子)和msdn bloggers(像这个帖子)上有很多关于它的帖子。

1
2
3
4
5
6
7
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());
}

编辑:使用空支持,尽管您可能需要根据特定情况调整它

1
2
3
4
5
6
7
8
9
10
11
12
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;
}


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

1
2
3
4
5
6
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允许您直接从同一个fluent调用链执行HTTP调用,并将其扩展到一个完整的REST客户机:

1
2
3
4
5
6
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


这是我迟到的地方。由于各种原因,我不喜欢其他人,所以我写了自己的。

此版本的功能:

  • 仅使用StringBuilder。没有ToArray()调用或其他扩展方法。它看起来不像其他一些响应那么漂亮,但我认为这是一个核心功能,因此效率比拥有"流畅的"、"一行程序"代码(隐藏效率低下)更重要。

  • 每个键处理多个值。(我自己不需要,只是想让毛里西奥闭嘴;)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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();
    }

示例用法

1
2
3
4
5
6
7
8
9
10
11
12
        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);

产量

1
http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26


I needed to solve the same problem for a portable class library (PCL) that I'm working on. In this case, I don't have access to System.Web so I can't use ParseQueryString.

Instead I used System.Net.Http.FormUrlEncodedContent是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var url = new UriBuilder("http://example.com");

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1","val1
<div class="
suo-content">[collapse title=""]<ul><li>这是我使用的技术,并在另一个问题http://stackoverflow.com/a/26744471/2108310中引用了它。唯一的区别是我使用了一个键值对数组…除了需要对System.net的引用(如您所述,它是可用的PCL)之外,这是一种最简单的方法,它不包括一些第三方软件包,或者尝试将自制的意大利面拼凑在一起。</li></ul>[/collapse]</div><hr><P>如何创建扩展方法,允许您以这样一种流畅的样式添加参数?</P>[cc lang="csharp"]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的过载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的过载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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));
}


1
2
3
4
5
6
7
8
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()))));
    }
}

像这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var actual = new QueryStringBuilder {
    {"foo", 123},
    {"bar","val31
<div class="
suo-content">[collapse title=""]<ul><li>现在,这应该是公认的答案;对于"foo[]=1,foo[]=2"这样的数组,以及保持参数顺序非常重要。</li></ul>[/collapse]</div><hr><P>我的奉献:</P>[cc lang="csharp"]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;
}

用途:

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

// http://stackoverflow.com?such=method&wow=soFluent


未经测试,但我认为这方面的工作会很好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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();


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
2
3
4
5
6
7
8
9
10
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相比,使用它的成本是最小的(我认为JeffAtwood在这个主题上发布了一些内容)。


结合最重要的答案创建匿名对象版本:

1
2
3
4
5
var queryString = HttpUtility2.BuildQueryString(new
{
    key2 ="value2",
    key1 ="value1",
});

这就产生了:

key2=value2&key1=value1

< /块引用>

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
    }
}

我有一个URI的扩展方法:

  • 接受匿名对象:uri.WithQuery(new { name ="value" })
  • 接受string/string对的集合(例如字典'2)。
  • 接受string/object对的集合(例如routeValueDictionary)。
  • 接受NameValueCollections。
  • 按键对查询值排序,以便相同的值产生相同的URI。
  • 每个键支持多个值,保留其原始顺序。

文档版本可以在这里找到。

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

查询分析器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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);
}

以下是测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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"));

[还有迟到]

httpValueCollection的可链包装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
        }
    }
}

示例用法:

1
2
3
4
5
QueryStringBuilder parameters = new QueryStringBuilder()
    .Add("view", ViewBag.PageView)
    .Add("page", ViewBag.PageNumber)
    .Add("size", ViewBag.PageSize);
string queryString = parameters.ToString();

Same as accepted solution, but transfred to"dot" LINQ syntax...

ZZU1〔0〕


我在pageBase类中添加了以下方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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());
    }

致电:

1
2
3
4
NameValueCollection querystrings = new NameValueCollection();    
querystrings.Add("language","en");
querystrings.Add("id","134");
this.Redirect("http://www.mypage.com", querystrings);

我使用了DSO提出的解决方案(在2011年8月2日7:29回答),他的解决方案不需要使用httputility。然而,根据dotnetearls上发表的一篇文章,使用字典比使用namevaluecollection更快(在性能上)。这里是DSO的解决方案,修改后使用字典代替NameValueCollection。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    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();
    }

我编写了一些扩展方法,这些方法在使用querystrings时非常有用。通常我想从当前的querystring开始,并在使用它之前进行修改。有点像

1
2
3
4
5
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.charlesrcok.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

它很基本,但我喜欢它的风格。


1
2
3
4
5
6
7
8
9
10
11
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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);
        }
    }
}

Just wanted to throw in my 2 cents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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中。

用途:

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


适用于NameValueCollection中的每个键的多个值。

例如:{ {"k1","v1


我用其他答案的一些提示为我的剃须刀项目写了一个助手。

ParseQueryString业务是必需的,因为我们不允许篡改当前请求的QueryString对象。

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

我是这样使用的:

1
location.search = '[email protected]("var-name","var-value")';

如果您希望它获取多个值,只需将参数更改为字典并将对添加到查询字符串中即可。


虽然不优雅,但我选择了一个更简单的版本,它不使用NameValueCollecitons,只是一个围绕StringBuilder的构建器模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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电话。它的用法是这样的:

1
2
3
4
5
6
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

This is another (maybe redundant :-]) way for do that.

The conceptuals are the same of the Vedran answer in this page (take a look here).

But this class is more efficient, because it iterate through all Keys only one time: when ToString is invoked.

The formatting code is also semplified and improved.

Hope that could be helpful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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();
    }
}

这与公认的答案相同,只是稍微紧凑一点:

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

下面的代码是通过ilspy从ToStringHttpValueCollection实现中去掉的,它为您提供了一个名称=value querystring。

不幸的是,httpValueCollection是一个内部类,只有在使用HttpUtility.ParseQueryString()时才能返回。我删除了它的所有viewstate部分,默认情况下它会进行编码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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();
    }
}

只适用于那些需要vb.net版本的顶级答案的用户:

1
2
3
4
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的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的C版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    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

这里是一个使用非常基本语言特性的实现。它是一个类的一部分,我们必须在目标C中移植和维护它,因此我们选择有更多的代码行,但是对于不太熟悉C的程序员来说,移植和理解更容易。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
        /// <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();
        }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}


编辑-正如评论中指出的,这不是一个好方法。

有这样一个类——URI类。"提供统一资源标识符(URI)的对象表示形式,并易于访问URI的各个部分。"(Microsoft文档)。

下面的示例创建一个URI类的实例,并使用它创建一个WebRequest实例。

C例

uri siteuri=new uri("http://www.contoso.com/");

webrequest wr=webrequest.create(siteuri);

看看,这个类有很多方法。