Преобразовать путь к файлу в URI файла?

имеет ли .NET Framework какие-либо методы для преобразования пути (например,"C:whatever.txt") в файл URI (например,"file:///C:/whatever.txt")?

на

6 ответов


на System.Uri конструктор имеет возможность анализировать полные пути к файлам и превращать их в пути стиля URI. Таким образом, вы можете просто сделать следующее:

var uri = new System.Uri("c:\foo");
var converted = uri.AbsoluteUri;

кажется, никто не понимает, что ни один из 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, чтобы превратить их в псевдо-urls формы file://remote/share/foo.txt, и мы должны принять это во внимание.

EscapeUriString также есть проблема, что он не избегает '#' символ. Казалось бы, на данный момент у нас нет другого выбора, кроме как сделать свой собственный метод с нуля. Вот что я предлагаю:--18-->

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();
}

это намеренно оставляет + и : unencoded, как это обычно делается в Windows. Он также кодирует только latin1, поскольку Internet Explorer не может понять символы юникода в URL-адресах файлов, если они закодированы.


VB.NET:

Dim URI As New Uri("D:\Development\~AppFolder\Att.gif")

разные выходы:

URI.AbsolutePath   ->  D:/Development/~AppFolder/Att/1.gif  
URI.AbsoluteUri    ->  file:///D:/Development/~AppFolder/Att/1.gif  
URI.OriginalString ->  D:\Development\~AppFolder\Att.gif  
URI.ToString       ->  file:///D:/Development/~AppFolder/Att/1.gif  
URI.LocalPath      ->  D:\Development\~AppFolder\Att.gif

один лайнер:

New Uri("D:\Development\~AppFolder\Att.gif").AbsoluteUri

выход: file:///D:/Development/~AppFolder/Att/1.gif


решения выше не работают на Linux.

использование .NET Core, попытка выполнить new Uri("/home/foo/README.md") приводит к исключению:

Unhandled Exception: System.UriFormatException: Invalid URI: The format of the URI could not be determined.
   at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
   at System.Uri..ctor(String uriString)
   ...

вам нужно дать CLR некоторые подсказки о том, какой URL у вас есть.

это работает:

Uri fileUri = new Uri(new Uri("file://"), "home/foo/README.md");

...и строка, возвращенная fileUri.ToString() is "file:///home/foo/README.md"

это работает и на Windows.

new Uri(new Uri("file://"), @"C:\Users\foo\README.md").ToString()

...выдает "file:///C:/Users/foo/README.md"


по крайней мере, в .NET 4.5+ вы также можете сделать:

var uri = new System.Uri("C:\foo", UriKind.Absolute);

UrlCreateFromPath на помощь! Ну, не совсем, так как он не поддерживает расширенные и UNC-форматы пути, но это не так сложно преодолеть:

public static Uri FileUrlFromPath(string path)
{
    const string prefix = @"\";
    const string extended = @"\?\";
    const string extendedUnc = @"\?\UNC\";
    const string device = @"\.\";
    const StringComparison comp = StringComparison.Ordinal;

    if(path.StartsWith(extendedUnc, comp))
    {
        path = prefix+path.Substring(extendedUnc.Length);
    }else if(path.StartsWith(extended, comp))
    {
        path = prefix+path.Substring(extended.Length);
    }else if(path.StartsWith(device, comp))
    {
        path = prefix+path.Substring(device.Length);
    }

    int len = 1;
    var buffer = new StringBuilder(len);
    int result = UrlCreateFromPath(path, buffer, ref len, 0);
    if(len == 1) Marshal.ThrowExceptionForHR(result);

    buffer.EnsureCapacity(len);
    result = UrlCreateFromPath(path, buffer, ref len, 0);
    if(result == 1) throw new ArgumentException("Argument is not a valid path.", "path");
    Marshal.ThrowExceptionForHR(result);
    return new Uri(buffer.ToString());
}

[DllImport("shlwapi.dll", CharSet=CharSet.Auto, SetLastError=true)]
static extern int UrlCreateFromPath(string path, StringBuilder url, ref int urlLength, int reserved);

если путь начинается со специального префикса, он удаляется. Хотя в документации об этом не упоминается, функция выводит длину URL-адреса, даже если буфер меньше, поэтому я сначала получаю длину, а затем выделяю буфер.

некоторые очень интересные наблюдение у меня было, что, хотя "\\device\path" правильно преобразован в "file://device/path", в частности "\\localhost\path" преобразуется в "file:///path".

функция WinApi сумела кодировать специальные символы, но оставляет символы, специфичные для Юникода, не закодированными, в отличие от Uri construtor. В таком случае ...--8-->AbsoluteUri содержит правильно закодированный URL, в то время как OriginalString может использоваться для сохранения Юникода письмена.