如果我想使用 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 编码)UriuriBuilder.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在项目中包含对的引用,则可以使用FormDataCollectionfromSystem.Net.Http.Formatting并执行以下操作:
System.Net.Http.Formatting.FormDataCollectionvar 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"