当从代码中调用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")); }
}

这是一个非常常见的任务,人们希望存在一个实用工具类,使其更加优雅和可读。扫描MSDN,我没有找到一个,这让我想到了以下问题:

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


当前回答

从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;
    }
}

用法:

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

编辑-标准兼容的变体

正如一些人指出的那样,httpValueCollection.ToString()以一种不符合标准的方式编码Unicode字符。这是通过调用HttpUtility来处理此类字符的相同扩展方法的变体。UrlEncode方法代替已弃用的HttpUtility方法。UrlEncodeUnicode方法。

using System.Web;

public static Uri AddQuery(this Uri uri, string name, string value)
{
    var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

    httpValueCollection.Remove(name);
    httpValueCollection.Add(name, value);

    var ub = new UriBuilder(uri);

    // this code block is taken from httpValueCollection.ToString() method
    // and modified so it encodes strings with HttpUtility.UrlEncode
    if (httpValueCollection.Count == 0)
        ub.Query = String.Empty;
    else
    {
        var sb = new StringBuilder();

        for (int i = 0; i < httpValueCollection.Count; i++)
        {
            string text = httpValueCollection.GetKey(i);
            {
                text = HttpUtility.UrlEncode(text);

                string val = (text != null) ? (text + "=") : string.Empty;
                string[] vals = httpValueCollection.GetValues(i);

                if (sb.Length > 0)
                    sb.Append('&');

                if (vals == null || vals.Length == 0)
                    sb.Append(val);
                else
                {
                    if (vals.Length == 1)
                    {
                        sb.Append(val);
                        sb.Append(HttpUtility.UrlEncode(vals[0]));
                    }
                    else
                    {
                        for (int j = 0; j < vals.Length; j++)
                        {
                            if (j > 0)
                                sb.Append('&');

                            sb.Append(val);
                            sb.Append(HttpUtility.UrlEncode(vals[j]));
                        }
                    }
                }
            }
        }

        ub.Query = sb.ToString();
    }

    return ub.Uri;
}

其他回答

我的提供:

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

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

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

可选的Flurl。Http companion lib允许您在相同的流畅调用链上执行Http调用,将其扩展为一个成熟的REST客户端:

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>安装包Flurl。Http

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

PM>安装包Flurl

我使用了DSO提出的解决方案(在8月2日11日7:29回答),他的解决方案不需要使用HttpUtility。然而,根据Dotnetpearls上的一篇文章,使用Dictionary比使用NameValueCollection(在性能上)更快。下面是修改后的DSO解决方案,使用Dictionary代替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();
    }

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

只针对那些需要VB的人。NET版本的顶级答案:

Public Function ToQueryString(nvc As System.Collections.Specialized.NameValueCollection) As String
    Dim array As String() = nvc.AllKeys.SelectMany(Function(key As String) nvc.GetValues(key), Function(key As String, value As String) String.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(key), System.Web.HttpUtility.UrlEncode(value))).ToArray()
    Return "?" + String.Join("&", array)
End Function

以及没有LINQ的版本:

Public Function ToQueryString(nvc As System.Collections.Specialized.NameValueCollection) As String
    Dim lsParams As New List(Of String)()

    For Each strKey As String In nvc.AllKeys
        Dim astrValue As String() = nvc.GetValues(strKey)

        For Each strValue As String In astrValue
            lsParams.Add(String.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(strKey), System.Web.HttpUtility.UrlEncode(strValue)))
        Next ' Next strValue
    Next ' strKey
    Dim astrParams As String() = lsParams.ToArray()
    lsParams.Clear()
    lsParams = Nothing

    Return "?" + String.Join("&", astrParams)
End Function ' ToQueryString

和没有LINQ的c#版本:

    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