前些天有 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.BaseDirectoryAppContext.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.GetCurrentDirectoryEnvironment.CurrentDirectory 方法输出均为 c:\APPS 而其它方法则都输出了 exe 所在目录。

使用另外一个 exe 程序启动测试程序

同样我们再次使用另外一个 WPF 程序来运行 basedir.exe 测试程序:

可以看到 Directory.GetCurrentDirectoryEnvironment.CurrentDirectory 方法输出均为 c:\APPS,也就是 WPF 所在的目录, 而其它方法则都输出了 exe 所在目录。

总结

以上常见的 5 种读取程序当前目录的办法在绝大多数情况下都可以正确的获取到预期的结果。其中需要注意的是Directory.GetCurrentDirectoryEnvironment.CurrentDirectory。这2个方法在 cmd 或者 bash 环境下返回的是工作目录;使用 A 程序启动另外一个 B 程序的时候,B 程序获取到的根目录是 A 程序所在的目录。所以使用 Directory.GetCurrentDirectoryEnvironment.CurrentDirectory 的时候一定要格外注意,避免引入 BUG 。

关注我的公众号一起玩转技术

.NET 程序读取当前目录避坑指南的更多相关文章

  1. 今天 1024,为了不 996,Lombok 用起来以及避坑指南

    Lombok简介.使用.工作原理.优缺点 Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中,Lombok 提供了一组有用的注解,用来消除 Java 类中的大量样板代码. 目录 L ...

  2. electron 编译 sqlite3避坑指南---尾部链接有已经编译成功的sqlite3

    electron 编译 sqlite3避坑指南(尾部链接有已经编译成功的sqlite3) sqlite很好用,不需要安装,使用electron开发桌面程序,sqlite自然是存储数据的不二之选,奈何编 ...

  3. CEF避坑指南(一)——下载并编译第一个示例

    CEF即Chromium Embedded Framework,Chrome浏览器嵌入式框架.它提供了接口供程序员们把Chrome放到自己的程序中.许多大型公司,如网易.腾讯都开始使用CEF进行前端开 ...

  4. Canal v1.1.4版本避坑指南

    前提 在忍耐了很久之后,忍不住爆发了,在掘金发了条沸点(下班时发的): 这是一个令人悲伤的故事,这条情感爆发的沸点好像被屏蔽了,另外小水渠(Canal意为水道.管道)上线一段时间,不出坑的时候风平浪静 ...

  5. Harmony OS 开发避坑指南——源码下载和编译

    Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...

  6. Linux下Python3.6的安装及避坑指南

    Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装Python3之前,需要将这些依赖库先行安装好. yum -y install zlib-dev ...

  7. Hive改表结构的两个坑|避坑指南

    Hive在大数据中可能是数据工程师使用的最多的组件,常见的数据仓库一般都是基于Hive搭建的,在使用Hive时候,遇到了两个奇怪的现象,今天给大家聊一下,以后遇到此类问题知道如何避坑! 坑一:改变字段 ...

  8. Android连接远程数据库的避坑指南

    Android连接远程数据库的避坑指南 今天用Android Studio连接数据库时候,写了个测试连接的按钮,然后连接的时候报错了,报错信息: 2021-09-07 22:45:20.433 705 ...

  9. Windows环境下Anaconda安装TensorFlow的避坑指南

    最近群里聊天时经常会提到DL的东西,也有群友在学习mxnet,但听说坑比较多.为了赶上潮流顺便避坑,我果断选择了TensorFlow,然而谁知一上来就掉坑里了…… 我根据网上的安装教程,默认安装了最新 ...

随机推荐

  1. 【论文阅读】CVPR2021: MP3: A Unified Model to Map, Perceive, Predict and Plan

    Sensor/组织: Uber Status: Reading Summary: 非常棒!端到端输出map中间态 一种建图 感知 预测 规划的通用框架 Type: CVPR Year: 2021 引用 ...

  2. VUE常见问题

    VUE常见问题 对于MVVM的理解 MVVM 是 Model-View-ViewModel 的缩写 Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑 View 代表UI 组件, ...

  3. Golang中常用的代码优化点

    Golang中常用的代码优化点 大家好,我是轩脉刃. 这篇想和大家聊一聊golang的常用代码写法.在golang中,如果大家不断在一线写代码,一定多多少少会有一些些代码的套路和经验.这些经验是代表你 ...

  4. SpringBoot+Vue+mysql 搭建(一)

    一.创建Spring boot maven 项目 Spring initializr 是Spring 官方提供的一个用来初始化一个Spring boot 项目的工具. 在idea中,直接 File-& ...

  5. spring源码-ioc容器周期

    Spring容器的refresh 创建刷新:   1-prepareRefresh刷新前的预处理: initPropertySources 初始化一些属性配置,原来是空的,子类自定义的属性设置方法 g ...

  6. 如果leader crash时,ISR为空怎么办?

    kafka在Broker端提供了一个配置参数:unclean.leader.election,这个参数有两个值:true(默认):允许不同步副本成为leader,由于不同步副本的消息较为滞后,此时成为 ...

  7. sleep()和wait()的区别?notify()和notifyAll()的区别?start()和run()的区别?

    sleep()和wait()的区别? 这两个方法来自不同的类分别是Thread和Object sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法.wait,not ...

  8. 在java web工程中jsp页面中使用kindeditor

    在这之前我们用Notepad++写过kindeditor 在Java web工程里也差不多 首先我们复制之前的thml代码粘贴到工程里 然后把样式也复制进去 然后就可以运行了

  9. 『忘了再学』Shell基础 — 6、Bash基本功能(输入输出重定向)

    目录 1.Bash的标准输入输出 2.输出重定向 (1)标准输出重定向 (2)标准错误输出重定向 (3)正确输出和错误输出同时保存 3.输入重定向 1.Bash的标准输入输出 我们前边一直在说,在Li ...

  10. Leetcode刷题之螺旋矩阵

    矩阵之螺旋矩阵 总体思路: 注意遍历顺序 每次遍历一圈时候不要多加元素 Leetcode54螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素. ...