更新必须由独立进程(如Updater.exe)完成,主程序外置启动后退出;更新器需管理员权限、校验哈希、原子替换、回退备份,并用服务端语义版本+构建号比对,HttpClient重试下载,进度回调用IProgress。

更新逻辑必须由主程序外置启动
不能在当前进程内直接覆盖自身文件,Windows 会锁定正在运行的 .exe 和引用的 .dll。正确做法是启动一个独立的更新器进程(如 Updater.exe),由它负责下载、校验、替换,再重新拉起主程序。
- 主程序检查更新后,用
Process.Start("Updater.exe", "--app-path " + Application.ExecutablePath)启动更新器,并立即Application.Exit() - 更新器需以管理员权限运行(尤其要写入
Program Files),可通过清单文件声明requireAdministrator或在启动时检测并提权 - 更新器退出码建议区分:0=更新成功并已重启主程序,1=无更新,2=下载失败,3=校验失败——主程序可据此决定是否弹提示
版本比对不能只看文件时间或简单字符串
用户可能手动复制旧版文件,或服务器部署时未更新版本号。必须用服务端返回的权威版本标识,推荐组合使用:
-
semantic version(如2.1.3)做逻辑比较,用SemanticVersion类或正则解析后逐段比对 - 附加一个单调递增的
build number(如21304),避免语义版本相同但实际二进制不同 - 绝对不要依赖
AssemblyVersion属性自动递增——它常被 CI 覆盖或人为冻结
下载和替换过程必须带校验与原子性
直接覆盖 DLL 或 EXE 文件极易导致程序损坏。关键操作必须有回退能力:
- 下载新包时,先保存为临时名(如
MyApp.new.zip),下载完成后用SHA256校验哈希值,匹配不上就删临时文件并报错 - 解压时指定独立临时目录(如
%TEMP%\MyAppUpdate\),全部解压完成再执行替换 - 替换文件用
File.Replace方法(支持备份原文件),例如:File.Replace(newPath, originalPath, backupPath);
,而非File.Delete + File.Move - 若更新涉及配置文件,不要覆盖用户修改过的
appsettings.json,应合并或提示手动处理
HTTP 客户端要处理常见网络异常场景
内网环境、代理、证书错误、断点续传缺失都会让更新失败。不要用裸 WebClient:
- 改用
HttpClient,设置合理的Timeout(至少 90 秒)和MaxResponseContentBufferSize - 添加重试逻辑(如 3 次,指数退避),对
HttpRequestException、TaskCanceledException分类重试,但跳过 4xx 错误 - 如果服务端支持,请求头加
If-None-Match(ETag)或If-Modified-Since避免重复下载 - 进度回调必须用
IProgress<t></t>而非 UI 控件直接赋值,防止跨线程异常


















