關於 .NET 在不同作業系統中 IO 檔案路徑拼接方法結升級 .NET 7 後注意到的一個小坑

張曉棟發表於2022-11-24

.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 平臺還是 DOS 的時候,那個時候 / 在 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

相關文章