.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. Logstash:为 Logstash 日志启动索引生命周期管理

    文章转载自:https://elasticstack.blog.csdn.net/article/details/110816948

  2. 使用coverlet统计单元测试的代码覆盖率

    单元测试是个好东西, 可以在一定程度上兜底 虽然写单元测试这件事情非常麻烦 但是好的单元测试可以显著提高代码质量, 减少bug, 避免无意中的修改导致其他模块出错 写测试用例的过程中, 靠人力去确保所 ...

  3. Mysql三种日志(binlog,redolog,undolog)的作用和区别

    Mysql有三种很重要的日志也是面试经常涉及到的考点,分别是 binlog .redo log和undo log, 这里面binlog 是server层实现的日志,而redo log 和undo lo ...

  4. localStorage概要

    在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localSt ...

  5. 跟我学Python图像处理丨傅里叶变换之高通滤波和低通滤波

    摘要:本文讲解基于傅里叶变换的高通滤波和低通滤波. 本文分享自华为云社区<[Python图像处理] 二十三.傅里叶变换之高通滤波和低通滤波>,作者:eastmount . 一.高通滤波 傅 ...

  6. 我的Vue之旅、05 导航栏、登录、注册 (Mobile)

    第一期 · 使用 Vue 3.1 + TypeScript + Router + Tailwind.css 构建手机底部导航栏.仿B站的登录.注册页面. 代码仓库 alicepolice/Vue-05 ...

  7. P2680 [NOIP2015 提高组] 运输计划 (树上差分-边差分)

    P2680 题目的大意就是走完m条路径所需要的最短时间(边权是时间), 其中我们可以把一条边的权值变成0(也就是题目所说的虫洞). 可以考虑二分答案x,找到一条边,使得所有大于x的路径都经过这条边(差 ...

  8. POJ1985 Cow Marathon (树的直径)

    用两次dfs求出树的直径,这两次dfs可以写在一起,当然为了方便理解,这里是分开写的. 1 //两次dfs求树的重心 2 #include<cstdio> 3 #include<cs ...

  9. struts.xml 中用OGNL表达式取不到中文文件名的原因

    在struts2中xml配置如下,以execl文件为例: <result name="success" type="stream">    < ...

  10. Python生成10个八位随机密码

    #生成10个八位随机密码 import random lst1=[ chr(i) for i in range(97,123) ] #生成26为字母列表 lst2=[i for i in range( ...