web-dev-qa-db-ja.com

System.Net.HttpClientのクエリ文字列を作成します。

System.Net.HttpClientを使用してhttp getリクエストを送信したい場合、パラメータを追加するAPIがないようですが、これは正しいですか?

名前値コレクションを構築し、それらをURLエンコードし、最後にそれらを連結することを含まない、クエリ文字列を構築するために利用できる単純なAPIはありますか? RestSharpのapi(AddParameter(..))のようなものを使用したいと思っていました。

146
NeoDarque

System.Net.HttpClientを使用してhttp getリクエストを送信したい場合、パラメータを追加するAPIがないようですが、これは正しいですか?

はい。

名前値コレクションを構築し、それらをURLエンコードし、最後にそれらを連結することを含まない、クエリ文字列を構築するために利用できる単純なAPIはありますか?

もちろんです。

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メソッドにフィードする以上のことができるということ。

249
Darin Dimitrov

まだ使っていないプロジェクトにSystem.Webを含めたくない人は、System.Net.HttpFormUrlEncodedContent を使って次のようにすることができます。

キーバリューペアバージョン

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

TL; DR:Unicode文字の扱いに関しては完全に壊れているので受け入れられたバージョンを使わず、内部APIを決して使わない

私は実際に受け入れられている解決策で奇妙な二重エンコーディングの問題を発見しました:

そのため、エンコードする必要がある文字を扱っている場合、承認された解決策は二重のエンコードになります。

  • クエリパラメータはNameValueCollectionインデクサー(を使用して自動エンコードされます。これはUrlEncodeUnicodeを使用します。通常は想定されていませんUrlEncode(!)
  • その後、uriBuilder.Uriを呼び出すと、もう一度エンコーディングを行うコンストラクターを使用して新しいUriが作成されます(通常の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!

出力:

?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上の私の最新の.NET上のP.S、bool docコメント付きのUriコンストラクタは「廃止されました。dontEscapeは常にfalseです」と言っていますが、実際には期待通りに動作します(スキップをスキップ)

それでそれはもう一つのバグのように見えます...

そして、これさえ明らかに間違っています - それは、サーバが期待するものだけではなく、UrlEncodedUnicodeをサーバに送信します

更新:もう1つ、NameValueCollectionは実際にはUrlEncodeUnicodeを使用します。これはもう使用されることはなく、通常のurl.encode/decodeとは互換性がありません( Name Query to the URL Query? を参照)。

つまり、NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);と一緒にこのハックを使うことは絶対にありません。あなたのUnicodeクエリパラメータをめちゃくちゃにするからです。手動でクエリを作成し、それをUriBuilder.Queryに割り当てるだけで、必要なエンコードが行われ、UriBuilder.Uriを使用してUriが取得されます。

このように使用されることは想定されていないコードを使用して自分自身を傷つけた典型的な例

32

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

あなたがチェックアウトしたいかもしれません Flurl [開示:私は作者です]、それを本格的なRESTクライアントに拡張するオプションのコンパニオンlibを持つ流暢なURLビルダー。

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

21
Todd Menier

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;
3
Mike Perrenoud

あるいは単に私の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

2
Roman Ratskey

RFC 6570 URIテンプレートライブラリ 私が開発しているものはこの操作を実行することができます。すべてのエンコーディングはそのRFCに従ってあなたのために処理されます。これを書いている時点ではベータリリースが利用可能です。安定版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);
2
Sam Harwell

クエリビルダーが組み込まれているため、いつでも 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();
1
Nikolay Hristov

HttpUtility.ParseQueryString()の代わりにUriBuilder.Uri.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();
1
Jpsy

この数回は再利用する必要があるので、このクラスを考え出したのは、クエリ文字列がどのように構成されるかを抽象化するのに役立つからです。

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

1
Jaider

Taras.roshkoの回答で説明されている二重エンコードの問題を回避し、クエリパラメータを簡単に操作できるようにするには、uriBuilder.Uri.ParseQueryString()の代わりにHttpUtility.ParseQueryString()を使用できます。

0
Valeriy Lyuchyn

"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();
    }
}
0
Waleed A.K.

これはRoman Ratskeyの答えに基づいた.Net Coreのための私の解決策です。型NameValueCollectionは.Net Coreで削除されました。

コード

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

0
Ângelo Polotto

Rostovの投稿と同じ行に沿って、プロジェクトにSystem.Webへの参照を含めたくない場合は、System.Net.Http.FormattingからFormDataCollectionを使用して、次のようなことを実行できます。

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();
0
cwills