System.Net.HttpClientのクエリ文字列を作成する


184

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

クエリ文字列を作成するために利用できる簡単なAPIはありますか?名前値コレクションの作成とそれらをURLエンコードしてからそれらを最終的に連結することは含まれていませんか?RestSharpのapi(AddParameter(..)など)を使用したいと思っていました。


@Michael Perrenoud承認された回答をエンコードが必要な文字で使用することを再検討したい場合があります。以下の私の説明を参照してください
違法移民

回答:


309

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


9
これは.NETでのURL処理の点で絶対的に最高です。手動でURLエンコードして文字列の連結や文字列ビルダーなどを実行する必要はありません。UriBuilderクラスは#Fragmentプロパティを使用して、フラグメント()を含むURLも処理します。組み込みツールを使用する代わりに、URLを手動で処理するというミスを犯している多くの人々を見てきました。
Darin Dimitrov 2013年

6
NameValueCollection.ToString()は通常、クエリ文字列を作成せずToString、の結果に対してParseQueryStringを実行すると新しいクエリ文字列が生成され、その機能が保証されないため、いつでも破損する可能性があることを示すドキュメントはありません。
マシュー

11
HttpUtilityはSystem.Webにありますが、ポータブルランタイムでは使用できません。この機能がクラスライブラリで一般的に利用できないのは奇妙に思われます。
Chris Eldredge 2013

82
この解決策は卑劣です。.Netには適切なクエリ文字列ビルダーが必要です。
Kugel 2013

8
空の文字列を渡してユーティリティメソッドを呼び出すことによってのみ取得できる内部クラスに最適なソリューションが隠されているという事実は、正確なソリューションとは言えません。
Kugel 2014年

79

System.Webまだ使用していないプロジェクトに含めたくない場合は、FormUrlEncodedContentfrom 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;
}

辞書版

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

なぜusingステートメントを使用するのですか?
Ian Warburton 2017年

リソースを解放する可能性がありますが、これはやりすぎです。これを行わないでください。
Kody

5
KVP配列の代わりにDictionary <string、string>を使用すると、これはより簡潔になります。次に、初期化構文を使用します:{"ham"、 "Glazed?" }
ショーンB

@SeanBこれは、特に動的/不明なパラメーターのリストを追加するために何かを使用する場合に、良い考えです。この例では、「固定」リストであるため、辞書のオーバーヘッドに価値があるとは感じませんでした。
ロストフ

6
@Kodyなぜ使用しないと言うのdisposeですか?再利用のような正当な理由がない限り、私は常に処分しHttpClientます。
Dan Friedman

41

TL; DR:ユニコード文字の処理に関して完全に壊れているため、承認されたバージョンを使用しないでください。また、内部APIを使用しないでください。

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

