如果我想使用 System.Net.HttpClient 提交 http get 请求,似乎没有 api 来添加参数,这是正确的吗?
是否有任何简单的 api 可用于构建不涉及构建名称值集合和 url 编码的查询字符串,然后最终将它们连接起来?我希望使用类似 RestSharp 的 API(即 AddParameter(..))
如果我想使用 System.Net.HttpClient 提交 http get 请求,似乎没有 api 来添加参数,这是正确的吗?
是的。
是否有任何简单的 api 可用于构建不涉及构建名称值集合和 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
方法。
For those who do not want to include System.Web
in projects that don't already use it, you can use FormUrlEncodedContent
from System.Net.Http
and do something like the following:
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;
}
string query;
using(var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{ "ham", "Glaced?"},
{ "x-men", "Wolverine + Logan"},
{ "Time", DateTime.UtcNow.ToString() },
})) {
query = content.ReadAsStringAsync().Result;
}
在 ASP.NET Core 项目中,您可以使用在ASP.NET CoreQueryHelpers
的命名空间中可用的类,或用于其他使用者Microsoft.AspNetCore.WebUtilities
的 .NET Standard 2.0 NuGet 包:
// using Microsoft.AspNetCore.WebUtilities;
var query = new Dictionary<string, string>
{
["foo"] = "bar",
["foo2"] = "bar2",
// ...
};
var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query));
TL;DR:不要使用接受的版本,因为它在处理 unicode 字符方面完全被破坏了,并且从不使用内部 API
实际上,我发现接受的解决方案存在奇怪的双重编码问题:
因此,如果您正在处理需要编码的字符,则可接受的解决方案会导致双重编码:
NameValueCollection
索引器自动编码的(这使用UrlEncodeUnicode
,而不是常规预期的UrlEncode
(!))uriBuilder.Uri
创建新的,该构造函数会再编码一次(正常的 url 编码)Uri
uriBuilder.ToString()
(即使这返回正确Uri
的 IMO 至少是不一致的,可能是一个错误,但这是另一个问题),然后使用HttpClient
接受字符串的方法 -客户端仍然会Uri
像这样从您传递的字符串中创建:new Uri(uri, UriKind.RelativeOrAbsolute)
小而完整的复制品:
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
如您所见,无论您执行uribuilder.ToString()
+httpClient.GetStringAsync(string)
或uriBuilder.Uri
+httpClient.GetStringAsync(Uri)
最终都会发送双编码参数
固定示例可能是:
var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);
但这使用过时的 Uri
构造函数
PS 在我在 Windows Server 上的最新 .NET 上,Uri
带有 bool doc 注释的构造函数说“已过时,dontEscape 始终为假”,但实际上按预期工作(跳过转义)
所以它看起来像另一个错误......
甚至这是完全错误的——它将 UrlEncodedUnicode 发送到服务器,而不仅仅是服务器期望的 UrlEncoded
更新:另外一件事是,NameValueCollection 实际上是 UrlEncodeUnicode,它不应该再使用并且与常规 url.encode/decode 不兼容(请参阅NameValueCollection to URL Query?)。
所以底线是:永远不要使用这个hack,NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
因为它会弄乱你的unicode查询参数。只需手动构建查询并将其分配给UriBuilder.Query
将进行必要编码的查询,然后使用UriBuilder.Uri
.
使用不应该这样使用的代码伤害自己的主要例子
您可能想查看Flurl [披露:我是作者],这是一个流畅的 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 构建器:
PM> Install-Package Flurl
与 Rostov 的帖子一样,如果您不想System.Web
在项目中包含对的引用,则可以使用FormDataCollection
fromSystem.Net.Http.Formatting
并执行以下操作:
System.Net.Http.Formatting.FormDataCollection
var parameters = new Dictionary<string, string>()
{
{ "ham", "Glaced?" },
{ "x-men", "Wolverine + Logan" },
{ "Time", DateTime.UtcNow.ToString() },
};
var query = new FormDataCollection(parameters).ReadAsNameValueCollection().ToString();
由于我必须重用这几次,我想出了这个类,它只是帮助抽象查询字符串的组成方式。
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 提供了一个有趣而聪明的解决方案,这可能是另一种选择:
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;
已接受答案的好部分,修改为使用 UriBuilder.Uri.ParseQueryString() 而不是 HttpUtility.ParseQueryString():
var builder = new UriBuilder("http://example.com");
var query = builder.Uri.ParseQueryString();
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();
我正在开发的RFC 6570 URI 模板库能够执行此操作。根据该 RFC 为您处理所有编码。在撰写本文时,有一个 beta 版本可用,它不被认为是稳定的 1.0 版本的唯一原因是文档没有完全满足我的期望(参见问题#17、#18、#32、#43)。
您可以单独构建一个查询字符串:
UriTemplate template = new UriTemplate("{?params*}");
var parameters = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri relativeUri = template.BindByName(parameters);
或者您可以构建一个完整的 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);
或者只是使用我的 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"}
});
为避免 taras.roshko 的回答中描述的双重编码问题并保持轻松使用查询参数的可能性,您可以uriBuilder.Uri.ParseQueryString()
使用HttpUtility.ParseQueryString()
.
感谢“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();
}
}
HttpClient client = new HttpClient();
var uri = Environment.GetEnvironmentVariable("URL of Api");
var requesturi = QueryHelpers.AddQueryString(uri, "parameter_name",parameter_value);
client.BaseAddress = new Uri(requesturi);
然后您还可以添加请求标头,例如:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("x-api-key", secretValue);
响应语法例如:
HttpResponseMessage response = client.GetAsync(requesturi).Result;
希望它对你有用。#.netcore #csharp #visualstudio #httpclient #addquerystring
我找不到比创建将 Dictionary 转换为 QueryStringFormat 的扩展方法更好的解决方案了。Waleed AK 提出的解决方案也很好。
按照我的解决方案:
创建扩展方法:
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"