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
メソッドにフィードする以上のことができるということ。
まだ使っていないプロジェクトにSystem.Web
を含めたくない人は、System.Net.Http
の FormUrlEncodedContent
を使って次のようにすることができます。
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上の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が取得されます。
このように使用されることは想定されていないコードを使用して自分自身を傷つけた典型的な例
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));
あなたがチェックアウトしたいかもしれません 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
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"}
});
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);
クエリビルダーが組み込まれているため、いつでも 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();
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();
この数回は再利用する必要があるので、このクラスを考え出したのは、クエリ文字列がどのように構成されるかを抽象化するのに役立つからです。
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()
を使用できます。
"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();
}
}
これは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);
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();