构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"} });
结果
我正在开发的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);
结果
为了避免在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¶m2=value2" param.ToQueryString("&"); //Will add (&) //"¶m1=value1¶m2=value2" param.ToQueryString(""); //Won't add anything //"param1=value1¶m2=value2"