将文件路径转换为文件URI?

. net框架是否有将路径(例如"C:\whatever.txt")转换为文件URI(例如"file:///C:/whatever.txt")的方法?

系统。Uri类具有相反的(从文件URI到绝对路径),但就我所能找到的转换为文件URI而言,没有任何东西。

另外,这是一个ASP。网络应用程序。

245512 次浏览

System.Uri构造函数具有解析完整文件路径并将其转换为URI样式路径的能力。所以你可以这样做:

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

VB。NET:

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

不同的输出:

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

一个衬套:

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

输出: file:///D:/Development/~AppFolder/Att/1.gif

至少在。net 4.5+中你还可以做到:

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

似乎没有人意识到的是,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:///"作为前缀,但是这没有考虑到像\\remote\share\foo.txt这样的UNC路径——在Windows上似乎普遍接受的是将它们转换为file://remote/share/foo.txt形式的伪url,所以我们也应该考虑到这一点。

EscapeUriString还有一个问题,它不能转义'#'字符。在这一点上,我们似乎没有其他选择,只能从头开始制作我们自己的方法。这就是我的建议:

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

这是故意让+和:不编码,因为这似乎是通常在Windows上做的事情。它也只编码latin1,因为Internet Explorer无法理解文件url中的unicode字符。

上述解决方案在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()返回的字符串是"file:///home/foo/README.md"

这也适用于Windows。

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

...发出"file:///C:/Users/foo/README.md"

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构造函数那样对特定于unicode的字符进行编码。在这种情况下,AbsoluteUri包含正确编码的URL,而OriginalString可用于保留Unicode字符。

变通办法很简单。只需使用Uri(). tostring()方法,然后使用百分比编码的空格(如果有的话)。

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

正确地返回文件:C: / / / /我% 20例ㄓ. txt