当从代码中调用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,我没有找到一个,这让我想到了以下问题:
你所知道的最优雅干净的方法是什么?
将这个类添加到项目中
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()))));
}
}
像这样使用它:
var actual = new QueryStringBuilder {
{"foo", 123},
{"bar", "val31"},
{"bar", "val32"}
};
actual.Add("a+b", "c+d");
actual.ToString(); // "foo=123&bar=val31&bar=val32&a%2bb=c%2bd"
基于扩展方法的快速版本:
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子句来选择将哪些参数添加到字符串中。
假设你想减少对其他程序集的依赖并保持简单,你可以这样做:
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的成本相比是最小的(我认为Jeff Atwood在这个主题上发表了一些文章)。
我刚才回答了一个类似的问题。基本上,最好的方法是使用HttpValueCollection类。净的请求。QueryString属性实际上是,不幸的是,它是。net框架内部的。
您可以使用Reflector来抓取它(并将其放入您的Utils类中)。通过这种方式,您可以像操作NameValueCollection一样操作查询字符串,但是需要考虑所有url编码/解码问题。
HttpValueCollection扩展了NameValueCollection,并有一个接受编码查询字符串(包括&和问号)的构造函数,它覆盖了ToString()方法,以便稍后从底层集合重新构建查询字符串。
例子:
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¶mA=A¶mB=B
下面是一个使用非常基本的语言特性的实现。它是我们必须在Objective C中移植和维护的类的一部分,所以我们选择了更多的代码行,但更容易移植和理解不太熟悉c#的程序员。
/// <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();
}