当从代码中调用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,我没有找到一个,这让我想到了以下问题:
你所知道的最优雅干净的方法是什么?
下面是一个使用非常基本的语言特性的实现。它是我们必须在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();
}
这是另一种(可能是多余的:-])方法。
其概念与本页的《吠陀经》答案相同(请看这里)。
但是这个类更有效,因为它只迭代所有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();
}
}
未经测试,但我认为沿着这些路线的东西会工作得很好
public class QueryString
{
private Dictionary<string,string> _Params = new Dictionary<string,string>();
public overide ToString()
{
List<string> returnParams = new List<string>();
foreach (KeyValuePair param in _Params)
{
returnParams.Add(String.Format("{0}={1}", param.Key, param.Value));
}
// return String.Format("?{0}", String.Join("&", returnParams.ToArray()));
// credit annakata
return "?" + String.Join("&", returnParams.ToArray());
}
public void Add(string key, string value)
{
_Params.Add(key, HttpUtility.UrlEncode(value));
}
}
QueryString query = new QueryString();
query.Add("param1", "value1");
query.Add("param2", "value2");
return query.ToString();
这里有很多很好的答案,但对于使用现代c#的人来说,这可能是一个很好的实用程序类。
public class QueryParamBuilder
{
private readonly Dictionary<string, string> _fields = new();
public QueryParamBuilder Add(string key, string value)
{
_fields.Add(key, value);
return this;
}
public string Build()
{
return $"?{String.Join("&", _fields.Select(pair => $"{HttpUtility.UrlEncode(pair.Key)}={HttpUtility.UrlEncode(pair.Value)}"))}";
}
public static QueryParamBuilder New => new();
}
我在这里使用了一个内部Dictionary,因为字典在内部是可枚举的键值对,这使得遍历它们比NameValueCollection容易得多。
那么查询字符串本身就是一个简单的带有连接的插值字符串。
另外,我为构造函数提供了一个静态接口,使构造新的构造器非常容易,并且只允许一个公开的方法Add来添加新的查询参数值。最后,使用Build()终止链以实际获得最终字符串。
下面是它用法的一个例子
var queryString = QueryParamBuilder.New
.Add("id", "0123")
.Add("value2", 1234.ToString())
.Add("valueWithSpace","value with spa12!@#@!ce")
.Build();
结果和预期的一样
?id=0123&value2=1234&valueWithSpace=value+with+spa12!%40%23%40!ce
希望你们中的一些人会觉得这个很好很优雅。
如何创建扩展方法,允许您以这样流畅的风格添加参数?
string a = "http://www.somedomain.com/somepage.html"
.AddQueryParam("A", "TheValueOfA")
.AddQueryParam("B", "TheValueOfB")
.AddQueryParam("Z", "TheValueOfZ");
string b = new StringBuilder("http://www.somedomain.com/anotherpage.html")
.AddQueryParam("A", "TheValueOfA")
.AddQueryParam("B", "TheValueOfB")
.AddQueryParam("Z", "TheValueOfZ")
.ToString();
下面是使用字符串的重载:
public static string AddQueryParam(
this string source, string key, string value)
{
string delim;
if ((source == null) || !source.Contains("?"))
{
delim = "?";
}
else if (source.EndsWith("?") || source.EndsWith("&"))
{
delim = string.Empty;
}
else
{
delim = "&";
}
return source + delim + HttpUtility.UrlEncode(key)
+ "=" + HttpUtility.UrlEncode(value);
}
这里是使用StringBuilder的重载:
public static StringBuilder AddQueryParam(
this StringBuilder source, string key, string value)
{
bool hasQuery = false;
for (int i = 0; i < source.Length; i++)
{
if (source[i] == '?')
{
hasQuery = true;
break;
}
}
string delim;
if (!hasQuery)
{
delim = "?";
}
else if ((source[source.Length - 1] == '?')
|| (source[source.Length - 1] == '&'))
{
delim = string.Empty;
}
else
{
delim = "&";
}
return source.Append(delim).Append(HttpUtility.UrlEncode(key))
.Append("=").Append(HttpUtility.UrlEncode(value));
}
你可以通过调用system . web . httputiity . parsequerystring (string.Empty)来创建一个新的可写的HttpValueCollection实例,然后将它用作任何NameValueCollection。一旦你添加了你想要的值,你可以在集合上调用ToString来获得一个查询字符串,如下所示:
NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);
queryString.Add("key1", "value1");
queryString.Add("key2", "value2");
return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded
HttpValueCollection是内部的,所以你不能直接构造一个实例。然而,一旦你获得了一个实例,你就可以像使用其他NameValueCollection一样使用它。因为你正在使用的实际对象是一个HttpValueCollection,调用ToString方法将调用HttpValueCollection上的重写方法,它将集合格式化为url编码的查询字符串。
在SO和网络上搜索类似问题的答案后,这是我能找到的最简单的解决方案。
net核心
如果你在。net Core中工作,你可以使用Microsoft.AspNetCore.WebUtilities.QueryHelpers类,它极大地简化了这一点。
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers
示例代码:
const string url = "https://customer-information.azure-api.net/customers/search/taxnbr";
var param = new Dictionary<string, string>() { { "CIKey", "123456789" } };
var newUrl = new Uri(QueryHelpers.AddQueryString(url, param));