Convertir ruta de archivo a un archivo URI?

201

¿Tiene .NET Framework algún método para convertir una ruta (por ejemplo "C:\whatever.txt") en un URI de archivo (por ejemplo "file:///C:/whatever.txt")?

La clase System.Uri tiene el reverso (de un URI de archivo a una ruta absoluta), pero nada en lo que puedo encontrar para convertir a un URI de archivo.

Además, esta no es una aplicación ASP.NET.

Tinister
fuente

Respuestas:

292

El System.Uriconstructor tiene la capacidad de analizar rutas de archivos completas y convertirlas en rutas de estilo URI. Entonces puedes hacer lo siguiente:

var uri = new System.Uri("c:\\foo");
var converted = uri.AbsoluteUri;
JaredPar
fuente
78
var path = new Uri("file:///C:/whatever.txt").LocalPath;convierte un Uri de nuevo en una ruta de archivo local también para cualquiera que lo necesite.
Pondidum
2
Como una nota Se puede hacer clic en ese tipo de Uri en la salida VS y la salida de las pruebas de la unidad R # en las ventanas de la sesión
AlfeG
77
Esto desafortunadamente no es correcto. Por ejemplo, new Uri(@"C:\%51.txt").AbsoluteUrite da en "file:///C:/Q.txt"lugar de"file:///C:/%2551.txt"
poizan42
2
esto no funcionará con la ruta con espacios, es decir: "C: \ carpeta de prueba \ whatever.txt"
Quad Coders
Tampoco funcionará con rutas que contengan un carácter #.
Lewis
42

Lo que nadie parece darse cuenta es que ninguno de los System.Uriconstructores maneja correctamente ciertas rutas con signos de porcentaje en ellas.

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

Esto te da en "file:///C:/Q.txt"lugar de "file:///C:/%2551.txt".

Ninguno de los valores del argumento dontEscape en desuso hace ninguna diferencia, y al especificar UriKind también se obtiene el mismo resultado. Intentar con UriBuilder tampoco ayuda:

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

Esto también regresa "file:///C:/Q.txt".

Por lo que puedo decir, el marco realmente carece de cualquier forma de hacer esto correctamente.

Podemos intentarlo reemplazando las barras diagonales inversas con barras diagonales hacia adelante y alimentando el camino a Uri.EscapeUriString, es decir

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

Esto parece funcionar al principio, pero si le das el camino, C:\a b.txtentonces terminas en file:///C:/a%2520b.txtlugar de file:///C:/a%20b.txt: de alguna manera, decide que algunas secuencias deben decodificarse pero no otras. Ahora podríamos prefijarnos a "file:///"nosotros mismos, sin embargo, esto no toma \\remote\share\foo.txten cuenta las rutas UNC , lo que parece ser generalmente aceptado en Windows es convertirlas en pseudo-urls de la forma file://remote/share/foo.txt, por lo que también debemos tener eso en cuenta.

EscapeUriStringTambién tiene el problema de que no escapa al '#'personaje. En este punto, parecería que no tenemos otra opción que hacer nuestro propio método desde cero. Entonces esto es lo que sugiero:

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

Esto deja intencionalmente + y: sin codificar, ya que parece ser la forma en que generalmente se hace en Windows. También solo codifica latin1 ya que Internet Explorer no puede entender los caracteres unicode en las URL de los archivos si están codificados.

poizan42
fuente
¿Hay un nuget que incluya esto con una licencia liberal? Es una pena manera no adecuada para esto existe en el marco y mantener actualizado copypasta es demasiado duro ...
binki
44
Puede usar el código anterior bajo los términos de la licencia del MIT (no creo que algo tan corto deba tener derechos de autor, pero ahora tiene una concesión explícita)
poizan42
9

Las soluciones anteriores no funcionan en Linux.

Usando .NET Core, intentando ejecutar new Uri("/home/foo/README.md")resultados en una excepción:

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

Debe dar al CLR algunas pistas sobre qué tipo de URL tiene.

Esto funciona:

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

... y la cadena devuelta por fileUri.ToString()es"file:///home/foo/README.md"

Esto también funciona en Windows.

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

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

Bob Stine
fuente
Si sabe que el camino es absoluto, puede usarlonew Uri("/path/to/file", UriKind.Absolute);
Minijack
8

VB.NET:

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

Diferentes salidas:

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

Un trazador de líneas:

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

Salida :file:///D:/Development/~AppFolder/Att/1.gif

Mr Calvin
fuente
2
AbsoluteUries correcto porque codifica también espacios a% 20.
psulek
Estoy convencido de que esto sufre los mismos problemas descritos en la respuesta que habla sobre el manejo de personajes especiales .
binki
4

Al menos en .NET 4.5+ también puedes hacer:

var uri = new System.Uri("C:\\foo", UriKind.Absolute);
Gavin Greenwalt
fuente
1
¿No te arriesgas a tener un UriFormatExceptiondía?
berezovskyi
Esto tampoco funciona correctamente, new Uri(@"C:\%51.txt",UriKind.Absolute).AbsoluteUriregresa en "file:///C:/Q.txt"lugar de"file:///C:/%2551.txt"
poizan42
2

UrlCreateFromPath al rescate! Bueno, no del todo, ya que no admite formatos de ruta extendidos y UNC, pero eso no es tan 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);

En caso de que la ruta comience con un prefijo especial, se elimina. Aunque la documentación no lo menciona, la función genera la longitud de la URL incluso si el búfer es más pequeño, por lo que primero obtengo la longitud y luego asigno el búfer.

Algunos muy observación interesante que tuve es que mientras "\\ device \ path" se transforma correctamente en "file: // device / path", específicamente "\\ localhost \ path" se transforma simplemente en "file: /// path" .

La función WinApi logró codificar caracteres especiales, pero deja los caracteres específicos de Unicode sin codificar, a diferencia del constructor Uri . En ese caso, AbsoluteUri contiene la URL codificada correctamente, mientras que OriginalString puede usarse para retener los caracteres Unicode.

IllidanS4 quiere que Monica regrese
fuente
0

La solución es simple. Simplemente use el método Uri (). ToString () y codifique con espacios en blanco, si corresponde, después.

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

devuelve correctamente el archivo: /// C: / my% 20example ㄓ .txt

Las cosas pasan
fuente