天天看點

.NET 程式讀取目前目錄避坑指南

前些天有 AgileConfig 的使用者反映,如果把 AgileConfig 部署成 Windows 服務程式會啟動失敗。我看了一下日志,發現根目錄被定位到了 ​

​C:\Windows\System32​

​ 下,那麼讀取 appsettings.json 配置檔案自然就失敗了。

var builder = new ConfigurationBuilder()
                   .SetBasePath(Directory.GetCurrentDirectory());      

以上是我構造 ConfigurationBuilder 的代碼。使用 ​

​Directory.GetCurrentDirectory()​

​​ 擷取程式根目錄然後設定 ​

​SetBasePath​

​ 。以上代碼在99%的情況是不會有問題的,那麼為什麼會在做為服務部署的時候會有問題呢?讓我們往下看。

Directory.GetCurrentDirectory()

​Directory.GetCurrentDirectory()​

​ 擷取根目錄是我們很常見的一個操作。先讓我們對其進行一些簡單的測試。建立一個控制台程式,編寫以下代碼進行:

var dirpath = Directory.GetCurrentDirectory();
Console.WriteLine("Directory.GetCurrentDirectory = " + dirpath);      

直接運作

代碼很簡單,就是讀取根目錄,然後輸出一下。

Directory.GetCurrentDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0      

編譯完成後輕按兩下 exe 檔案,可以看到擷取到的目錄是正确的。

使用 cmd 運作

下面讓我們試一下在 cmd 下運作這個 exe 。

C:\>cd C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0>basedir.exe
Directory.GetCurrentDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0      

我們把路徑切換到 exe 所在的目錄,然後輸入 basedir.exe 直接運作它,可以看到輸出的目錄也是正确的。

切換工作目錄

這次我們把工作目錄切換到 C 盤的 apps 目錄,然後使用完全路徑去執行 exe 程式。

C:\>cd apps
C:\APPS>C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\basedir.exe
Directory.GetCurrentDirectory = C:\APPS      

怎麼樣?是不是跟預期的不一樣了?這次輸出的根目錄不是 exe 所在的目錄,而是 ​

​c:\APPS​

​ ,也就是我們的工作目錄。

使用另外一個 exe 程式啟動測試程式

在我們日常場景中有很多時候需要通過一個程式去運作另外一個程式,那麼這個時候 ​

​Directory.GetCurrentDirectory​

​ 擷取的根目錄是怎麼樣的呢?

首先我們編寫另外一個 WPF 的程式,使用這個程式來啟動我們的 basedir.exe 測試程式。

我們把這個 WPF 程式放在 ​

​c:\APPS​

​ 目錄下,然後運作它:

.NET 程式讀取目前目錄避坑指南

其中按鈕的事件代碼如下:

private void Button_Click(object sender, RoutedEventArgs e)
        {
            var process = new Process();
            process.StartInfo.FileName = this.path.Text;
            process.Start();
        }      

點選這個按鈕,它會把我們的測試程式 basedir.exe 給運作起來:

.NET 程式讀取目前目錄避坑指南

我們可以看到,當 WPF 程式把我們的測試程式運作起來的時候,測試程式輸出的目錄為 ​

​c:\APPS​

​,也就是 WPF 程式所在的目錄。

為什麼做為服務運作的時候擷取程式根目錄為 System32

通過以上的測試,AgileConfig 做為服務運作的時候擷取根目錄為 ​

​C:\Windows\System32​

​​ 的原因已經很明顯了。我們的 windows 服務的啟動一般來說有2個途徑,一個是通過 sc.exe 工具另外一個是通過 services.exe 也就是 SCM (service control manager) 來啟動。那麼這2個可執行程式在哪裡呢?答案就是 ​

​C:\Windows\System32​

​​ 。我們的 windows 服務的啟動其實是通過這2個工具來運作的,是以根據上面的測試,很明顯通過​

​Directory.GetCurrentDirectory​

​來擷取根目錄的話會是這2個工具所在的目錄。

其它讀取程式根目錄的方式

通過以上我們知道通過​

​Directory.GetCurrentDirectory​

​讀取根目錄會有一點小坑。在我們的 .NET 世界裡還有很多辦法能擷取根目錄,那麼他們會不會也有坑呢?

以下列幾個常見的擷取根目錄的方法:

