System.Net.HttpClient を使用して http get リクエストを送信したい場合、パラメータを追加する API がないようですが、これは正しいですか?
名前値のコレクションとそれらをエンコードする URL を作成し、最後にそれらを連結することを伴わない、クエリ文字列を作成するために使用できる単純な API はありますか? RestSharp の API (つまり、AddParameter(..)) のようなものを使用したいと思っていました。
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
。
まだ使用していないプロジェクトに含めたくない場合は、 fromをSystem.Web
使用して次のようにすることができます。FormUrlEncodedContent
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;
}
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 の PS、Uri
bool doc コメント付きのコンストラクターは「時代遅れ、dontEscape は常に false」と言っていますが、実際には期待どおりに動作します (エスケープをスキップします)。
だからそれは別のバグのように見えます...
そして、これでさえ明らかに間違っています-サーバーが期待するものだけでなく、UrlEncodedUnicodeをサーバーに送信します
更新: もう 1 つ、NameValueCollection は実際には UrlEncodeUnicode を実行しますが、これはもう使用されることは想定されておらず、通常の url.encode/decode とは互換性がありません (NameValueCollectionから URL クエリへの参照? を参照)。
要するに、Unicode クエリ パラメータが台無しになるため、このハックを決して使用しないでください。NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
クエリを手動で作成UriBuilder.Query
し、必要なエンコーディングを行うに割り当ててから、 を使用して Uri を取得するだけUriBuilder.Uri
です。
このような使用を想定されていないコードを使用して自分を傷つける代表的な例
本格的な REST クライアントに拡張するオプションのコンパニオン lib を備えた流暢な 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
この数回再利用する必要があるため、クエリ文字列の構成方法を抽象化するのに役立つこのクラスを思いつきました。
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
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;
または単に私の 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"}
});
"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();
}
}