关于 .NET 在不同操作系统中 IO 文件路径拼接方法结升级 .NET 7 后注意到的一个小坑

时间:2022-11-24 11:05:50

.NET 现在支持跨平台这件事情已经是众所周知的特点了,虽然平台整体支持跨平台了,但是我们的代码如果真的想要实现跨平台运行其实还是有些小细节要注意的,今天想要记录分享的就是关于 文件I/O操作时路径的拼接问题。

在 Windows 环境下我们常见的路径格式如下:

D:\Software\AppData\Files\aaa.jpg

可以看到 Windows 环境下文分隔符为 \ 路径由三部分组成分别是:

  1. 盘符: D:\
  2. 文件夹层级:Software\AppData\Files
  3. 文件名:aaa.jpg

在 .NET 平台常见的获取当成程序主机路径的方法主要从

.NET 控制台程序,通过依赖注入获取 IHostEnvironment hostEnvironment

.NET Web程序,通过依赖注入获取 IWebHostEnvironment webHostEnvironment

hostEnvironment.ContentRootPath
webHostEnvironment.ContentRootPath

ContentRootPath 指的是应用程序内容文件的目录的绝对路径;


webHostEnvironment.WebRootPath

WebRootPath 指的是 其实就是用于存放静态资源的那个 wwwroot 目录的绝对路径,ASP.NET Core MVC 项目的 css、 js、 img 等静态资源一般都是存放在 wwwroot 目录中,ASP.NET Core WebAPI 项目有需要也可以开启这个 wwwroot 的选项,只要在项目启动的时候 app.UseStaticFiles(); 启用静态文件模块即可。


在刚开始接触 .NET 项目时,我代码中的文件上传路径是这样拼接的。

webHostEnvironment.ContentRootPath + "files\\"+ DateTime.UtcNow.ToString("yyyy\\MM\\dd\\")+"xxx.jpg";

这样组合出来的路径地址可能如下:

d:\appdata\files\2022\11\24\xxx.jpg

如果代码这样写,我们在 Windows 平台运行是不会有有任何问题的,但是如果有一天想要尝试跨平台部署,把代码搬到 Linux 或者 Mac 平台运行就会发现这个代码会报错,原因在于 Linux 和 Mac 平台无法识别 \ 分割凭借的文件路径,因为这两个平台是采用 / 做为文件路径分割符的。

比如 Linux 下的常见路径格式如下:

/var/appdata/xxxx

所以这个时候我们只要调整我们的代码为

webHostEnvironment.ContentRootPath + "files/"+ DateTime.UtcNow.ToString("yyyy/MM/dd/")+"xxx.jpg";

凭借出来的路径格式则为

d:/appdata/files/2022/11/24/xxx.jpg

/var/appdata/files/2022/11/24/xxx.jpg

重新编译之后就可以在 Linux 和 Mac 平台运行了,并且 Windows 平台其实也是可以兼容 / 作为文件路径分割符号的,至此三个平台都可以正常运行了。

上面的代码运行了3年左右时间,直至最近更新了 .NET 7 发现上面的代码,在服务器上又报错了,上面的代码执行效果变成了下面这样

d:/appdatafiles/2022/11/24/xxx.jpg

/var/appdatafiles/2022/11/24/xxx.jpg

通过观察可以发现原来是 appdata/files 之间的 分隔符 / 消失了,导致拼接的结果变成了 appdatafiles ,经过调试之后发现原因如下:
在 .NET 6.0 及以前的版本中

webHostEnvironment.ContentRootPath;
webHostEnvironment.WebRootPath;
hostEnvironment.ContentRootPath;

三个变量的末尾都是带有一个分隔符的,他们的取值都是

d:/appdata/var/appdata/ 像这样尾部有跟随一个 / 分割符,但是到了 .NET 7.0 中,他们的取值变了,变成了
d:/appdatavar/appdata 尾部的分割符号不见了,这就导致我们上面的路径拼接代码出现了异常。

这时候想起来微软官方自带的拼接方法 Path.Combine ,该方法用于将多个路径信息进行拼接,改造后的代码如下

Path.Combine(webHostEnvironment.ContentRootPath, "files", DateTime.UtcNow.ToString("yyyy"),DateTime.UtcNow.ToString("MM"),DateTime.UtcNow.ToString("dd"),"xxx.jpg");
这样的到结果如下

d:\appdata\files\2022\11\24\xxx.jpg

/var/appdata/files/2022/11/24/xxx.jpg

可以看到在 Windows 平台运行时还是采用了默认的 \ 作为文件夹的分割符号,而在 Linux 和 Mac 平台运行时则采用了 / 作为文件夹的分割符号。

虽然通过 Path.Combine 可以自动生成符合各个平台运行要求的路径,倒是如果需要把文件路径保存起来的时候还是建议采用 / 作为文件分隔符,这样方便随时切换运行平台,否则 代码在 Windows 平台运行期间产生的数据保存到数据库之后,将来有一天切换到其他平台时这样的路径被查询出来执行时还是会报错,但是采用 / 作为文件分隔符则不需要担心,所以像文件上传方法这种场景在需要记录文件路径到数据库时可以 .Replace("\","/") 对路径进行一下转换之后再保存到数据库中

Path.Combine(webHostEnvironment.ContentRootPath, "files", DateTime.UtcNow.ToString("yyyy"),DateTime.UtcNow.ToString("MM"),DateTime.UtcNow.ToString("dd"),"xxx.jpg").Replace("\\","/");


可能有人会问,为什么 Windows 就不能和 Mac 与 Linux 等系统一样本身也默认采用 / 作为文件分隔符,直接大统一多好,其实这属于历史遗留问题了,因为在 Windows 平台还是 DOC 的时候,那个时候 / 在 Windows 平台是作为命令的参数标记使用的,所以为了不和 命令参数符号 / 重复,就采用最为接近的 \ 充当了路径分隔符,而 Linux 与 Mac 平台传递参数则是采用 - 符号,如我们熟知的 ipconfig 命令。

默认查询的简单信息,如果需要查询全部信息则是
ipconfig /all

如果需要清理 dns 缓存信息则是
ipconfig /flushdns

可以看到传递参数时是需要 / 符号的,当然现在新版的 Windows 系统其实也支持 - 作为参数传递符号了,下面的命令也可以正常运行
ipconfig -all
ipconfig -flushdns

至此 关于 .NET 在不同操作系统中 IO 文件路径拼接方法总结 就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流,有兴趣的朋友可以关注我目前在维护的一个 .NET 基础框架项目,项目地址如下
https://github.com/berkerdong/NetEngine.git
https://gitee.com/berkerdong/NetEngine.git