CSharp - c#如何生成查询字符串用于URL?

  显示原文与译文双语对照的内容

调用来自代码的网络资源时,常见任务是生成查询字符串以包含所有必要的参数。 同时意味着没有火箭科学,有一些漂亮的细节你需要照顾,附加一个 & 如果不是第一个参数,参数编码等。

执行该操作的代码非常简单,但有点乏味:


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

这是一个常见的任务会期望一个实用程序类存在,使得它更加优雅和可读性。 我没有找到 one—which,我发现带我到了以下问题:

你知道的最优雅的清理方式是什么?

时间:

如果你在引擎盖下查找 QueryString 属性是一个 NameValueCollection 。 当我完成类似的事情时,我通常对serialising和deserialising感兴趣,所以我建议构建一个 NameValueCollection,然后通过:


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中也有一个非常优雅的方法来实现这一点。

你可以通过调用HttpValueCollection创建一个新的可以写实例 System.Web.HttpUtility.ParseQueryString(string.Empty) ,然后将它的用作任何元素。 添加所需的值之后,可以在集合上调用ToString以获取查询字符串,如下所示:


NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

queryString["key1"] ="value1";
queryString["key2"] ="value2";

return queryString.ToString();//Returns"key1=value1&key2=value2", all URL-encoded

HttpValueCollection是内部的,因此不能直接构造一个实例。 但是,一旦获得了一个实例,就可以像其他任何元素一样使用它。 由于你使用的实际对象是 HttpValueCollection,调用ToString方法将调用HttpValueCollection上重写的方法,该方法将集合格式化为URL-encoded查询字符串。

在搜索之后,站点得到一个类似问题的答案,这是我能找到的最简单的解决方案。

由于来自修补程序Roy注释的启发,我最后在Uri类上使用了一个简单的扩展方法,它使代码简洁简洁:


using System.Web;

public static class HttpExtensions
{
 public static Uri AddQuery(this Uri uri, string name, string value)
 {
 var ub = new UriBuilder(uri);

//decodes urlencoded pairs from uri.Query to HttpValueCollection
 var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

 httpValueCollection.Add(name, value);

//urlencodes the whole HttpValueCollection
 ub.Query = httpValueCollection.ToString();

 return ub.Uri;
 }
}

使用方法:


Uri url = new Uri("http://localhost/rest/something/browse").
 AddQuery("page","0").
 AddQuery("pageSize","200");


编辑- 符合标准的变体

就像几个人指出的,httpValueCollection.ToString()non-standards-compliant的方式编码Unicode字符。 这是同一个扩展方法的变体,它通过调用 HttpUtility.UrlEncode 方法而不是使用过时的HttpUtility.UrlEncodeUnicode 方法来处理这些字符。


using System.Web;

public static Uri AddQuery(this Uri uri, string name, string value)
{
 var ub = new UriBuilder(uri);

//decodes urlencoded pairs from uri.Query to HttpValueCollection
 var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
 httpValueCollection.Add(name, value);

//this code block is taken from httpValueCollection.ToString() method
//and modified so it encodes strings with HttpUtility.UrlEncode
 if (httpValueCollection.Count == 0)
 ub.Query = String.Empty;
 else
 {
 var sb = new StringBuilder();

 for (int i = 0; i <httpValueCollection.Count; i++)
 {
 string text = httpValueCollection.GetKey(i);
 {
 text = HttpUtility.UrlEncode(text);

 string val = (text!= null)? (text +"=") : string.Empty;
 string[] vals = httpValueCollection.GetValues(i);

 if (sb.Length> 0)
 sb.Append('&');

 if (vals == null || vals.Length == 0)
 sb.Append(val);
 else
 {
 if (vals.Length == 1)
 {
 sb.Append(val);
 sb.Append(HttpUtility.UrlEncode(vals[0]));
 }
 else
 {
 for (int j = 0; j <vals.Length; j++)
 {
 if (j> 0)
 sb.Append('&');

 sb.Append(val);
 sb.Append(HttpUtility.UrlEncode(vals[j]));
 }
 }
 }
 }
 }

 ub.Query = sb.ToString();
 }

 return ub.Uri;
}

我回答了一个类似的问题,时间是 。 基本上,最好的方法是使用 HttpValueCollection,这是 ASP.NET's Request.QueryString 属性,不幸的是,它是在. NET 框架中。 你可以使用反射器来抓取它( 并将它放入你的Utils类) 。 这样你就可以像一个NameValueCollection一样操纵查询字符串,但是你所需要的所有url编码/解码问题。

HttpValueCollectionNameValueCollection 延伸,一个构造函数,该函数接收一个查询字符串( 包含的符号和问号) 编码,它覆盖 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&paramA=A&paramB=B

这里是一个流畅的/lambda-ish方法作为一个扩展方法( 组合上一篇文章中的概念),它支持同一个键的多个值。 我个人的偏好是对discover-ability的扩展,对于其他的团队成员来说是这样的。 注意,对编码方法有争议,在堆栈溢出( 一个这样的post ) 和 MSDN blogger ( 就像一样,这个 ) 上有大量关于它的帖子。


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

的编辑:带有空支持的,虽然你可能需要为你的特定情况调整它


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

如何创建允许你以流畅风格添加参数的扩展方法?


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

下面是使用 string的重载:


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

这是我的late 。由于种种原因,我不喜欢其他的,所以我自己写了。

这里版本功能:

  • 仅使用 StringBuilder 。没有 ToArray() 调用或者其他扩展方法。 它看起来不像其他的响应,但是我认为这是一个核心函数,所以效率比使用"流畅","one-liner"代码来隐藏低效。

  • 处理每个键的多个值。 ( 我不需要它,只是为了静音 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&


 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 web [ 公开:我是使用匿名对象( 其中之一) 构建查询字符串的author]:


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!"
});

[UPDATE]

Flurl现在有一个用于从fluent链执行HTTP调用的伴随库:


await"https://api.mysite.com"
. AppendPathSegment("person")
. SetQueryParams(new { ap_key ="my-key" })
. WithOAuthBearerToken("MyToken")
. PostJsonAsync(new { first_name = firstName, last_name = lastName });

还包括一套漂亮的测试特性。 在NuGet上可以使用完整软件包:

PM> Install-Package Flurl.Http

或者单独的URL生成器:

PM> Install-Package Flurl

将此类添加到项目中


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

public class QueryStringBuilder : List<KeyValuePair<string, object>>
{
 public void Add(string name, object value)
 {
 Add(new KeyValuePair<string, object>(name, value));
 }

 public override string ToString()
 {
 return String.Join("&", this.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"

...