当从代码中调用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,我没有找到一个,这让我想到了以下问题:
你所知道的最优雅干净的方法是什么?
这是另一种(可能是多余的:-])方法。
其概念与本页的《吠陀经》答案相同(请看这里)。
但是这个类更有效,因为它只迭代所有key一次:当调用ToString时。
格式化代码也进行了简化和改进。
希望对大家有所帮助。
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();
}
}
如果你仔细观察,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中也有一种非常优雅的方式来做到这一点……
这是我最近的记录。由于种种原因,我不喜欢其他的,所以我自己写了一个。
这个版本的特点:
Use of StringBuilder only. No ToArray() calls or other extension methods. It doesn't look as pretty as some of the other responses, but I consider this a core function so efficiency is more important than having "fluent", "one-liner" code which hide inefficiencies.
Handles multiple values per key. (Didn't need it myself but just to silence 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();
}
示例使用
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);
输出
http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26
基于扩展方法的快速版本:
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子句来选择将哪些参数添加到字符串中。
下面的代码是从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();
}
}