当从代码中调用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,我没有找到一个,这让我想到了以下问题:

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


当前回答

只是想说说我的看法

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。查询将以?如果它是非空的,如果你要修改它,你应该把它剪掉。

注意HttpUtility。UrlEncode可以在System.Web中找到。

用法:

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

其他回答

如果你仔细观察,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);
}

我想在LINQ中也有一种非常优雅的方式来做到这一点……

这里有一种流畅的/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());
}

Edit: 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;
}

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

下面的代码是从ToString的HttpValueCollection实现中截取的,通过ILSpy,它会给你一个name=value querystring。

不幸的是,HttpValueCollection是一个内部类,只有当你使用HttpUtility.ParseQueryString()时才会返回。我删除了它的所有视图状态部分,它默认编码:

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