.net 是否将文件路径转换为文件URI?

qltillow  于 2022-12-20  发布在  .NET
关注(0)|答案(8)|浏览(131)

.NET Framework是否有将路径(例如"C:\whatever.txt")转换成档案URI(例如"file:///C:/whatever.txt")的方法?
System.Uri类的情况正好相反(从文件URI到绝对路径),但就我所知,没有任何东西可以转换为文件URI。
此外,这不是ASP.NET应用程序。

ckx4rj1h

ckx4rj1h1#

System.Uri构造函数能够解析完整的文件路径,并将其转换为URI样式的路径。

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

rslzwgfq2#

似乎没有人意识到的是,没有一个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--不知何故,它决定了 * 一些 * 序列应该被解码,而其他的不应该。然而,这未能考虑诸如\\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上做的。它也只编码latin 1,因为Internet Explorer不能理解文件url中的unicode字符,如果他们被编码。

wwodge7n

wwodge7n3#

上述解决方案在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"

bjg7j2ky

bjg7j2ky4#

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

qni6mghb

qni6mghb5#

至少在.NET 4.5+中,您还可以:

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

wb1gzix06#

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字符。

u1ehiz5o

u1ehiz5o7#

不幸的是,@poizan42答案没有考虑到我们生活在Unicode世界中的事实,而且根据RFC 3986,它的限制性太强。可接受的答案@pierre-arnaud和@jaredpar依赖于System.Uri构造函数,该构造函数必须处理太多的Uri组件,以便能够管理文件名的可变性,并且它在百分比字符和其他情况下失败得很差。其他的答案都是简单化的或者根本没用的。最好的答案应该是@is4,但是在发布了这篇文章的第一个版本之后,我在我为我自己编写的测试用例中一起测试了它,它在许多Unicode字符上都失败了。
在我的例子中,我开始研究@poizan42代码和各种各样的答案,评论什么是工作的,什么是不工作的,所以我采取了一个稍微不同的方法。
首先我认为输入字符串是一个有效的文件路径,因此我在测试中使用所有有效的unicode字符和代理项对以编程方式创建了path。()似乎至少在Windows中返回了正确的集合。然后,我将这些路径传递给了一个方法,该方法是我按照ABNF路径规则实现的,您可以在https://www.ietf.org/rfc/rfc3986.txt的第22页找到该规则。
我将它的结果与UriBuilder生成的结果进行了比较,这是最终的修复结果:

public static string FilePathToFileUrl(string path)
{
    return new UriBuilder("file",string.Empty)
        {
            Path = path
                .Replace("%",$"%{(int)'%':X2}")
                .Replace("[",$"%{(int)'[':X2}")
                .Replace("]",$"%{(int)']':X2}"),
        }
        .Uri
        .AbsoluteUri;
}

这是完全未经优化的,并执行三个替换,所以随时可以将其转换为Span或StringBuilder。

sqxo8psd

sqxo8psd8#

解决方法很简单,只需使用Uri().ToString()方法,然后对空格(如果有的话)进行百分比编码。

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

正确返回 * 文件:///C:/my%20示例.txt*

相关问题