做 .NET 桌面应用程序开发这些年,我几乎每到一个新项目都会遇到同一个问题:自动更新怎么做。无论你是在写 WinForms、WPF,还是基于 .NET Framework 的维护型项目,只要软件发出去,用户手里跑的不是你本地那份“最新代码”,更新机制就躲不掉。客户不会主动去官网下载新包,也不会看你发的更新说明,他们只会在程序里点“检查更新”这个按钮,然后期望一切自动完成。这篇就把我从零手写更新器、到集成第三方库、再到维护更新通道这整个过程里的经验和踩过的坑,完整分享出来。
这篇文章适合几类读者:正在给 .NET 桌面应用设计更新方案的开发者;遇到更新后文件被占用、证书不信任、杀软误报等问题的排查者;以及想搞清楚 Squirrel、AutoUpdater.NET、ClickOnce 这些方案到底有什么区别、该选哪个的技术负责人。我会尽量把每个方案的原理、选型逻辑和实操细节讲透,而不是只给你贴一段代码。
1. 先想清楚:五种主流更新方案各自的定位
选择更新方案之前,得先弄清楚一件事:你需要的是一次性的发布渠道,还是一个长期的更新机制。很多项目在初期图省事,直接用“让用户下载安装包手动装”,结果发版一两个月就开始被用户投诉“又要重新装”。我建议在架构设计阶段就把更新链路定下来,后面会省掉大量返工成本。
1.1 从零手写:最灵活也最费心的方案
手写更新器的核心思路是:自己管理更新清单、文件下载、版本校验和替换逻辑。它的最大优势是完全可控,更新频率、更新范围、UI 交互、回滚策略都可以按照项目需求来定制,不受第三方库的约束。
但代价也很明显:你要自己处理的东西远比想象中多。版本号比较不能直接用字符串比;下载过程中断需要重试和断点续传;更新文件被主程序占用需要设计独立的更新流程;下载下来的文件还得做哈希校验防止损坏;一旦更新到一半失败,还要有办法回滚到上一个可用版本。
我见过不少团队在这上面翻了车。最常见的错误是让主程序自己覆盖自己,结果 Windows 文件锁直接导致更新失败。还有人把更新包做成 zip 后直接解压覆盖,却不处理正在运行的程序集,最后进程崩溃。如果你决定手写,至少要把这些边界情况都覆盖住,我会在第二节详细讲。
1.2 集成更新库:AutoUpdater.NET、Squirrel.Windows 与 Velopack
如果不想从零开始,NuGet 上现成的更新库可以大幅缩短开发时间。这几种库的定位差别很大,选错了后面很难受。
AutoUpdater.NET 是最轻量的一种,适合在已有项目里快速接入。它的工作方式是:程序启动时或者点击按钮时,从一个 XML/JSON 地址拉取更新清单,发现有新版本就弹出更新提示,下载安装包或者更新文件后引导用户重启完成更新。它灵活、简单,嵌到现有 WinForms/WPF 项目里非常方便,但它的更新过程依然是“下载文件+替换文件”,文件占用问题仍然需要处理。
Squirrel.Windows 则是另一种思路,它把安装过程做成了一套规范,每次发布都会生成安装器和增量包,更新时通过内置的 UpdateManager 拉取增量并静默安装。它在后期维护体验上更好,但最开始接入时需要改变你的打包和安装方式,学习成本会高一些。Velopack 是 Squirrel 的现代化分支,支持 .NET 6+,维护更积极,如果你是新项目,我更推荐 Velopack 而不是老旧的 Squirrel.Windows。
1.3 平台级方案:ClickOnce 与 MSIX
除了手写和第三方库,还有两个平台级方案经常被忽略:ClickOnce 和 MSIX。
ClickOnce 是 .NET Framework 时代最经典的自动更新方案,开发者在 Visual Studio 里直接把项目发布成 clickonce 应用,然后放到服务器上分发给用户。客户端安装后,每次启动都会检查发布服务器,有新版就自动更新。它最省心的地方是:更新逻辑、安装包生成、版本管理全部由平台完成,几乎不用写代码。缺陷也很明显,权限受限、无法做系统级安装、对老 .NET Framework 应用的支持虽然成熟但到现代 .NET 上已经不再推荐。
MSIX 是微软比较新的打包格式,比 ClickOnce 能力更强,支持增量更新、灵活部署、权限隔离。但它对项目的改造力度比较大,打包签名、证书管理、分发通道都有自己的规范,而且要配合 Windows 应用打包机制来用,不是所有项目都适合迁移上去。如果你想把自动更新做得很“正规”,MSIX 值得调研,但如果只是内部工具,可以先跳过。
我个人对不同场景的建议是:老项目快速迭代,优先用 AutoUpdater.NET 或者干脆手写;新项目重视安装体验和长期维护,直接上 Velopack;团队有资源并且产品要走正规分发渠道,再考虑 MSIX。ClickOnce 除非是历史包袱很重,否则不建议新项目使用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 自建更新通道:服务端资源设计与客户端更新器实现
这一节我详细讲一下手写更新方案的核心结构。不要觉得自建通道很复杂,其实客户端更新器只需要负责三件事:拉取并解析更新清单、下载差异文件并校验、退出主程序后完成替换和回滚。服务端则更简单,只要提供一个静态文件服务器,放好更新清单和更新包就行。
2.1 更新清单:版本信息与文件哈希的元数据设计
整个更新流程的起点是更新清单(Manifest),它是一个描述“最新版本包含哪些文件、校验值是什么、最低兼容版本是多少”的 JSON 或 XML 文件。我常用 JSON,因为解析方便,而且扩展性比 XML 好。
一个典型的更新清单长这样:
json复制{
"appId": "com.example.desktopproduct",
"channel": "stable",
"appVersion": "2.3.1",
"minSupportedVersion": "2.0.0",
"publishTime": "2025-06-01T10:30:00Z",
"releaseNotes": "修复导出报表时偶发崩溃的问题",
"files": [
{
"path": "MyApp.exe",
"url": "https://update.example.com/packages/v2.3.1/MyApp
