¿Convertir la ruta del archivo en un URI de archivo?
¿Tiene .NET Framework algún método para convertir una ruta (p. ej. "C:\whatever.txt"
) en un URI de archivo (p. ej. "file:///C:/whatever.txt"
)?
La clase System.Uri tiene lo contrario (de un URI de archivo a una ruta absoluta), pero hasta donde puedo encontrar nada para convertir a un URI de archivo.
Además, esta no es una aplicación ASP.NET.
El System.Uri
constructor 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;
Lo que nadie parece darse cuenta es que ninguno de los System.Uri
constructores maneja correctamente ciertas rutas con signos de porcentaje.
new Uri(@"C:\%51.txt").AbsoluteUri;
Esto te da "file:///C:/Q.txt"
en lugar de "file:///C:/%2551.txt"
.
Ninguno de los valores del obsoleto argumento dontEscape hace ninguna diferencia, y especificar UriKind también da el mismo resultado. Probar con UriBuilder tampoco ayuda:
new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri
Esto "file:///C:/Q.txt"
también regresa.
Hasta donde puedo decir, al marco le falta alguna forma de hacer esto correctamente.
Podemos intentarlo reemplazando las barras invertidas con barras diagonales y alimentando la ruta a, Uri.EscapeUriString
es decir
new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri
Esto parece funcionar al principio, pero si le da la ruta C:\a b.txt
, termina con file:///C:/a%2520b.txt
en lugar de file:///C:/a%20b.txt
: de alguna manera decide que algunas secuencias deben decodificarse pero no otras. Ahora podríamos simplemente anteponernos a "file:///"
nosotros mismos, sin embargo, esto no toma \\remote\share\foo.txt
en cuenta las rutas UNC; lo que parece ser generalmente aceptado en Windows es convertirlas en pseudo-URL del formulario file://remote/share/foo.txt
, por lo que también debemos tener eso en cuenta.
EscapeUriString
También tiene el problema de que no se le escapa al '#'
personaje. Parecería que a estas alturas no nos queda más remedio que crear 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 que así se hace normalmente 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.
Las soluciones anteriores no funcionan en Linux.
Al utilizar .NET Core, intentar ejecutar new Uri("/home/foo/README.md")
da como resultado 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 darle 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"