То, что никто, кажется, не понимает, - то, что ни один из System.Uri
конструкторов правильно не обрабатывает определенные пути с символами процента в них.
new Uri(@"C:\%51.txt").AbsoluteUri;
Это дает вам "file:///C:/Q.txt"
вместо "file:///C:/%2551.txt"
.
Ни одно из значений устаревшего аргумента dontEscape не имеет никакого значения, и указание UriKind также дает тот же результат. Попытка с UriBuilder также не помогает:
new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri
Это также возвращается "file:///C:/Q.txt"
.
Насколько я могу сказать, в фреймворке на самом деле отсутствует какой-либо способ сделать это правильно.
Мы можем попытаться сделать это, заменив обратную косую черту прямой косой чертой и указав путь к Uri.EscapeUriString
- т.е.
new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri
Поначалу кажется, что это работает, но если вы дадите ему путь, C:\a b.txt
то в итоге вы получите file:///C:/a%2520b.txt
вместо file:///C:/a%20b.txt
- каким-то образом он решит, что некоторые последовательности должны быть декодированы, а другие нет. Теперь мы могли бы просто "file:///"
поставить перед собой префикс , однако в нем не учитываются пути UNC, например, \\remote\share\foo.txt
что в Windows кажется общепринятым - превращать их в псевдо-URL-адреса формы file://remote/share/foo.txt
, поэтому мы должны принять это во внимание также.
EscapeUriString
Также есть проблема в том, что он не избежит '#'
персонажа. В этот момент может показаться, что у нас нет другого выбора, кроме как создать собственный метод с нуля. Вот что я предлагаю:
public static string FilePathToFileUrl(string filePath)
{
StringBuilder uri = new StringBuilder();
foreach (char v in filePath)
{
if ((v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '9') ||
v == '+' || v == '/' || v == ':' || v == '.' || v == '-' || v == '_' || v == '~' ||
v > '\xFF')
{
uri.Append(v);
}
else if (v == Path.DirectorySeparatorChar || v == Path.AltDirectorySeparatorChar)
{
uri.Append('/');
}
else
{
uri.Append(String.Format("%{0:X2}", (int)v));
}
}
if (uri.Length >= 2 && uri[0] == '/' && uri[1] == '/') // UNC path
uri.Insert(0, "file:");
else
uri.Insert(0, "file:///");
return uri.ToString();
}
Это преднамеренно оставляет + и: незашифрованными, так как кажется, что это обычно делается в Windows. Он также кодирует только латиницу 1, поскольку Internet Explorer не может понимать символы Юникода в URL-адресах файлов, если они закодированы.
var path = new Uri("file:///C:/whatever.txt").LocalPath;
превращает Uri обратно в локальный путь к файлу для тех, кому это нужно.