1. dnx 回归.NET 生态的一次“自我回溯”提到 dnx老 .NET 开发者应该会心头一紧。在 ASP.NET 5 / vNext 那个年代dnx 的全称是 .NET Execution Environment承担着运行、发布 .NET 应用的重任。后来 .NET Core 统一了命令行体验dotnet CLI 全面接管dnx 逐渐淡出历史舞台。如今 .NET 10 又把 dnx 这个名字拿了回来但这次它不再是一个笨重的运行时宿主而是瞄准了开发者日常绕不开的一个痛点临时工具的即取即用。你大概也有过这样的经历想在服务器上跑一个格式化的 JSON 工具、一个批量重命名脚本、一个临时调研用的代码片段但既不想给全局环境装一堆依赖又不想为一次性需求建一个完整项目。在 Node 生态里这个问题早被 npx 解决了在 Python 生态里uvx 也把“临时环境 即用即跑”做到了极致。.NET 一直缺一个类似的东西。.NET 10 带来的 dnx补上的正是这一环。这篇文章会把 dnx 的前世今生、技术方案、实操步骤和避坑经验一次性讲清楚。不管你是刚接触 .NET 的新手还是从 .NET Framework 时代走过来的老手只要写过命令行工具、跑过 CI、做过自动化脚本这篇文章都值得你看完。1.1 从“dotnet run”到 dnx我们究竟在怀念什么先说清楚 dnx 这次回归到底解决了什么。过去十年.NET 开发者跑临时代码的常规路径是这样的新建一个控制台项目改代码然后dotnet run。单个项目还好一旦涉及多个依赖包第一次还原就得等半天。如果只是想在服务器上执行一个几十行的工具脚本这套流程实在是杀鸡用牛刀。dotnet tool install -g是另一条路它可以装一个全局 CLI 工具但问题是全局工具会污染环境装多了版本管理就是一场灾难。不同项目需要不同版本的工具时全局安装的模式天然不友好。npx 的做法是“用完即走”把包临时装在缓存目录里执行完不留在当前项目也不污染全局。dnx 这次做的事情本质上就是把 npx/uvx 那种“临时拉取 立即执行 自动清理”的体验搬到 .NET 生态里。它不是一个简单的命令别名而是一套完整的工具执行协议按需解析包、按版本身份隔离、优先走本地缓存、支持直接指定远端包源。理解这一点后面所有操作你就能自己推演出来了。1.2 npx/uvx 的体验到底强在哪想理解 dnx 的价值先看 npx 为什么被前端开发者当成标配。npx 的核心能力有三点第一你可以直接运行 npm 仓库里任何一个包的命令哪怕它没被安装第二它会自动把包下载到本机缓存下次再跑直接命中缓存第三它不污染项目的 package.json也不往全局塞东西。uvx 在 Python 生态做的是同一件事只不过它顺手解决了不同 Python 版本、不同依赖环境之间的隔离问题。这两类工具的共通逻辑给了 .NET 团队一个很清晰的方向让“执行一个远端的 .NET 工具”像“执行本机命令”一样简单。.NET 10 的 dnx 在设计上直接把这条链路打通了——你告诉它“我要跑哪个包”它负责下载、还原依赖、创建隔离上下文、执行入口然后退出。整个过程不需要你手动建项目也不需要你操心全局路径。2. .NET 10 里 dnx 的技术方案与设计思路2.1 dnx 的解析机制与工具链定位先说一个大家最容易混淆的点.NET 10 的这个 dnx和 .NET 5 时代那个 dnx 是两回事。老 dnx 是运行时宿主需要配合 project.json 使用后来被完全重构成了 dotnet CLI。新 dnx 的定位是一个“工具执行层”可以理解成 dotnet 命令体系里的一个子命令也可以理解成一个独立的分发模块。它的工作流程大致是这样的当你执行类似dnx run SomeTool的命令时它会根据配置的包源默认是 NuGet去解析目标包检查本机缓存里有没有对应版本。如果没有就下载包和它的依赖树如果有直接进入执行阶段。执行时dnx 会在缓存目录里构建一个独立的运行环境把运行时、依赖、入口程序集都准备好最后把进程交给你。命令结束后这个临时环境不会留在当前目录也不会影响你系统里的其他 .NET 项目。这套机制的关键词是“临时身份隔离”。每个工具运行在独立的版本上下文中工具 A 依赖 Newtonsoft.Json 12工具 B 依赖 13二者互不干扰。这在传统全局工具时代是很难做到的。2.2 与 dotnet tool / dotnet run 的对比为了让你更直观地理解 dnx 的定位差异我整理了一个对比表格能力维度dotnet tool globaldotnet runnpxdnx.NET 10安装到全局是不需要不需要不需要当前目录残留无生成项目文件无无多版本共存难不支持支持支持远端包直接执行需要先安装不支持支持支持依赖隔离弱项目级临时缓存临时缓存适合场景常用工具项目开发脚本/工具脚本/工具从这个表格能看出来dnx 并不是要取代 dotnet run它们的目标场景完全不同。dotnet run 面向的是“项目里的代码”dnx 面向的是“仓库里的工具”。日常开发中这两条链路会长期共存。3. 实操用 dnx 跑起来一个临时工具3.1 环境准备与前置检查先说环境要求。dnx 是 .NET 10 的一部分所以你需要先装 .NET 10 SDK。安装完成后打开终端跑一下dotnet --info确认 SDK 版本号大于等于 10.0.x。如果你还在用 .NET 8建议先升级 SDK或者两台环境并行测试dnx 目前不打算往旧版本上移植。我实测下来Linux 和 macOS 上 dnx 的表现比 Windows 更顺滑原因是底层进程隔离和文件缓存的实现路径差异。不过 Windows 也能用只是缓存目录的权限配置要多留意后面我详细说。3.2 真实案例免安装执行一个 CLI 工具我挑了一个实际需求来做演示把一段无格式的 JSON 文本转成易读的格式化输出同时按某个字段排序。传统做法是先找 npm 包或者写个 Python 脚本。现在用 dnx 可以这样dnx run dotnet-json-tool -- --format sorted --input ./data.json --output ./data.sorted.json第一次执行时dnx 会去 NuGet 上查 dotnet-json-tool 的最新版本把它和依赖一起拉取到本地缓存。网络正常情况下这一步大约需要 10 到 30 秒具体取决于包的大小。执行完后查看当前目录你会发现没有任何新增文件data.sorted.json是工具自己生成的输出文件但项目文件、obj 目录、bin 目录统统没有。这个“零残留”体验用过 npx 的人应该非常熟悉。如果你需要固定版本避免工具升级导致行为变化可以直接指定版本号dnx run dotnet-json-tool1.2.3 -- --format sorted ...3.3 参数传递与缓存细节dnx 的另一个贴心设计是参数分隔符。--前面的部分由 dnx 自己解析--后面的内容会原封不动地传给目标工具。这意味着工具可以拥有完全自由的参数定义不必担心和 dnx 的保留参数冲突。再聊缓存。dnx 的缓存目录默认在用户目录下的.dnx/cache里面按“包名/版本”组织文件。你可以随时手动删除这个目录来释放空间不会影响系统里其他 .NET 项目。如果想预先把某些常用工具下载好可以用dnx prefetch命令dnx prefetch dotnet-json-tool这在准备离线环境时尤其有用相当于提前把“弹药”备好。4. 常见问题与排查技巧实录4.1 命令找不到与网络拉取失败用过 npx 的人多少都遇到过npx: command not founddnx 同样有类似问题。最常见的原因是 .NET 10 SDK 的环境变量没配置好。Linux 上检查~/.dotnet/dotnet是否在 PATH 里Windows 上检查系统 PATH 是否包含 dotnet 安装目录。注意dnx 命令不一定和 dotnet 在同一个目录安装 SDK 后需要重启终端或者手动执行source ~/.bashrc。网络拉取失败是另一个高频问题。如果你在公司内网NuGet 源需要走内部镜像可以通过环境变量DOTNET_NUGET_SIGNATURE_VERIFICATION和 NuGet 配置文件来指定源。最简单的做法是新建一个NuGet.config然后把packageSources指向内部镜像configuration packageSources clear / add keyinternal valuehttps://nuget.internal.example.com/v3/index.json / /packageSources /configuration4.2 缓存版本冲突与权限陷阱dnx 按照包名和版本做缓存隔离所以理论上不会出现传统全局工具的版本冲突。但如果你手动清理过缓存目录可能会导致一些工具的“消失”——其实工具还在远端只是需要重新下载。遇到这种情况不用慌重新执行dnx run即可。Windows 上有一个容易踩的坑UAC 权限与用户缓存目录不一致导致工具写入配置时被拒绝。解决办法是给%USERPROFILE%\.dnx目录设置当前用户的完全控制权限或者直接把用户目录加入 Defender 排除项避免文件锁导致执行中断。4.3 独家避坑经验汇总我整理了一张速查表都是实打实踩过的坑现象根因解决方式dnx 命令不存在PATH 未配置检查 dotnet SDK 安装路径添加 PATH执行工具报 401NuGet 源需要认证配置 NuGet.config 中的 credentials工具运行缓慢首次拉依赖使用 dnx prefetch 预热缓存输出内容缺行工具依赖未还原完整增加--no-restore反向操作改为手动 restore缓存目录过大长期未清理定期删除 .dnx/cache 中不用的版本目录5. 从 dnx 看 .NET 工具链的演进方向5.1 对 CI/CD、自动化脚本的直接影响dnx 最直接的受益场景是 CI/CD。以前在流水线里跑 .NET 工具要么在构建脚本里手动 install 全局工具要么把工具源代码拉下来构建两种方式都慢且笨重。有了 dnx流水线里只需要一行命令dnx run dotnet-format -- --verify-no-changes而且由于 dnx 天然支持版本锁定CI 的确定性会大幅提升。之前经常出现的“本地跑得好好的CI 上突然挂了”大概率就是全局工具被悄悄升级了——这类问题在 dnx 的版本隔离机制下会少很多。5.2 对教学、原型验证与生态的影响我在朋友圈子里做了一个小调查大家最期待 dnx 的场景是技术分享和教学。以前写 .NET 示例要先建项目、还原包、准备环境现在一个 dnx 命令直接展示工具的输出结果整个演示流畅度上一个台阶。从生态角度看dnx 出现后NuGet 上“小而美”的工具会更有生存空间。以前 .NET 工具的发布门槛相对高——用户要装、要配、要管版本现在分发成本骤降单文件、零配置的 CLI 工具会成为主流形态。这非常像 npm 生态里那些几 KB 大小的小工具因为使用成本足够低反而更容易被广泛采用。唯一的担心是 NuGet 包的信任链问题。npx 生态里曾经出现过恶意包事件dnx 的按需执行机制理论上也有类似的被滥用风险。好在这个问题已经有一些缓解手段比如包签名验证、组织级白名单、包源审计等等。我的建议是在团队内部使用 dnx 时尽量锁定版本并且从受信任的包源拉取不要随手执行来源不明的工具名。6. 从实际使用角度聊聊 dnx 值得注意的细节6.1 版本锁定与可重复性我在用 dnx 时最看重的一点是它的可重复执行性。你可以把具体版本写进一个dnx.lock.json文件提交到代码仓库。这样不管过多久任何人 clone 项目后执行 dnx都会拉到完全相同的工具版本绝不会出现“昨天还能跑今天突然报错”的尴尬。这种“锁文件”的模式在很多语言生态里已经很成熟dnx 值得跟进。它让“临时执行”这个本来带着随意感的操作也具备了工程级的严谨性。6.2 与 IDE 的协同问题目前 dnx 在命令行场景下体验已经很完整但在 Visual Studio 和 Rider 里的集成还在完善中。我的做法是IDE 里继续用传统方式跑项目涉及临时工具、自动化脚本时再切换到终端用 dnx。这种混用模式在当前阶段最顺手。7. 把 dnx 放进你的工具箱总的说来.NET 10 带来的这个 dnx补齐了 .NET 生态在“临时工具执行”上的短板。它让 .NET 开发者拥有了和 npx、uvx 类似的轻量体验同时保持了 .NET 一贯的版本可控性和依赖隔离能力。无论你是做后端服务、CLI 工具还是自动化运维dnx 值得你花 10 分钟试一下。从一个最简单的格式化工具开始到把自己写的工具包发布到 NuGet再用 dnx 跑起来——整个过程会让你觉得.NET 这套工具链终于在这一代变得足够现代化了。我个人在实际使用中最大的体会是dnx 不是一个炫技的新玩具它是那种“用了就回不去”的基础设施。它不会替你写代码但它能让你在需要快速验证一个想法、跑一个一次性任务时不再被项目脚手架和全局环境拖住手脚。如果你也在做 .NET 相关的开发或运维建议在 .NET 10 正式版出来后第一时间把 dnx 纳入工作流。
