尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

dnx 来了:.NET 10 内置 npx 式临时工具运行器

dnx 来了:.NET 10 内置 npx 式临时工具运行器 如果你做过几年 .NET 开发大概率经历过这种场景想临时跑一个工具先得dotnet tool install -g装完还怕跟全局已有的版本冲突折腾半天终于跑通了换台机器又得重新来一遍。Node 那边有 npxPython 那边有 uvx一条命令直接拉起来用完即走.NET 生态一直缺这么个趁手的东西。这次 .NET 10 把 dnx 带回来了算是把这块短板补上了。这篇东西我不打算写成官方文档就按我自己拿到预览版之后实际折腾的思路聊聊 dnx 到底解决了什么问题、怎么用、以及中途踩过的那些坑。1. 全局安装工具的痛点以及 npx/uvx 给出的答案1.1 从“装到系统里”到“用的时候再拉”先说说 npx 和 uvx 为什么能火。以前我们要用某个 Node 包标准流程是npm install -g把包装进全局目录然后所有项目都能直接调用。这套模式的问题在于全局空间像一间堆满杂物的仓库装得越多越乱。你为了一个脚本命令装了个全局包这个包又带了十几个依赖时间一长根本记不清机器上到底装了什么。更麻烦的是版本两个项目一个要 A 版本一个要 B 版本全局只能装一个来回切换配置能让人抓狂。npx 的思路是完全换了个玩法我不管你本机有没有这个包反正我执行npx 包名的时候去 registry 里找到它然后临时拉下来跑。如果本机已经有缓存直接用缓存如果没有拉完跑完就扔不写进任何全局目录。Python 那边的 uvx 也是同样逻辑只是把 registry 换成了 PyPI把包管理器从 npm 换成了 uv。这种“临时拉取”的模式解决了一个很本质的问题工具的运行时状态跟项目的运行时状态彻底解耦。你写项目的时候依赖 lock 文件来固定版本工具凭什么不能这样npx 和 uvx 让你可以为某个目录、甚至某一条命令临时指定版本用完不残留环境永远是干净的。1.2 .NET 生态的尴尬dotnet tool 为什么一直差点意思其实 .NET 不是没有工具机制dotnet tool install已经存在好几个大版本了。但它的问题在于设计哲学还停留在“全局安装”的时代。哪怕你计划只在一个项目里用某个工具官方推荐的做法也是先全局装好然后通过dotnet tool restore在项目维度上做管理。这就带来两个实际体验上的落差第一第一次使用必须先安装再运行两步走第二全局安装的入口一旦多了环境变量 PATH 就很容易出问题我在 Windows 上就遇到过dotnet tool install -g装完以后命令找不到的情况最后发现是工具目录没进 PATH这种问题排查起来非常恶心。所以社区里一直有人拿 npx 来类比期望.NET 能不能也搞一个“一条命令临时跑 NuGet 包”的东西dnx 在 .NET 10 里回归就是冲着这个需求来的。2. dnx 的定位与设计逻辑2.1 dnx 是什么不是老 DNX 的复活如果你是一个老 ASP.NET 5 时期的开发者听到 DNX 可能会一愣——那个不是早没了吗对.NET Core 早期确实有个 DNX.NET Execution Environment当年是拿来跑跨平台应用的一层运行时。后来 .NET CLI 成熟之后那套东西就被并掉了。现在 .NET 10 的 dnx虽然缩写一样但定位完全不同它的全称更接近“.NET Execute / Tool Runner”是一个专门用来“临时获取并运行 NuGet 工具”的命令工具。你可以先有一个朴素的认知dnx就是 .NET 版的npx。它不再是某个运行时框架的名字而是一个薄薄的命令行入口负责两件事——解析你要运行的工具名以及把对应的 NuGet 包临时拉取到本机缓存里执行。这个设计思路和 npx 几乎一致不需要手动dotnet tool install运行结束后工具不会常驻在任何项目或全局工具清单里多版本可以共存按命令级别来动态选择。2.2 它和 dotnet tool 的关系互补而非取代这里我想先打消一个顾虑dnx 不是来消灭dotnet tool的。恰恰相反两者面向的场景不同对比维度dotnet tooldnx安装方式显式 install 到全局或工具清单临时拉取自动缓存版本管理手动指定更新需要显式 upgrade可在命令里直接指定版本使用场景长期使用的开发辅助工具偶尔执行、验证、脚本化调用环境侵入会写入全局工具目录或本地 manifest不写入项目只进缓存目录适合人群每天都要用的核心工具临时想跑一下或 CI 里快速调用打个比方dotnet tool像是在家里固定安装一台咖啡机你每天都要用放在台面上方便。而 dnx 更像你去朋友家做客时临时要冲一杯挂耳咖啡用完纸袋一扔不占用任何厨柜空间。两者并不冲突反而配合起来很舒服——常用工具用dotnet tool固定冷门工具、一次性的脚本调用全部交给 dnx。2.3 为什么说它开启了“npx/uvx 时代”这个词不是夸张。过去你要在 .NET 生态里找一个工具第一反应是去 NuGet 搜索然后手动安装中间隔着“搜索、了解安装方式、全局安装、补 PATH、验证”五个步骤。dnx 直接把这个链条压缩成一步知道工具名就能跑。这带来的连锁反应是工具的分享和传播开始变得像命令一样轻量一个工具的“安装成本”趋近于零。这跟 npx 第一次普及时候的状态非常像——为什么大家都喜欢用npx create-react-app而不喜欢全局装脚手架因为省心、干净、不会污染环境。dnx 在 .NET 生态里扮演的正是这个角色尤其是配合 .NET 10 对本地 AOT 发布、以及对单文件工具的支持跑一个工具的成本已经低到可以忽略不计。3. dnx 的安装和核心用法3.1 前置条件版本和运行时先泼一盆冷水dnx 不是一个独立的安装包它是 .NET 10 SDK 自带的能力所以前置条件非常直接——安装 .NET 10 SDK。我在 Windows 和 Ubuntu 上都做了验证安装完之后直接执行dnx --version如果你能看到版本号说明已经就绪了。注意这里不需要单独“开启某个功能开关”SDK 默认就带。在 Windows 上要特别检查一下新版 SDK 安装完之后 PATH 里是否已经有dnx的入口有些场景下旧版本的 SDK 配置可能会覆盖新的 PATH 写入导致命令找不到。我需要强调一点dnx 本身依赖 .NET 10 运行时所以这台机器上至少要能跑dotnet --version得到 10.x。如果你同时在用 Visual Studio尽量把 VS 升级到支持 .NET 10 的版本否则 IDE 里的外部工具调用可能解析不到 dnx。3.2 一次运行最核心的 dnx 命令假设我想临时跑一个叫dotnet-format的代码格式化工具传统做法是dotnet tool install -g dotnet-format dotnet-format --version用 dnx 的话直接dnx dotnet-format --version第一次执行时dnx 会去 NuGet 上解析最新的稳定版下载到本地缓存然后运行。之后你再执行同样的命令它检测到已有缓存直接复用速度会快非常多。这就是 npx 用户非常熟悉的那套“第一次慢一点后面飞起”的体验。如果你想临时用某个特定版本可以这样dnx dotnet-format8.0.0 --version版本号直接跟在包名后面分隔。这个设计跟 npx 的version后缀几乎一模一样好处是在同一个目录里你可以一键切换不同版本的工具来对比输出结果互不干扰。3.3 缓存与清理别让临时文件悄悄霸占磁盘一边强调“用完即走很干净”一边也得诚实面对缓存机制。dnx 的临时包会缓存在用户目录下Windows 上通常是在%LOCALAPPDATA%\dnx\cacheLinux/macOS 是在~/.cache/dnx一类的位置长时间使用下来缓存目录会越来越大。好在它跟 NuGet 包缓存是两套体系清理起来也比较直接把整个缓存目录删掉下次需要时会重新拉取不影响任何项目。如果你对磁盘空间比较敏感可以考虑定期执行清理或者让 CI 环境里的 dnx 跑完之后自动删除缓存目录。我在本地一般不会主动清理毕竟缓存能提升二次执行速度但确实见过同事拿着几 GB 的缓存目录来找我帮忙排查磁盘占用所以这块还是建议大家心里有数。3.4 项目级配置在 manifest 中固定 dnx 依赖dnx 的长期价值和 npx 一样在于它可以配合项目的 manifest 文件来固定工具版本。做法是在项目根目录维护一个工具配置文件里面声明你需要哪些 dnx 工具和版本团队成员拉完代码之后直接用 dnx 一条命令就能把环境拉到一致状态。这比每个人手动装全局工具靠谱太多——很多人换了机器或者新入职第一步永远是在为环境不一致而调来调去。以我自己的项目为例我习惯在仓库里放一个dnx.json内容大致是{ tools: { dotnet-format: 8.0.0, dotnet-ef: 9.0.0 } }然后在 README 里写清楚跑新代码之前执行一次dnx restore它会根据 manifest 里声明的版本把对应工具拉取好后续在项目目录里用哪些工具、什么版本全团队保持一致。这个思路本质上是把“工具依赖”也纳入版本管理跟前端package.json里声明依赖再npm install的路数完全一致。4. 实操过程与核心场景复现4.1 从零跑通一个格式化工具我先拿一个最常见的场景完整演示一遍。假设我在写一个 .NET 库项目代码风格想统一但不想让团队每个人手动装dotnet-format也不想把它写进全局目录。第一步进入项目目录执行dnx dotnet-format --verify-no-changes我这里特意加了--verify-no-changes是让工具只校验格式而不真正改动文件。第一次执行时dnx 会显示正在获取dotnet-format包以及依赖项然后跑出结果。你会注意到这一步之后项目的obj目录里不会出现工具的痕迹全局工具清单里也不会新增任何条目干干净净。第二步如果你想真正格式化把参数去掉dnx dotnet-format它会自动扫描当前目录下的解决方案文件格式化所有源码文件。改动完成后我用git status能看到具体哪些文件被动了完全可控。这套流程放在以前至少要两步先dotnet tool install -g dotnet-format再等它下载安装然后在输出的路径里去找命令。而且不止于此被装在全局的工具会常驻在 PATH 中如果哪天你不再需要它还得记得dotnet tool uninstall。dnx 跑完就扔不装了也不用卸载体验上完全是另一种东西。4.2 在 CI 脚本里用 dnx 跑迁移工具另一个我实际用得很顺的场景是 CI 里的数据库迁移。过去在 CI 里跑 EF Core 迁移要在脚本里先dotnet tool install -g dotnet-ef再dotnet ef database update。这里有两个烦人点一是全局安装需要额外的网络请求和失败重试逻辑二是安装版本如果不固定今天跑是 8.0明天可能因 NuGet 上最新版变化而踩坑。换成 dnx 之后CI 脚本变得非常简洁dnx dotnet-ef9.0.0 database update --connection $CONN_STR版本直接锚定在命令里第一次拉取缓存后后续步骤读取缓存速度稳定。就算 CI 环境每次都是全新的也就只有一次下载成本相比传统的跑到一半发现工具版本不对这种方式的确定性高得多。我特别建议在 CI 里把 dnx 缓存目录挂到持久化路径上比如自建 Runner 的/opt/dnx-cache这样同一台机器上多次构建可以共享缓存进一步减少网络开销。这个思路和很多人用npm cache加速 CI 是一个道理。4.3 移动端、IoT、脚本场景的临时体验dnx 还有一个容易被忽略但很有价值的使用场景你在一个只有 .NET 运行时、没有完整开发环境的机器上比如 IoT 设备、临时容器想快速跑一个工具做诊断。传统方式会卡在“装 SDK、配 PATH、装工具”这一串步骤上而 dnx 只需要在具备 .NET 10 运行时的环境里直接拉取工具执行非常契合快速排查、临时脚本这类需求。举个例子我在一台跑着 .NET 10 服务的容器里想快速看某个程序的依赖信息用 dnx 临时跑一个解析依赖的工具几秒钟就出结果容器退出之后什么都不留下。这在以前的 .NET 版本里是没法想象的通常你得把工具提前打进镜像为此还得专门维护一段 Dockerfile。5. 常见问题与排查技巧实录5.1 命令找不到PATH 与环境解析问题很多人在第一次用 npx 时遇到的那句经典报错——npx : 无法将“npx”项识别为 cmdlet、函数、脚本文件或可运行程序的名称——本质上不是 npx 的问题而是 Node 安装之后 PATH 没生效或者安装没装全。dnx 也可能会踩到同款坑。我的排查顺序是这样的先用where.exe dnxWindows或which dnxLinux/macOS确认命令是否真的在 PATH 里。如果找不到进入 .NET SDK 的安装目录手动确认dnx是否存在。Windows 下通常 SDK 根目录里有个dnx.exe它与dotnet.exe同目录如果只有dotnet.exe而缺少dnx.exe多半是 SDK 版本不对需要重新安装完整的 .NET 10 SDK。另一种情况是在 Windows 上用 PowerShell 执行.cmd后缀的命令可能遇到执行策略限制。解决方案不是关闭整个系统的脚本策略而是针对当前用户开放受限的执行策略或者用dnx.cmd的完整路径来调用。5.2 版本解析不一致为什么拉到的不是想象中那个版本dnx 默认会解析 NuGet 上的最新稳定版但如果你本地缓存里已经有了旧版本它可能不会立刻去获取最新版。这个行为跟 npx 类似都是“有缓存用缓存”。我遇到过的问题是这样的朋友在 NuGet 上发布了一个新版本我这边执行 dnx 命令时用的还是几个礼拜前的缓存版本一度以为发布失败了。排查方法很简单给命令加一个--refresh或者手动清理缓存目录强制重新解析版本。平时为了稳定起见我更推荐在命令里直接指定版本号这样既不会被缓存误导也能保证 CI 环境的一致性。5.3 代理与内网环境NuGet 源配置在一台只能访问内网 NuGet 源的环境里使用 dnx跟用dotnet restore是一样的逻辑需要配置 NuGet 源地址。如果没配好dnx 会一直停在“无法连接远程服务器”的报错上看起来就像网络不通。我的配置经验是在NuGet.config里同时配置内网源和官方源并且把内网源放在前面这样 dnx 会优先从内网拉取工具包。如果你的内网源是类似 Artifactory 或者本地 NuGet Server需要额外确认 API 版本兼容性新版 NuGet 协议和老的 NuGet.Server 之间偶尔会有格式不兼容的情况。这里也可以说一个务实的经验不要只在官方文档里找答案多看看本机已有的NuGet.config继承链。dnx 用的源配置和dotnet restore是同一条链路你在项目目录下新建一个NuGet.config把源配好dnx 就会自动遵循。5.4 常见问题速查表症状可能原因处理方式dnx 命令找不到SDK 不是 .NET 10或 PATH 未包含 SDK 目录重新安装 .NET 10 SDK 并检查 PATH执行脚本被拦截PowerShell 执行策略限制针对当前用户设置允许本地脚本执行拉包失败网络受限或 NuGet 源不可达配置内网源检查代理设置使用的版本跟预期不一致缓存了旧版本清缓存或显式指定版本号在非 SDK 环境无法运行只装了运行时没装 SDK安装包含 dnx 的完整 SDK和旧版 dotnet tool 命令重名冲突全局工具里有同名命令用 dnx 完整命令路径或卸载旧全局工具排查这类问题我个人的心得是先别急着怀疑工具本身先判断它到底走到哪一步。dnx 无非就是“解析包名、拉取包、执行工具”三段哪一段出问题报错信息位置大致能看出来。拉取阶段的错误多半是网络和源配置执行阶段的错误多半是运行时不匹配或参数问题。把阶段分清楚排查效率能高不少。6. 我对 dnx 的一些具体体会从拿到 .NET 10 预览版开始我把 dnx 用在了日常开发、脚本编写、CI 流程、容器诊断好几个场景里最大的感受是“存在感很低”。它不是那种会让你频繁感知到的功能但当你习惯了直接在命令行敲dnx 工具名版本之后再让你回到“先全局安装再跑”的节奏你会觉得非常别扭。我最喜欢的一点是它把工具的使用门槛降到了极低。以前我给同事推荐一个 .NET 工具得附带一段安装说明现在直接告诉他“执行dnx 包名就行第一次会自动拉取”大家都觉得这个事情变得轻松很多。当然它也有一些需要适应的地方。比如本地缓存的清理策略目前比较原始磁盘空间偏紧的人需要主动管理还有 NuGet 包作为工具分发渠道对包体积和入口程序集结构有要求并不是所有包都能直接拿来做 dnx 运行。但这些都是生态逐步完善的过程方向对了细节可以慢慢磨。往后我大概率会在所有新项目里统一使用 dnx 来管理少量辅助工具把dotnet tool留给那些真正需要全局常驻的重型工具。两套配合着用环境干净、版本可控这大概就是 .NET 工具链正在变好的一种具体感受吧。
返回列表