构buildSystem.Net.HttpClient get的查询string

如果我想使用System.Net.HttpClient提交一个http get请求,似乎没有api来添加参数,这是正确的吗?

是否有任何简单的API可用于构build查询string,不涉及构build名称值集合和URL编码,然后最终连接它们? 我希望能使用像RestSharp的api(即AddParameter(..))

如果我想使用System.Net.HttpClient提交一个http get请求,似乎没有api来添加参数,这是正确的吗?

是。

是否有任何简单的API可用于构build查询string,不涉及构build名称值集合和URL编码,然后最终连接它们?

当然:

var query = HttpUtility.ParseQueryString(string.Empty); query["foo"] = "bar<>&-baz"; query["bar"] = "bazinga"; string queryString = query.ToString(); 

会给你预期的结果:

 foo=bar%3c%3e%26-baz&bar=bazinga 

你也可能会发现UriBuilder类有用:

 var builder = new UriBuilder("http://example.com"); builder.Port = -1; var query = HttpUtility.ParseQueryString(builder.Query); query["foo"] = "bar<>&-baz"; query["bar"] = "bazinga"; builder.Query = query.ToString(); string url = builder.ToString(); 

会给你预期的结果:

 http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga 

你可以安全地提供给你的HttpClient.GetAsync方法。

对于那些不想将System.Web包含在尚未使用System.Web的项目中,可以使用System.Net.Http并执行如下操作:

 string query; using(var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{ new KeyValuePair<string, string>("ham", "Glazed?"), new KeyValuePair<string, string>("x-men", "Wolverine + Logan"), new KeyValuePair<string, string>("Time", DateTime.UtcNow.ToString()), })) { query = content.ReadAsStringAsync().Result; } 

TL; DR:不要使用公认的版本,因为它在处理unicode字符方面完全破坏了,并且从不使用内部API

我已经发现奇怪的双重编码问题与接受的解决scheme:

所以,如果你正在处理需要编码的字符,那么接受的解决scheme会导致双重编码:

  • 查询参数通过使用NameValueCollection索引器自动编码( 这使用UrlEncodeUnicode ,而不是经常预期的UrlEncode (!)
  • 然后,当你调用uriBuilder.Uri它创build新的Uri使用构造函数编码一次 (正常的URL编码)
  • 这不能避免通过做uriBuilder.ToString() (即使这将返回正确的Uri ,其中IMO是至less不一致,也许是一个错误,但这是另一个问题),然后使用HttpClient方法接受string – 客户端仍然创buildUri出你的传递像这样的string: new Uri(uri, UriKind.RelativeOrAbsolute)

小,但完整的repro:

 var builder = new UriBuilder { Scheme = Uri.UriSchemeHttps, Port = -1, Host = "127.0.0.1", Path = "app" }; NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); query["cyrillic"] = "кирилиця"; builder.Query = query.ToString(); Console.WriteLine(builder.Query); //query with cyrillic stuff UrlEncodedUnicode, and that's not what you want var uri = builder.Uri; // creates new Uri using constructor which does encode and messes cyrillic parameter even more Console.WriteLine(uri); // this is still wrong: var stringUri = builder.ToString(); // returns more 'correct' (still `UrlEncodedUnicode`, but at least once, not twice) new HttpClient().GetStringAsync(stringUri); // this creates Uri object out of 'stringUri' so we still end up sending double encoded cyrillic text to server. Ouch! 

输出:

 ?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f 

正如你可以看到的,不pipe你做uribuilder.ToString() + httpClient.GetStringAsync(string)uriBuilder.Uri + httpClient.GetStringAsync(Uri)你最终发送双编码参数

修正的例子可能是:

 var uri = new Uri(builder.ToString(), dontEscape: true); new HttpClient().GetStringAsync(uri); 

但是这使用过时的 Uri构造函数

PS在我的最新的.NET在Windows服务器, Uri构造与bool文档评论说:“过时,dontEscape总是假的”,但实际上按预期工作(跳过转义)

所以它看起来像另一个bug …

即使这是明显的错误 – 它发送UrlEncodedUnicode到服务器,不只是UrlEncoded什么服务器预计

更新:还有一件事是,NameValueCollection实际上是UrlEncodeUnicode,它不应该被使用,是不兼容的常规url.encode / decode(请参阅NameValueCollection URL查询? )。

所以底线是: 从来没有使用这个黑客与NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); 因为它会搅乱你的unicode查询参数。 只需手动构build查询并将其分配给UriBuilder.Query ,它将执行必要的编码,然后使用UriBuilder.Uri获取Uri。

通过使用不应该像这样使用的代码伤害自己的最佳例子

你可能想看看Flurl [披露:我是作者],一个stream利的URL生成器与可选的伴侣库,扩展到一个成熟的REST客户端。

 var result = await "https://api.com" // basic URL building: .AppendPathSegment("endpoint") .SetQueryParams(new { api_key = ConfigurationManager.AppSettings["SomeApiKey"], max_results = 20, q = "Don't worry, I'll get encoded!" }) .SetQueryParams(myDictionary) .SetQueryParam("q", "overwrite q!") // extensions provided by Flurl.Http: .WithOAuthBearerToken("token") .GetJsonAsync<TResult>(); 

查看文档以获取更多详细信息。 NuGet提供完整的软件包:

PM> Install-Package Flurl.Http

或者只是独立的URL构build器:

PM> Install-Package Flurl

达林提供了一个有趣和聪明的解决scheme,这里可能是另一种select:

 public class ParameterCollection { private Dictionary<string, string> _parms = new Dictionary<string, string>(); public void Add(string key, string val) { if (_parms.ContainsKey(key)) { throw new InvalidOperationException(string.Format("The key {0} already exists.", key)); } _parms.Add(key, val); } public override string ToString() { var server = HttpContext.Current.Server; var sb = new StringBuilder(); foreach (var kvp in _parms) { if (sb.Length > 0) { sb.Append("&"); } sb.AppendFormat("{0}={1}", server.UrlEncode(kvp.Key), server.UrlEncode(kvp.Value)); } return sb.ToString(); } } 

