为System.Net.HttpClient get构建查询字符串

Neo*_*que 159 .net c# http

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

是否有任何简单的api可用于构建查询字符串,该字符串不涉及构建名称值集合和url编码那些然后最终连接它们?我希望使用类似RestSharp的api(即AddParameter(..))

Dar*_*rov 272

如果我希望使用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();
Run Code Online (Sandbox Code Playgroud)

会给你预期的结果:

foo=bar%3c%3e%26-baz&bar=bazinga
Run Code Online (Sandbox Code Playgroud)

您可能还会发现该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();
Run Code Online (Sandbox Code Playgroud)

会给你预期的结果:

http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga
Run Code Online (Sandbox Code Playgroud)

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

  • 这种解决方案是卑鄙的..Net应该有适当的查询字符串构建器. (71认同)
  • HttpUtility在System.Web中,在便携式运行时不可用.看起来奇怪的是,这个功能在类库中并不常用. (10认同)
  • 就.NET中的url处理而言,这是绝对最好的.无需手动编写url并进行字符串连接或字符串构建器或其他任何操作.UriBuilder类甚至可以使用Fragment属性为您处理带有片段(`#`)的URL.我见过很多人犯了手动处理网址而不是使用内置工具的错误. (9认同)
  • 事实上,最好的解决方案隐藏在内部类中,只能通过调用传入空字符串的实用程序方法来获取,这不能被称为优雅的解决方案. (7认同)
  • `NameValueCollection.ToString()`通常不会创建查询字符串,并且没有文档声明对`ParseQueryString`的结果执行`ToString`将导致新的查询字符串,因此可能随时中断,因为不能保证在那个功能. (5认同)
  • 我同意它绝对是最容易使用的,但它正在处理未记录的功能.如果你要做`new NameValueCollection`(`ParseQueryString`返回),你就不能对它做`.ToString()`并期望得到相同的结果. (3认同)
  • 此解决方案运行良好,但我不喜欢必须包含 System.Web 才能使用它。 (3认同)
  • 我假设这不适用于生成一个具有多个键值的查询字符串(例如``foo = 1&foo = 2&foo = 3`)?任何方便的选择? (3认同)
  • 你没有文档的功能是什么意思?ParseQueryString方法返回NameValueCollection的派生类,它为您处理所有url编码.[`ParseQueryString`](http://msdn.microsoft.com/en-us/library/ms150046.aspx)似乎记录得非常好. (2认同)
  • @label17如果您使用语法:`query.Add("foo", "1");`,它确实有效,因为`ParseQueryString`解析为允许重复的NameValueCollection。 (2认同)

Ros*_*tov 76

对于那些谁不想要包括System.Web在还没有使用它的项目,你可以使用FormUrlEncodedContentSystem.Net.Http做类似如下:

keyvaluepair版本

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;
}
Run Code Online (Sandbox Code Playgroud)

字典版

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 通过使用Dictionary <string,string>而不是KVP数组,这可以更简洁.然后使用初始化语法:{"ham","Glazed?" } (5认同)
  • @Kody为什么你说不使用`dispose`?我总是处理,除非我有充分的理由不重复,比如重用`HttpClient`. (5认同)

ill*_*ant 33

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

我实际上发现了接受的解决方案奇怪的双重编码问题:

因此,如果您正在处理需要编码的字符,则接受的解决方案会导致双重编码:

  • 查询参数是使用NameValueCollection索引器自动编码的(这使用UrlEncodeUnicode,而不是常规预期UrlEncode(!))
  • 然后,当你调用uriBuilder.Uri它时会创建一个新的Uriusing构造函数,它会再次编码(正常的url编码)
  • 这是无法避免的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!
Run Code Online (Sandbox Code Playgroud)

输出:

?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f

https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,无论您执行uribuilder.ToString()+ httpClient.GetStringAsync(string)uriBuilder.Uri+,httpClient.GetStringAsync(Uri)您最终都会发送双重编码参数

固定的例子可能是:

var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);
Run Code Online (Sandbox Code Playgroud)

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

PS on我最新的Windows on Windows Server,Uri构造函数与bool doc评论说"过时,dontEscape总是假的",但实际上按预期工作(跳过转义)

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

即使这是完全错误的 - 它将UrlEncodedUnicode发送到服务器,而不仅仅是UrlEncoded服务器所期望的

更新:一件事是,NameValueCollection中实际上做UrlEncodeUnicode,这是不应该再被使用,并定期url.encode /解码兼容(见的NameValueCollection到URL查询?).

所以底线是:永远不要使用这个hack,NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);因为它会弄乱你的unicode查询参数.只需手动构建查询并将其分配给UriBuilder.Query必要的编码,然后使用Uri UriBuilder.Uri.

通过使用不应该像这样使用的代码来伤害自己的主要例子

  • 你能为这个答案添加一个完整的效用函数吗? (11认同)
  • 我是第二个mafu:我读完了答案,但没有得出结论.对此有明确的答案吗? (7认同)
  • 我也希望看到这个问题的明确答案 (3认同)

Mag*_*agu 29

在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));
Run Code Online (Sandbox Code Playgroud)

  • 令人讨厌的是,尽管使用此过程,您仍然无法为同一个键发送多个值。如果您只想将“bar”和“bar2”作为 foo 的一部分发送,这是不可能的。 (2认同)
  • 对于现代应用程序来说,这是一个很好的答案,可以在我的场景中使用,简单干净。但是,我不需要任何转义机制-未经测试。 (2认同)
  • @m0g现在您可以使用 [AddQueryString(String, IEnumerable&lt;KeyValuePair&lt;String,StringValues&gt;&gt;)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers.addquerystring ?view=aspnetcore-7.0#microsoft-aspnetcore-webutilities-queryhelpers-addquerystring(system-string-system-collections-generic-ienumerable((system-collections-generic-keyvaluepair((system-string-microsoft-extensions-primitives-stringvalues) ))))))) (2认同)

Tod*_*ier 22

您可能想查看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>();
Run Code Online (Sandbox Code Playgroud)

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

PM> Install-Package Flurl.Http

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

PM> Install-Package Flurl

  • 为什么不扩展`Uri`或从你自己的类而不是`string`开始? (2认同)
  • 从技术上讲,我确实从我自己的`Url`类开始。上面的内容等同于`new Url(“ https://api.com”).AppendPathSegment ...`我个人更喜欢使用字符串扩展名,因为它减少了击键次数,并且在文档中对其进行了标准化,但是您可以选择两种方式。 (2认同)

cwi*_*lls 7

与 Rostov 的帖子相同,如果您不想System.Web在项目中包含对的引用,您可以使用FormDataCollectionfromSystem.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();
Run Code Online (Sandbox Code Playgroud)