.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 指的是应用程序内容文件的目录的绝对路径;


Web 服务应用程序内容文件的目录的绝对路径

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

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

  1. Android中获取文件路径的方法总结及对照

    最近在写文件存贮,Android中获取文件路径的方法比较多,所以自己也很混乱.找了好几篇博客,发现了以下的路径归纳,记录一下,以备不时之需 Environment.getDataDirectory() ...

  2. IOS中获取文件路径的方法

    iphone沙箱模型的有四个文件夹,分别是什么,永久数据存储一般放在什么位置,得到模拟器的路径的简单方式是什么. documents,tmp,app,Library. (NSHomeDirectory ...

  3. 【转】c#.net各种应用程序中获取文件路径的方法

    控制台应用程序:Environment.CurrentDirectory.Directory.GetCurrentDirectory() windows服务:Environment.CurrentDi ...

  4. Linux操作系统中打开文件数量的查看方法

    Linux操作系统中打开文件数量的查看方法ulimit -n 4096也就是限制用户的最大文件打开数为4096个 在网上查了关于怎么查看文件打开数的文章大致有两种说法/proc/sys/fs/file ...

  5. 【转】 Linux内核中读写文件数据的方法--不错

    原文网址:http://blog.csdn.net/tommy_wxie/article/details/8193954 Linux内核中读写文件数据的方法  有时候需要在Linuxkernel--大 ...

  6. Java中获取文件路径

    Java中获取文件路径 1.实例说明 (1)得到 ClassPath的绝对URI路径 Thread.currentThread().getContextClassLoader().getResourc ...

  7. js/jquery 获取本地文件的文件路劲 获取input框中type=‘file’ 中的文件路径(转载)

     原文:http://blog.csdn.net/niyingxunzong/article/details/16989947 js/jquery 获取本地文件的文件路劲 获取input框中type= ...

  8. 在Python中使用glob模块查找文件路径的方法

    在Python中使用glob模块查找文件路径的方法 glob模块是最简单的模块之一,内容非常少.用它可以查找符合特定规则的文件路径名.跟使用windows下的文件搜索差不多.查找文件只用到三个匹配符: ...

  9. 在Python中操作文件之truncate()方法的使用教程

    在Python中操作文件之truncate()方法的使用教程 这篇文章主要介绍了在Python中操作文件之truncate()方法的使用教程,是Python入门学习中的基础知识,需要的朋友可以参考下 ...

  10. pip freeze > requirements.txt` 命令输出文件中出现文件路径而非版本号

    pip freeze > requirements.txt 命令输出文件中出现文件路径而非版本号 解决办法: pip list --format=freeze > requirements ...

随机推荐

  1. 插入排序C语言版本

    算法思路:        每趟将一个待排序的元素作为关键字,按照其关键字值的大小插入到已经排好的部分的适当位置上,直到插入完成.        数组中待排序的关键字前面的数据为已经排序的数据,关键字插 ...

  2. UEC++ 多线程(一) FRunnable

    虚幻官方文档:https://docs.unrealengine.com/5.0/en-US/API/Runtime/Core/HAL/FRunnable/ FRunnable "runna ...

  3. 还不会Traefik?看这篇文章就够了!

    文章转载自:https://mp.weixin.qq.com/s/ImZG0XANFOYsk9InOjQPVA 提到Traefik,有些人可能并不熟悉,但是提到Nginx,应该都耳熟能详. 暂且我们把 ...

  4. MySQL 安装(二进制版)

    MySQL 的安装方式一般分为三种,二进制版本.编译版本.RPM 包.比较常见的是二进制版本安装,方便简单,相对于编译安装,如果不是追求极致性能,使用起来差别不大.本次教程以二进制版本为例,系统为 c ...

  5. Docker搭建自己的Gitlab CI Runner

    转载自:https://cloud.tencent.com/developer/article/1010595 1.Gitlab CI介绍 CI:持续集成,我们通常使用CI来做一些自动化工作,比如程序 ...

  6. Docker MySql 查看版本的三种方法

    目录 Docker MySql 查看版本的三种方法 1.mysql -V命令查看版本 2.status命令查看版本 3.version命令查看版本 Docker MySql 查看版本的三种方法 1.m ...

  7. 【LeetCode第 313 场周赛】忘光光

    比赛链接 最近不怎么打比赛,不能马上反应过来考察的是什么,全部忘光光了... 6192. 公因子的数目 题意: 给定 \(a\) 和 \(b\),问两者的公因子数量 数据范围:\(1\leq a,b\ ...

  8. 学习ASP.NET Core Blazor编程系列五——列表页面

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  9. PCA原理及其代码实现

    首先简述一下PCA的作用: PCA是一种线性降维方法,它的目标i是通过某种线性投影,将高维的数据映射到低维空间中,并期望在所投影的维度上数据的信息量最大(方差最大),以此使用较少的数据维度,同时保留较 ...

  10. XSS-Game

    很简单的弹窗 http://192.168.31.177/xssgame/level1.php?name=<script>alert(1)</script> 过滤了>.& ...