TL; DR: не использовать принятую версию, так как она полностью не работает с обработкой символов Юникода и никогда не использует внутренний API
На самом деле я обнаружил странную проблему двойного кодирования с принятым решением:
Итак, если вы имеете дело с символами, которые необходимо кодировать, то принятое решение приводит к двойному кодированию:
- параметры запроса автоматически кодируются с помощью
NameValueCollection
индексатора ( и это использует UrlEncodeUnicode
, а не обычное ожидание UrlEncode
(!) )
- Затем при вызове
uriBuilder.Uri
он создает новый Uri
конструктор using, который выполняет кодирование еще раз (обычное кодирование 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
конструктор
PS в моем последнем .NET на Windows Server Uri
конструктор с комментарием bool doc говорит: «устарел, dontEscape всегда ложен», но на самом деле работает как положено (пропускает экранирование)
Так что похоже на еще одну ошибку ...
И даже это совершенно неправильно - он отправляет UrlEncodedUnicode на сервер, а не только UrlEncoded, что ожидает сервер
Обновление: еще одна вещь, NameValueCollection фактически делает UrlEncodeUnicode, который больше не должен использоваться и несовместим с обычным url.encode / decode (см. NameValueCollection для URL Query? ).
Итак, суть в том, что никогда не используйте этот хак,NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
поскольку он испортит параметры вашего запроса в Юникоде. Просто создайте запрос вручную и назначьте его, UriBuilder.Query
который будет выполнять необходимую кодировку, а затем получите Uri, используя UriBuilder.Uri
.
Яркий пример причинения себе вреда при использовании кода, который не должен использоваться таким образом