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

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


当前回答

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

实际上,我在公认的解决方案中发现了奇怪的双编码问题:

所以,如果你处理的字符需要编码,接受的解决方案导致双重编码:

query parameters are auto encoded by using NameValueCollection indexer (and this uses UrlEncodeUnicode, not regular expected UrlEncode(!)) Then, when you call uriBuilder.Uri it creates new Uri using constructor which does encoding one more time (normal url encoding) That cannot be avoided by doing uriBuilder.ToString() (even though this returns correct Uri which IMO is at least inconsistency, maybe a bug, but that's another question) and then using HttpClient method accepting string - client still creates Uri out of your passed string like this: 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. tostring()。Uri + httpClient.GetStringAsync(Uri)你最终会发送双编码参数

固定的例子可以是:

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

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

p.s.在我最新的。net在Windows服务器上,Uri构造函数与bool doc注释说“过时,dontEscape总是假的”,但实际上工作如预期(跳过转义)

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

甚至这是完全错误的-它发送UrlEncodedUnicode到服务器,而不仅仅是UrlEncoded服务器期望

更新:还有一件事是,NameValueCollection实际上做UrlEncodeUnicode,这是不应该再使用的,与常规url不兼容。encode/decode(参见NameValueCollection to URL Query?)

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

使用不应该这样使用的代码而伤害自己的最好例子

其他回答

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

Yes.

是否有任何简单的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方法。

我正在开发的RFC 6570 URI Template库能够执行此操作。所有编码都按照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);

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;

我的答案与公认的答案/其他答案在全球范围内并无不同。我只是尝试为Uri类型创建一个扩展方法,它接受可变数量的参数。

public static class UriExtensions
{
    public static Uri AddParameter(this Uri url, params (string Name, string Value)[] @params)
    {
        if (!@params.Any())
        {
            return url;
        }

        UriBuilder uriBuilder = new(url);

        NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);

        foreach (var param in @params)
        {
            query[param.Name] = param.Value.Trim();
        }

        uriBuilder.Query = query.ToString();

        return uriBuilder.Uri;
    }
}

使用的例子:

var uri = new Uri("http://someuri.com")
    .AddParameter(
       ("p1.name", "p1.value"),
       ("p2.name", "p2.value"),
       ("p3.name", "p3.value"));

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

Code

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