前些天有 AgileConfig 的用戶反映,如果把 AgileConfig 部署成 Windows 服務程式會啟動失敗。我看了一下日誌,發現根目錄被定位到了 C:\Windows\System32 下,那麼讀取 appsettings.json 配置文件自然就失敗了。 var builder = ...
前些天有 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
目錄下,然後運行它:
其中按鈕的事件代碼如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
var process = new Process();
process.StartInfo.FileName = this.path.Text;
process.Start();
}
點擊這個按鈕,它會把我們的測試程式 basedir.exe 給運行起來:
我們可以看到,當 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 測試程式:
可以看到 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 VX:kklldog 一起探討學習.NET技術
作者:Agile.Zhou(kklldog)
出處:http://www.cnblogs.com/kklldog/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。