したがって、エンコードする必要がある文字を処理している場合、受け入れられた解決策は二重エンコードにつながります:

  • クエリパラメーターはNameValueCollectionインデクサーを使用して自動エンコードされます(これはを使用しUrlEncodeUnicode、通常は期待されませんUrlEncode(!)
  • 次に、uriBuilder.Uriそれを呼び出すと、もう一度エンコーディングUriを行うコンストラクタ使用して新しいものが作成されます(通常のURLエンコーディング)
  • それはやってすることで回避することはできませんuriBuilder.ToString()(このリターンは修正にもかかわらず、UriIMO多分少なくとも矛盾、バグであるが、それは別の問題だ)してから使用して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上のPS Uri、ブールドキュメンテーションコメント付きのコンストラクターは「廃止され、dontEscapeは常にfalseです」と述べていますが、実際には期待どおりに機能します(エスケープをスキップします)。

別のバグのようです...

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

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

したがって、最終的にはNameValueCollection query = HttpUtility.ParseQueryString(builder.Query);このハックを使用しないでください。Unicodeクエリパラメータが混乱するためです。クエリを手動で作成UriBuilder.Queryし、必要なエンコーディングを行うクエリに割り当ててから、を使用してUriを取得しUriBuilder.Uriます。

このような使用が想定されていないコードを使用して自分を傷つけることの主な例


16
機能するこの答えに完全なユーティリティ関数を追加できますか?
mafu 2016年

8
私はこれについて2番目のまふです。私は答えを読みましたが、結論はありません。これに対する決定的な答えはありますか?
リチャードグリフィス

3
また、この問題の決定的な答えを見たい
ポネス

この問題の明確な答えはを使用することvar namedValues = HttpUtility.ParseQueryString(builder.Query)ですが、返されたNameValueCollectionを使用する代わりに、すぐに次のように辞書に変換しvar dic = values.ToDictionary(x => x, x => values[x]); ます。新しい値を辞書に追加し、それをコンストラクタに渡してFormUrlEncodedContent呼び出しますReadAsStringAsync().Result。これにより、適切にエンコードされたクエリ文字列が得られ、UriBuilderに割り当てることができます。
Triynko 16

実際に、すべてではなくNamedValueCollection.ToStringを使用できますが、ASP.NETが '%uXXXX'エンコーディングを使用できないようにするapp.config / web.config設定を変更した場合のみです<add key="aspnet:DontUsePercentUUrlEncoding" value="true" />。私はこの動作に依存しないので、以前の回答で示されているように、FormUrlEncodedContentクラスを使用することをお勧めします:stackoverflow.com/a/26744471/88409
Triynko

41

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

2
このプロセスを使用しても、同じキーに複数の値を送信することはできません。fooの一部として「bar」と「bar2」を送信する場合、それは不可能です。
m0g

2
これは、最新のアプリに最適な答えであり、私のシナリオで機能し、シンプルでクリーンです。ただし、エスケープメカニズムは必要ありません-テストされていません。
Patrick Stalph、2018

このNuGetパッケージは.NET標準2.0を対象としています。つまり、完全な.NETフレームワーク4.6.1以降で使用できます
eddiewould

24

フル機能のRESTクライアントに拡張するオプションのコンパニオンライブラリを備えた流暢なURLビルダーであるFlurl [開示:私は作者です] をチェックしてみてください。

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


2
Uri代わりに、独自のクラスで拡張または開始しないのはなぜstringですか?
mpen

2
技術的には、自分のUrlクラスから始めました。上記はnew Url("https://api.com").AppendPathSegment...、キーストロークが少なくドキュメントで標準化されているため、個人的には文字列拡張を優先していますが、どちらの方法でも実行できます。
トッド・メニア2014

オフトピックですが、本当に素晴らしいlibです。これを見てから使用しています。IHttpClientFactoryもご利用いただきありがとうございます。
Ed S.

4

Rostovの投稿と同じようSystem.Webに、プロジェクトへの参照を含めたくない場合は、FormDataCollectionfrom 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();

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;

5
完全なクエリ文字列ではなく、forループ内kvp.Keykvp.Value個別にエンコードすることをお勧めします(したがって&、および=文字をエンコードしません)。
マシュー

マイクありがとう。他の提案されたソリューション(NameValueCollectionを含む)は、PCLプロジェクトに参加しているため機能しませんでした。クライアント側で作業している他のユーザーは、次のものserver.UrlEncodeと置き換えることができますWebUtility.UrlEncode
BCA

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


27
URLエンコードを忘れていませんか?
Kugel 2013

1
これは、拡張機能を使用して明確で有用なヘルパーを作成する良い例です。あなたが受け入れ答えと組み合わせる場合は、固体RestClient構築にあなたの方法にしている
emran

2

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

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


1

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


1

受け入れられた回答の大部分は、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();

FYI:これはへの参照が必要ですSystem.Net.Httpをとして、ParseQueryString()拡張メソッドが内にありませんSystem
Sunny Patel、2018

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

-1

辞書を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"

1
このソリューションには、パラメーターの適切なURLエンコードが欠落しており、「無効な」文字を含む値では機能しません
Xavier Poinas

答えを自由に更新して、不足しているエンコード行を追加してください。これはコード行です。
Diego Mendes、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.