Converter caminho do arquivo em um URI de arquivo?

201

O .NET Framework possui métodos para converter um caminho (por exemplo "C:\whatever.txt") em um URI de arquivo (por exemplo,"file:///C:/whatever.txt" )?

A classe System.Uri tem o inverso (de um URI de arquivo para o caminho absoluto), mas nada que eu possa encontrar para converter em um URI de arquivo.

Além disso, este não é um aplicativo ASP.NET.

Tinister
fonte

Respostas:

292

O System.Uriconstrutor tem a capacidade de analisar caminhos de arquivos completos e transformá-los em caminhos de estilo URI. Então você pode simplesmente fazer o seguinte:

var uri = new System.Uri("c:\\foo");
var converted = uri.AbsoluteUri;
JaredPar
fonte
78
var path = new Uri("file:///C:/whatever.txt").LocalPath;transforma um Uri de volta em um caminho de arquivo local também para quem precisa disso.
Pondidum 8/11
2
Como uma nota. Esses tipos de Uri é clicável no VS saída de testes de unidade de saída e R # no janelas da sessão
AlfeG
7
Infelizmente isso não está correto. Por exemplo, new Uri(@"C:\%51.txt").AbsoluteUridá-lhe em "file:///C:/Q.txt"vez de"file:///C:/%2551.txt"
poizan42
2
isso não funcionará com caminho com espaços, ou seja: "C: \ test folder \ Whatever.txt"
Quad Coders
Isso também não funcionará com caminhos que contenham um caractere #.
Lewis
42

O que ninguém parece perceber é que nenhum dos System.Uriconstrutores manipula corretamente certos caminhos com sinais de porcentagem neles.

new Uri(@"C:\%51.txt").AbsoluteUri;

Isso dá a você, em "file:///C:/Q.txt"vez de"file:///C:/%2551.txt" .

Nenhum dos valores do argumento dontEscape descontinuado faz qualquer diferença e a especificação do UriKind também fornece o mesmo resultado. Tentar com o UriBuilder também não ajuda:

new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri

Isso retorna "file:///C:/Q.txt"também.

Tanto quanto posso dizer, o framework realmente não tem nenhuma maneira de fazer isso corretamente.

Podemos tentar isso substituindo as barras invertidas por barras invertidas e alimentando o caminho para Uri.EscapeUriString- ie

new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri

Isso parece funcionar no começo, mas se você der o caminho C:\a b.txt, você termina com, em file:///C:/a%2520b.txtvez de file:///C:/a%20b.txt- de alguma forma, decide que algumas seqüências devem ser decodificadas, mas não outras. Agora podemos apenas prefixar a "file:///"nós mesmos, no entanto, isso não leva \\remote\share\foo.txtem consideração os caminhos UNC - o que parece ser geralmente aceito no Windows é transformá-los em pseudo-urls do formulário file://remote/share/foo.txt, por isso devemos levar isso em consideração também.

EscapeUriStringtambém tem o problema de não escapar do '#'personagem. Parece que, nesse ponto, não temos outra escolha a não ser fazer nosso próprio método a partir do zero. Então é isso que eu sugiro:

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

Isso deixa intencionalmente + e: sem codificação, pois parece que é assim que geralmente é feito no Windows. Ele também codifica latin1, pois o Internet Explorer não pode entender caracteres unicode em URLs de arquivo se eles estiverem codificados.

poizan42
fonte
Existe uma pepita que inclua isso com uma licença liberal? É uma pena nenhuma maneira adequada para este existe no quadro e manter copypasta atualizado é difícil também ...
binki
4
Você pode usar o código acima sob os termos da licença MIT (Eu não acredito que algo que curto deve mesmo ser a direitos de autor, mas agora você tem uma concessão explícita)
poizan42
9

As soluções acima não funcionam no Linux.

Usando o .NET Core, a tentativa de executar new Uri("/home/foo/README.md")resultados em uma exceção:

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)
   ...

Você precisa fornecer ao CLR algumas dicas sobre que tipo de URL você possui.

Isso funciona:

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

... e a string retornada por fileUri.ToString()é"file:///home/foo/README.md"

Isso funciona no Windows também.

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

... emite "file:///C:/Users/foo/README.md"

Bob Stine
fonte
Se você sabe o caminho é absoluto você pode usarnew Uri("/path/to/file", UriKind.Absolute);
Minijack
8

VB.NET:

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

Saídas diferentes:

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

Um forro:

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

Saída :file:///D:/Development/~AppFolder/Att/1.gif

MrCalvin
fonte
2
AbsoluteUriestá correto porque também codifica espaços para% 20.
Psulek
Estou convencido de que isso sofre dos mesmos problemas descritos na resposta que fala sobre manipulação de caracteres especiais .
binki
4

Pelo menos no .NET 4.5+, você também pode:

var uri = new System.Uri("C:\\foo", UriKind.Absolute);
Gavin Greenwalt
fonte
1
Você não corre o risco de conseguir UriFormatExceptionum dia?
precisa
Isso também não funciona corretamente, new Uri(@"C:\%51.txt",UriKind.Absolute).AbsoluteUriretorna em "file:///C:/Q.txt"vez de"file:///C:/%2551.txt"
poizan42
2

UrlCreateFromPath para o resgate! Bem, não inteiramente, pois ele não suporta os formatos de caminho estendido e UNC, mas isso não é tão difícil de superar:

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

Caso o caminho comece com um prefixo especial, ele será removido. Embora a documentação não mencione isso, a função gera o tamanho da URL, mesmo que o buffer seja menor, então eu primeiro obtenho o tamanho e aloco o buffer.

Alguns muito observação interessante que tive é que, enquanto "\\ device \ path" é corretamente transformado em "file: // device / path", especificamente "\\ localhost \ path" é transformado em apenas "file: /// path" .

A função WinApi conseguiu codificar caracteres especiais, mas deixa os caracteres específicos do Unicode não codificados, ao contrário do construtor Uri . Nesse caso, AbsoluteUri contém a URL codificada corretamente, enquanto OriginalString pode ser usado para reter os caracteres Unicode.

IllidanS4 quer Monica de volta
fonte
0

A solução alternativa é simples. Basta usar o método Uri (). ToString () e espaços em branco com codificação percentual, se houver, posteriormente.

string path = new Uri("C:\my exampleㄓ.txt").ToString().Replace(" ", "%20");

retorna corretamente o arquivo: /// C: / my% 20example ㄓ .txt

Coisas acontecem
fonte