当从代码中调用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 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());
}
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
结合顶部的答案创建一个匿名对象版本:
var queryString = HttpUtility2.BuildQueryString(new
{
key2 = "value2",
key1 = "value1",
});
这会产生这样的结果:
key2 = value2&key1 = value1
代码如下:
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();
}
}
在dotnet核心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);
这里有一种流畅的/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;
}