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

时间:2013-06-13 20:16:22

标签: c# .net http

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

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

16 个答案:

答案 0 :(得分:247)

  

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

答案 1 :(得分:72)

对于那些不希望在尚未使用System.Web的项目中加入System.Net.Http的人,您可以使用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; } 中的FormUrlEncodedContent并执行以下操作:< / p>

keyvaluepair版本

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

字典版

{{1}}

答案 2 :(得分:32)

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

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

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

  • 使用NameValueCollection索引器自动编码查询参数(使用UrlEncodeUnicode,而非常规预期UrlEncode(!)
  • 然后,当您致电uriBuilder.Uri时,它会使用构造函数创建新的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构造函数

关于我在Windows Server上的最新版本的PS,使用bool doc注释的Uri构造函数表示&#34;已过时,dontEscape始终为false&#34;,但实际上按预期工作(跳过转义)

所以它看起来像另一个错误...

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

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

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

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

答案 3 :(得分:23)

在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));

答案 4 :(得分:21)

您可能需要查看Flurl [披露:我是作者],一个流畅的URL构建器,带有可选的随附lib,可将其扩展为一个完整的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>();

查看the docs了解详情。完整的软件包可以在NuGet上找到:

PM> Install-Package Flurl.Http

或只是独立的网址构建器:

PM> Install-Package Flurl

答案 5 :(得分:3)

达林提供了一个有趣而聪明的解决方案,这可能是另一种选择:

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;

答案 6 :(得分:2)

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

答案 7 :(得分:2)

RFC 6570 URI Template library我正在开发的能够执行此操作。根据该RFC处理所有编码。在撰写本文时,可以使用测试版,并且它不被视为稳定的1.0版本的唯一原因是文档不能完全满足我的期望(请参阅问题#17,{{ 3}},#18#32)。

您可以单独构建查询字符串:

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

答案 8 :(得分:1)

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

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

答案 9 :(得分:1)

您始终可以使用IEnterprise.Easy-HTTP,因为它具有内置查询构建器:

await new RequestBuilder<ExampleObject>()
.SetHost("https://httpbin.org")
.SetContentType(ContentType.Application_Json)
.SetType(RequestType.Get)
.ContinueToQuery()
    .SetQuery("/get")
    .ParseModelToQuery(dto)
    .Build()
.Build()
.Execute();

答案 10 :(得分:1)

已接受答案的一部分,已修改为使用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();

答案 11 :(得分:0)

感谢&#34; Darin Dimitrov&#34;,这是扩展方法。

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

答案 12 :(得分:0)

这是我在Roman Ratskey的答案中为.Net Core提供的解决方案。在.Net Core中删除了NameValueCollection类型。

代码

public static class UriExtensions
    {
        public static string AttachParameters(this string uri, Dictionary<string, string> parameters)
        {
            var stringBuilder = new StringBuilder();
            string str = "?";

            foreach (KeyValuePair<string, string> parameter in parameters)
            {
                stringBuilder.Append(str + parameter.Key + "=" + parameter.Value);
                str = "&";
            }
            return uri + stringBuilder;
        }
    }

用法

 var parameters = new Dictionary<string, string>();
            parameters.Add("Bill", "Gates");
            parameters.Add("Steve", "Jobs");

string uri = "http://www.example.com/index.php".AttachParameters(parameters);

结果

  

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

答案 13 :(得分:0)

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

答案 14 :(得分:0)

与罗斯托夫的帖子相同,如果您不想在项目中包含对System.Web的引用,则可以使用FormDataCollection中的System.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();

答案 15 :(得分:-1)

我找不到比创建扩展方法将Dictionary转换为QueryStringFormat更好的解决方案。 Waleed A.K.提出的解决方案。也很好。

按照我的解决方案:

创建扩展方法:

#include <algorithm>
#include <cmath>
#include <vector>

#include <CGAL/convex_hull_traits_3.h>
#include <CGAL/convex_hull_3.h>

typedef Kernel::Point_3              Point;
typedef Kernel::Vector_3             Vector;
typedef Kernel::Aff_transformation_3 Transformation;
typedef CGAL::Polyhedron_3<Kernel>   Polyhedron;

struct Plane_from_facet {
  Polyhedron::Plane_3 operator()(Polyhedron::Facet& f) {
      Polyhedron::Halfedge_handle h = f.halfedge();
      return Polyhedron::Plane_3(h->vertex()->point(),
                                 h->next()->vertex()->point(),
                                 h->opposite()->vertex()->point());
  }
};

inline static double planeDistance(Plane &p, Plane &q) {
    double sc1 = max(abs(p.a()),
                 max(abs(p.b()),
                 max(abs(p.c()),
                     abs(p.d()))));
    double sc2 = max(abs(q.a()),
                 max(abs(q.b()),
                 max(abs(q.c()),
                     abs(q.d()))));
    Plane r(p.a() * sc2,
            p.b() * sc2,
            p.c() * sc2,
            p.d() * sc2);
    Plane s(q.a() * sc1,
            q.b() * sc1,
            q.c() * sc1,
            q.d() * sc1);
    return ((r.a() - s.a()) * (r.a() - s.a()) +
            (r.b() - s.b()) * (r.b() - s.b()) +
            (r.c() - s.c()) * (r.c() - s.c()) +
            (r.d() - s.d()) * (r.d() - s.d())) / (sc1 * sc2);
}

static void detriangulatePolyhedron(Polyhedron &poly) {
    vector<Polyhedron::Halfedge_handle> toJoin;
    for (auto edge = poly.edges_begin(); edge != poly.edges_end(); edge++) {
        auto f1 = edge->facet();
        auto f2 = edge->opposite()->facet();
        if (planeDistance(f1->plane(), f2->plane()) < 1E-5) {
            toJoin.push_back(edge);
        }
    }
    for (auto edge = toJoin.begin(); edge != toJoin.end(); edge++) {
        poly.join_facet(*edge);
    }
}

 ...

                    Polyhedron convexHull;

                    CGAL::convex_hull_3(shape.begin(),
                                        shape.end(),
                                        convexHull);

                    transform(convexHull.facets_begin(),
                              convexHull.facets_end(),
                              convexHull.planes_begin(),
                              Plane_from_facet());

                    detriangulatePolyhedron(convexHull);

                    Plane bounds[convexHull.size_of_facets()];
                    int boundCount = 0;

                    for (auto facet = convexHull.facets_begin(); facet != convexHull.facets_end(); facet++) {
                        bounds[boundCount++] = facet->plane();
                    }
...

他们:

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