var dirpath = Directory.GetCurrentDirectory();
Console.WriteLine("Directory.GetCurrentDirectory = " + dirpath);

// 通過 AppDomain.CurrentDomain.BaseDirectory 讀取根目錄
var dirpath1 = AppDomain.CurrentDomain.BaseDirectory;
Console.WriteLine("AppDomain.CurrentDomain.BaseDirectory = " + dirpath1);

// 通過 Environment.CurrentDirectory 來讀取根目錄
var dirpath2 = Environment.CurrentDirectory;
Console.WriteLine("Environment.CurrentDirectory = " + dirpath2);

// 通過 Assembly.GetExecutingAssembly().Location 來擷取運作程式集所在的位置,進而判斷根目錄
var dirpath3 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Console.WriteLine("Path.GetDirectoryName Assembly.GetExecutingAssembly().Location = " + dirpath3);

// 通過 AppContext.BaseDirectory 擷取根目錄
var dirpath4 = AppContext.BaseDirectory;
Console.WriteLine("AppContext.BaseDirectory = " + dirpath4);      

直接運作

把以上擷取根目錄的代碼補充進我們的測試程式,編譯成功後直接運作:

Directory.GetCurrentDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
AppDomain.CurrentDomain.BaseDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\
Environment.CurrentDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
Path.GetDirectoryName Assembly.GetExecutingAssembly().Location = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
AppContext.BaseDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\      

以上是輸出結果。可以看到所有的方法都準确的擷取到了 exe 程式所在的根目錄。有一點要注意的是

​AppDomain.CurrentDomain.BaseDirectory​

​ 跟 ​

​AppContext.BaseDirectory​

​ 方法擷取的路徑最後帶有一個 ​

​\​

​ 其它則沒有。

使用 cmd 運作

同樣讓我們在 cmd 下運作一下:

C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0>basedir.exe
Directory.GetCurrentDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
AppDomain.CurrentDomain.BaseDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\
Environment.CurrentDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
Path.GetDirectoryName Assembly.GetExecutingAssembly().Location = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
AppContext.BaseDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\      

我們把路徑切換到 exe 所在的目錄,然後輸入 basedir.exe 直接運作它,可以看到所有的方法輸出的目錄都是正确的。

切換工作目錄

同樣我們在 cmd 下把工作目錄切換到 ​

​c:\APPS​

​ ,然後運作 exe 。

C:\>cd APPS

C:\APPS>C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\basedir.exe
Directory.GetCurrentDirectory = C:\APPS
AppDomain.CurrentDomain.BaseDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\
Environment.CurrentDirectory = C:\APPS
Path.GetDirectoryName Assembly.GetExecutingAssembly().Location = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0
AppContext.BaseDirectory = C:\00WORKSPACE\basedir\basedir\bin\Debug\net6.0\      

可以看到 ​

​Directory.GetCurrentDirectory​

​​ 和 ​

​Environment.CurrentDirectory​

​​ 方法輸出均為 ​

​c:\APPS​

​ 而其它方法則都輸出了 exe 所在目錄。

使用另外一個 exe 程式啟動測試程式

同樣我們再次使用另外一個 WPF 程式來運作 basedir.exe 測試程式:

.NET 程式讀取目前目錄避坑指南
.NET 程式讀取目前目錄避坑指南

可以看到 ​

​Directory.GetCurrentDirectory​

​​ 和 ​

​Environment.CurrentDirectory​

​​ 方法輸出均為 ​

​c:\APPS​

​,也就是 WPF 所在的目錄, 而其它方法則都輸出了 exe 所在目錄。

總結

以上常見的 5 種讀取程式目前目錄的辦法在絕大多數情況下都可以正确的擷取到預期的結果。其中需要注意的是​

​Directory.GetCurrentDirectory​

​​ 和 ​

​Environment.CurrentDirectory​

​​。這2個方法在 cmd 或者 bash 環境下傳回的是工作目錄;使用 A 程式啟動另外一個 B 程式的時候,B 程式擷取到的根目錄是 A 程式所在的目錄。是以使用 ​

​Directory.GetCurrentDirectory​

​​ 和 ​

​Environment.CurrentDirectory​

​ 的時候一定要格外注意,避免引入 BUG 。

關注我的公衆号一起玩轉技術

QQ群:1022985150 

作者:Agile.Zhou(kklldog)