所以当使用它时,你可能会这样做:

 var parms = new ParameterCollection(); parms.Add("key", "value"); var url = ... url += "?" + parms; 

或者简单地使用我的Uri扩展

 public static Uri AttachParameters(this Uri uri, NameValueCollection parameters) { var stringBuilder = new StringBuilder(); string str = "?"; for (int index = 0; index < parameters.Count; ++index) { stringBuilder.Append(str + parameters.AllKeys[index] + "=" + parameters[index]); str = "&"; } return new Uri(uri + stringBuilder.ToString()); } 

用法

 Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection { {"Bill", "Gates"}, {"Steve", "Jobs"} }); 

结果

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

我正在开发的RFC 6570 URI模板库能够执行此操作。 所有的编码都是按照RFC来处理的。 在写这篇文章的时候,testing版本是可用的,它不被认为是一个稳定的1.0版本的唯一原因是文档不完全符合我的期望(见问题#17 , #18 , #32 , #43 )。

你可以单独build立一个查询string:

 UriTemplate template = new UriTemplate("{?params*}"); var parameters = new Dictionary<string, string> { { "param1", "value1" }, { "param2", "value2" }, }; Uri relativeUri = template.BindByName(parameters); 

或者你可以build立一个完整的URI:

 UriTemplate template = new UriTemplate("path/to/item{?params*}"); var parameters = new Dictionary<string, string> { { "param1", "value1" }, { "param2", "value2" }, }; Uri baseAddress = new Uri("http://www.example.com"); Uri relativeUri = template.BindByName(baseAddress, parameters); 

在ASP.NET Core项目中,您可以使用QueryHelpers类。

 // using Microsoft.AspNetCore.WebUtilities; var query = new Dictionary<string, string> { ["foo"] = "bar", ["foo2"] = "bar2", // ... }; var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query)); 

由于我不得不重复使用这几次,所以我想出了这个类,它只是简单地帮助抽象如何组成查询string。

 public class UriBuilderExt { private NameValueCollection collection; private UriBuilder builder; public UriBuilderExt(string uri) { builder = new UriBuilder(uri); collection = System.Web.HttpUtility.ParseQueryString(string.Empty); } public void AddParameter(string key, string value) { collection.Add(key, value); } public Uri Uri{ get { builder.Query = collection.ToString(); return builder.Uri; } } } 

使用会简化为这样的事情:

 var builder = new UriBuilderExt("http://example.com/"); builder.AddParameter("foo", "bar<>&-baz"); builder.AddParameter("bar", "second"); var uri = builder.Uri; 

这将返回uri: http : //example.com/? foo= bar% 3c%3e%26-baz&bar= second

感谢“Darin Dimitrov”,这是扩展方法。

  public static partial class Ext { public static Uri GetUriWithparameters(this Uri uri,Dictionary<string,string> queryParams = null,int port = -1) { var builder = new UriBuilder(uri); builder.Port = port; if(null != queryParams && 0 < queryParams.Count) { var query = HttpUtility.ParseQueryString(builder.Query); foreach(var item in queryParams) { query[item.Key] = item.Value; } builder.Query = query.ToString(); } return builder.Uri; } public static string GetUriWithparameters(string uri,Dictionary<string,string> queryParams = null,int port = -1) { var builder = new UriBuilder(uri); builder.Port = port; if(null != queryParams && 0 < queryParams.Count) { var query = HttpUtility.ParseQueryString(builder.Query); foreach(var item in queryParams) { query[item.Key] = item.Value; } builder.Query = query.ToString(); } return builder.Uri.ToString(); } } 

这是基于Roman Ratskey答案的.Net Core解决scheme。 .Net Core中删除了NameValueCollectiontypes。

 public static class UriExtensions { public static string AttachParameters(this string uri, Dictionary<string, string> parameters) { var stringBuilder = new StringBuilder(); string str = "?"; foreach (KeyValuePair<string, string> parameter in parameters) { stringBuilder.Append(str + parameter.Key + "=" + parameter.Value); str = "&"; } return uri + stringBuilder; } } 

用法

  var parameters = new Dictionary<string, string>(); parameters.Add("Bill", "Gates"); parameters.Add("Steve", "Jobs"); string uri = "http://www.example.com/index.php".AttachParameters(parameters); 

结果

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

为了避免在taras.roshko的答案中描述的双重编码问题,并保持可以轻松使用查询参数的可能性,您可以使用uriBuilder.Uri.ParseQueryString()而不是HttpUtility.ParseQueryString()

我找不到比创build一个将字典转换为QueryStringFormat的扩展方法更好的解决scheme。 Waleed AK提出的解决scheme也不错。

遵循我的解决scheme

创build扩展方法:

 public static class DictionaryExt { public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary) { return ToQueryString<TKey, TValue>(dictionary, "?"); } public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, string startupDelimiter) { string result = string.Empty; foreach (var item in dictionary) { if (string.IsNullOrEmpty(result)) result += startupDelimiter; // "?"; else result += "&"; result += string.Format("{0}={1}", item.Key, item.Value); } return result; } } 

还有他们:

 var param = new Dictionary<string, string> { { "param1", "value1" }, { "param2", "value2" }, }; param.ToQueryString(); //By default will add (?) question mark at begining //"?param1=value1&param2=value2" param.ToQueryString("&"); //Will add (&) //"&param1=value1&param2=value2" param.ToQueryString(""); //Won't add anything //"param1=value1&param2=value2"