Lo que nadie parece darse cuenta es que ninguno de los System.Uri
constructores 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.txt
entonces terminas en file:///C:/a%2520b.txt
lugar 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.txt
en 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.
EscapeUriString
Tambié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.
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.