System.Net.HttpClientを使用してhttp getリクエストを送信したい場合、パラメーターを追加するAPIがないようですが、これは正しいですか?
クエリ文字列を作成するために利用できる簡単なAPIはありますか?名前値コレクションの作成とそれらをURLエンコードしてからそれらを最終的に連結することは含まれていませんか?RestSharpのapi(AddParameter(..)など)を使用したいと思っていました。
System.Net.HttpClientを使用してhttp getリクエストを送信したい場合、パラメーターを追加するAPIがないようですが、これは正しいですか?
クエリ文字列を作成するために利用できる簡単なAPIはありますか?名前値コレクションの作成とそれらをURLエンコードしてからそれらを最終的に連結することは含まれていませんか?RestSharpのapi(AddParameter(..)など)を使用したいと思っていました。
回答:
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
メソッドに安全にフィードする以上のことができること。
#
Fragmentプロパティを使用して、フラグメント()を含むURLも処理します。組み込みツールを使用する代わりに、URLを手動で処理するというミスを犯している多くの人々を見てきました。
NameValueCollection.ToString()
は通常、クエリ文字列を作成せずToString
、の結果に対してParseQueryString
を実行すると新しいクエリ文字列が生成され、その機能が保証されないため、いつでも破損する可能性があることを示すドキュメントはありません。
System.Web
まだ使用していないプロジェクトに含めたくない場合は、FormUrlEncodedContent
from 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;
}
dispose
ですか?再利用のような正当な理由がない限り、私は常に処分しHttpClient
ます。
TL; DR:ユニコード文字の処理に関して完全に壊れているため、承認されたバージョンを使用しないでください。また、内部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上の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
ます。
このような使用が想定されていないコードを使用して自分を傷つけることの主な例
var namedValues = HttpUtility.ParseQueryString(builder.Query)
ですが、返されたNameValueCollectionを使用する代わりに、すぐに次のように辞書に変換しvar dic = values.ToDictionary(x => x, x => values[x]);
ます。新しい値を辞書に追加し、それをコンストラクタに渡してFormUrlEncodedContent
呼び出しますReadAsStringAsync().Result
。これにより、適切にエンコードされたクエリ文字列が得られ、UriBuilderに割り当てることができます。
<add key="aspnet:DontUsePercentUUrlEncoding" value="true" />
。私はこの動作に依存しないので、以前の回答で示されているように、FormUrlEncodedContentクラスを使用することをお勧めします:stackoverflow.com/a/26744471/88409
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));
フル機能の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
Uri
代わりに、独自のクラスで拡張または開始しないのはなぜstring
ですか?
Url
クラスから始めました。上記はnew Url("https://api.com").AppendPathSegment...
、キーストロークが少なくドキュメントで標準化されているため、個人的には文字列拡張を優先していますが、どちらの方法でも実行できます。
Rostovの投稿と同じようSystem.Web
に、プロジェクトへの参照を含めたくない場合は、FormDataCollection
from 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();
ダリンは興味深くて賢い解決策を提供しました、そしてこれは別のオプションかもしれないものです:
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;
kvp.Key
でkvp.Value
個別にエンコードすることをお勧めします(したがって&
、および=
文字をエンコードしません)。
server.UrlEncode
と置き換えることができますWebUtility.UrlEncode
または単に私の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"}
});
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);
この数回再利用する必要があるため、クエリ文字列の構成方法を抽象化するのに役立つこのクラスを思いつきました。
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
taras.roshkoの回答で説明されている二重エンコードの問題を回避し、クエリパラメータを簡単に操作できるようにするuriBuilder.Uri.ParseQueryString()
には、の代わりにを使用できますHttpUtility.ParseQueryString()
。
受け入れられた回答の大部分は、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();
「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();
}
}
辞書を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¶m2=value2"
param.ToQueryString("&"); //Will add (&)
//"¶m1=value1¶m2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1¶m2=value2"