231

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

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

4

15 回答 15

381

如果我想使用 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方法。

于 2013-06-13T20:20:41.887 回答
92

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:

keyvaluepair version

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;
}

dictionary version

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;
}
于 2014-11-04T20:32:07.460 回答
65

在 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));
于 2017-07-10T07:51:04.777 回答
47

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.

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

于 2015-07-06T13:34:08.900 回答
29

您可能想查看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

于 2014-02-21T06:37:57.937 回答
5

与 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();
于 2019-06-28T06:40:08.370 回答
4

由于我必须重用这几次,我想出了这个类,它只是帮助抽象查询字符串的组成方式。

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

于 2015-02-18T21:05:42.603 回答
3

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;
于 2013-06-13T20:23:30.373 回答
3

已接受答案的好部分,修改为使用 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();
于 2018-09-19T09:29:47.027 回答
2

我正在开发的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);
于 2014-08-01T15:38:45.317 回答
1

或者只是使用我的 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"}
                                                                           });

结果

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

于 2013-07-30T01:30:58.973 回答
1

为避免 taras.roshko 的回答中描述的双重编码问题并保持轻松使用查询参数的可能性,您可以uriBuilder.Uri.ParseQueryString()使用HttpUtility.ParseQueryString().

于 2017-04-19T15:34:19.047 回答
0

感谢“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();
    }
}
于 2015-03-03T21:37:03.337 回答
0
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

于 2021-12-27T14:23:03.967 回答
-3

我找不到比创建将 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&param2=value2"
param.ToQueryString("&"); //Will add (&)
//"&param1=value1&param2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1&param2=value2"
于 2015-06-03T00:44:30.873 回答