【手把手实战教学】基于C#和.NET Framework的WinForms开发教程系列(6)AutoUpdater.NET 自动更新
系列目录
(1)Visual Studio 2026 中创建、运行、发布应用
(2)开机自启
(3)自动定时更新
(4)后台运行
(5)版本自增
(6)AutoUpdater.NET 自动更新
文章目录
前言
开发环境:
IDE:Visual Studio 2026
语言:C#
框架:.NET Framework 4.5
UI:WinForms
在前面的文章中,我们让程序拥有了开机自启、定时任务、后台托盘等强大能力,并且通过自动生成脚本实现了版本号递增。但是,如果新版本只停留在开发者的电脑上,用户永远不会知道。如何让用户手中的软件自动检测并升级到最新版本,是商业软件必须要解决的问题。
本文将使用开源库 AutoUpdater.NET,仅需几十行代码,就能为你的 WinForms 程序添加强大的自动更新功能。
我们将从 NuGet 包的安装、服务器更新配置文件的准备,到程序中定时/手动检查更新的实现,一步步带你完成。
一、为什么需要自动更新?
传统的软件更新方式通常需要用户主动访问官网、下载安装包、卸载旧版本再安装新版本。这个过程不仅繁琐,而且用户往往因为嫌麻烦而停留在旧版本,导致 Bug 得不到修复、新功能无法触达,甚至引发兼容性问题。
有了自动更新后,程序可以在后台静默检查新版本,当发现更新时弹出友好的升级提示窗口,用户只需点击一下“更新”,程序就会自动下载并替换为新版本,整个过程无需离开软件界面,极大提升了用户体验和版本覆盖率。
二、实现原理
AutoUpdater.NET 是一个专门为 .NET WinForms / WPF 设计的自动更新组件,它的工作流程如下:
- 程序启动或定时触发时,向指定的 URL 请求一个
update.xml文件。 - 解析 XML 中的最新版本号、下载地址、更新日志、是否强制更新等信息。
- 与当前程序版本(通过
InstalledVersion属性设定)进行比较。 - 如果有新版本,弹出更新对话框,用户可以选择立即更新、稍后提醒或跳过;如果设置为强制更新,则用户不能跳过或关闭。
- 下载完成后,自动替换当前运行的程序文件(利用系统的文件移动特性),完成更新。
整个流程只需我们准备一个简单的 XML 文件和一个可访问的下载地址,程序内部调用其 API 即可。
三、实现步骤
1. 安装 NuGet 包
在“解决方案资源管理器”中右键项目 → “管理 NuGet 程序包”

搜索 Autoupdater,点击安装。

点击应用

提示版本不匹配
无法安装程序包“Autoupdater.NET.Official 1.9.2”。你正在尝试将此程序包安装到目标为“.NETFramework,Version=v4.5”的项目中,但该程序包不包含任何与该框架兼容的程序集引用或内容文件。有关详细信息,请联系程序包作者。
查看介绍发现最少支持.net 4.6.2

往前翻一翻历史版本,发现1.8.6支持.net 4.5,再次点击安装
点击应用

看到已完成说明安装完成

2. 准备服务器上的更新配置文件
在服务器上放置一个 update.xml 文件,内容如下(可根据需要调整字段):
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.0.6.0</version> <!-- 新版本号 -->
<url>https://your-server.com/download/MyApp_1.0.6.0.exe</url> <!-- 新版本安装包下载地址 -->
<mandatory>true</mandatory> <!-- 是否为强制更新 -->
<mandatoryVersion>1.0.3.0</mandatoryVersion> <!-- 低于此版本号将被强制更新 -->
</item>
version:最新版本号,主程序会用InstalledVersion与之比较。url:新版本安装程序的下载地址,支持 HTTP/HTTPS。注意名称不能用中文,否则更新不成功。mandatory:true表示强制更新,用户无法跳过或关闭更新窗口;false则用户可以自行选择。(实测true也可以关闭更新)mandatoryVersion:若当前程序版本低于此值,即使mandatory为false,也会变为强制更新,确保旧版本用户及时升级。
将这个文件上传到你的网站服务器(如 https://your-server.com/update.xml),确保程序可以访问。可以直接在浏览器测试,能看到xml内容才算成功。

3. 在程序中初始化 AutoUpdater
在 MainForm 的构造函数中,执行以下初始化代码:
using AutoUpdaterDotNET; // 引入命名空间
using System; // Version 类
public MainForm()
{
InitializeComponent();
#region 自动更新配置初始化(AutoUpdater.NET)
// 订阅 AutoUpdater 事件,以便手动检查时获得“无更新”提示
SubscribeAutoUpdaterEvent();
// 基础设置
// 设置更新对话框的标题
AutoUpdater.AppTitle = "我的应用";
// 添加 User-Agent,部分服务器需要验证
AutoUpdater.HttpUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
// AutoUpdater.NET 默认比较AssemblyVersion的版本号,但我们使用自定义的 BuildVersion.Version,
// 因此需要手动设置已安装版本号,确保更新检查正确比较版本。
// 配置版本号(AutoUpdater.NET 需要一个 Version 对象来比较版本,确保格式正确)
try
{
Version currentVersion = new Version(BuildVersion.Version);
AutoUpdater.InstalledVersion = currentVersion;
}
catch (Exception ex)
{
Debug.WriteLine($"版本号解析失败:{ex.Message}");
// 可回退到程序集版本
// AutoUpdater.InstalledVersion = Assembly.GetExecutingAssembly().GetName().Version;
}
// 根据编译模式设置错误报告:Debug 模式下显示详细错误,Release 关闭
#if DEBUG
AutoUpdater.ReportErrors = true; // 调试时显示详细错误,便于定位问题
#else
AutoUpdater.ReportErrors = false; // 正式版关闭错误报告,避免暴露底层细节
#endif
// 启动更新检查(异步,不会阻塞 UI 线程)
AutoUpdater.Start("https://your-server.com/update.xml");
// 自动更新检查定时器(每隔一小时)
InitHourlyUpdateTimer();
#endregion
}
上述代码完成了最基本的配置,现在每次启动程序时都会自动检查一次更新。
中间尝试实现强制更新,但是测试了许多配置都不成功
// 强制更新,经测试均无效
/*
AutoUpdater.Mandatory = false;
AutoUpdater.UpdateMode = Mode.ForcedDownload;
*/
/*
// 强制更新时隐藏“跳过”按钮
AutoUpdater.ShowSkipButton = false;
// 强制更新时隐藏“稍后提醒”按钮
AutoUpdater.ShowRemindLaterButton = false;
// 用户点击“跳过”后,1天后提醒(默认2天)
AutoUpdater.RemindLaterAt = 1;
// 单位:天
AutoUpdater.RemindLaterTimeSpan = RemindLaterFormat.Days;
*/
4. 添加定时检查更新(如每小时一次)
启动时检查一次还不够,我们需要在程序运行期间定期检查。利用 System.Windows.Forms.Timer,设置间隔为 1 小时:
#region 自动更新检查定时器(每隔一小时)
// 更新时间间隔(毫秒),默认 1 小时 = 3600000 毫秒
private const int UpdateCheckIntervalMs = 60 * 60 * 1000;
private System.Windows.Forms.Timer hourlyUpdateTimer;
/// <summary>
/// 初始化每小时更新检查定时器
/// </summary>
private void InitHourlyUpdateTimer()
{
hourlyUpdateTimer = new System.Windows.Forms.Timer();
hourlyUpdateTimer.Interval = UpdateCheckIntervalMs;
hourlyUpdateTimer.Tick += HourlyUpdateTimer_Tick;
hourlyUpdateTimer.Start();
}
private void HourlyUpdateTimer_Tick(object sender, EventArgs e)
{
// 定时器 Tick 时,先停止,避免重入
hourlyUpdateTimer.Stop();
try
{
// 确保自动检查时不会无端弹出“无更新”提示
_isManualCheck = false;
// 执行更新检查(AutoUpdater 内部会避免重复)
AutoUpdater.Start();
}
finally
{
// 重新启动定时器,开始下一个周期
hourlyUpdateTimer.Start();
}
}
#endregion
在构造函数中调用 InitHourlyUpdateTimer(); 即可,前面代码已添加。
5. 添加手动检查更新按钮,并优化无更新时的提示
用户有时想主动检查是否有新版本。在“关于”页面放一个按钮,并实现手动检查。
已经时最新版本时,AutoUpdater.NET默认无提示无返回,手动检查的时候最好给用户一个提示窗口。
#region 自动更新相关(手动/自动检查区分)
/// <summary>
/// 标识当前触发的更新检查是否为手动操作(用户点击“检查更新”按钮)。
/// true:手动检查;false:自动检查(定时器触发)。
/// 用于在 CheckForUpdateEvent 事件中决定是否弹出“无可用更新”提示。
/// </summary>
private bool _isManualCheck = false;
/// <summary>
/// 订阅 AutoUpdater 的更新检查完成事件,以便自定义无更新时的提示行为。
/// 注意:订阅该事件后,AutoUpdater 将不再自动显示内置的更新对话框,
/// 需要在事件中自行处理有更新时的 UI(可复用 AutoUpdater 的默认窗体)。
/// 本实现中,仅对“无更新”情况进行处理;有更新时仍让 AutoUpdater 自动处理(需取消事件订阅或手动调用 UpdateForm)。
/// 为简化,实际采用更稳健的方法:不订阅事件,而是手动调用 Start() 后通过额外逻辑判断,
/// 但这里按用户需求给出使用事件并区分手动/自动的示例。
/// </summary>
private void SubscribeAutoUpdaterEvent()
{
AutoUpdater.CheckForUpdateEvent += AutoUpdater_CheckForUpdateEvent;
}
/// <summary>
/// AutoUpdater 检查完成后的回调事件。
/// </summary>
/// <param name="args">包含更新信息或错误的对象</param>
private void AutoUpdater_CheckForUpdateEvent(UpdateInfoEventArgs args)
{
// 发生错误(网络、解析等)时,无论手动/自动都提示用户(可选,根据需求调整)
if (args.Error != null)
{
// 可根据 _isManualCheck 决定是否提示,此处建议始终提示便于排查
MessageBox.Show($"检查更新失败:{args.Error.Message}", "更新错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
_isManualCheck = false; // 重置标志
return;
}
// 有可用更新时,此处可按需求显示自定义界面,或调用 AutoUpdater 的默认更新窗体
if (args.IsUpdateAvailable)
{
// 使用内置更新窗体(需传入 args)
AutoUpdater.ShowUpdateForm(args);
return;
}
// 无可用更新:仅当手动检查时才提示
if (_isManualCheck)
{
MessageBox.Show("当前已经是最新版本!", "检查更新", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
// 重置手动检查标志,防止影响下一次自动检查
_isManualCheck = false;
}
#endregion
/// <summary>
/// 检查更新按钮点击事件:手动触发自动更新检查
/// </summary>
private void btnCheckUpdate_Click(object sender, EventArgs e)
{
// 标记为手动检查,以便在事件中区分
_isManualCheck = true;
// 另一种区分方法:在手动检查前,单独访问更新路径获取最新版本,对比当前版本后再决定是否更新
// 调用 AutoUpdater.Start() 进行更新检查(内部已有防重入机制,不会重复启动多个更新对话框)
AutoUpdater.Start();
}
别忘了在构造函数中调用 SubscribeAutoUpdaterEvent(); 订阅事件,前面代码已添加。
通过区分手动/自动标志,我们可以让自动定时检查时“无更新”保持安静,只在用户主动点击时才弹出提示,避免打扰。
6. 完整代码片段整合
将上述代码组合到 MainForm 中,项目结构更加清晰。关键点:初始化时设置版本、启动检查和定时器,按钮事件切换标志,事件处理中区分情况。
四、测试验证
-
发布一个新版本:前面的版本自增教程保证了发布的新版本号会自动增加。
-
后台更新流程:上传新安装包到服务器,更新 XML,在旧版程序上手动点击更新,弹出更新窗口。

五、注意事项
- XML 文件编码:建议使用 UTF-8 无 BOM,避免中文字符导致解析错误。虽然示例中都是英文,但良好的编码习惯很重要。
- 下载地址:确保安装包 URL 可访问,且新安装程序能够静默覆盖旧版本(Inno Setup 生成的安装包支持静默安装参数
/VERYSILENT,但需要脚本配合,本文未深入)。 - 版本格式:
AutoUpdater.InstalledVersion必须是System.Version对象,构建时注意格式为major.minor.build.revision,不能有多余的字符。 - UAC 权限:如果程序安装在 Program Files 目录,自动更新替换文件可能需要管理员权限。大多数情况下,Inno Setup 制作的安装程序在安装时会请求管理员权限,因此更新过程可以顺利完成。
- 更新文件替换机制:AutoUpdater.NET 并非直接覆盖正在运行的 EXE,而是利用 Windows 的“重启后替换”机制或临时文件,具体可查阅其文档。一般来说,它会将新文件下载到临时目录,然后启动新安装包,由安装包完成后续工作。
- 网络请求超时:如果服务器响应很慢,AutoUpdater 可能长时间无响应,可根据需要调整
HttpUserAgent和超时设置(高版本 AutoUpdater 可能不支持直接设置超时,此时可自行继承或使用异步方式包装,但本教程不做深入)。 - 调试模式:Debug 时
ReportErrors = true,会弹出详细错误信息,方便排查;Release 时务必设为false,防止暴露内部路径等敏感信息给用户。
六、总结
通过本篇,我们为 WinForms 程序集成了 AutoUpdater.NET 自动更新组件,实现了:
- 程序启动时自动检查更新
- 每小时后台轮询更新
- 用户手动检查更新
- 无更新时静默,手动检查才提示
配合之前文章中的版本自增、开机自启、托盘运行,一个成熟稳定的桌面应用程序框架已经搭建完成。你现在可以将自己的业务逻辑填入其中,专注于核心功能的开发,而不用担心分发和更新的问题。
后记
文中代码均已在实际项目中稳定运行,NuGet 包版本为 1.8.6,其他版本可能略有差异,请参考官方文档。如有任何疑问,欢迎在评论区留言交流。
参考文献
AutoUpdater.NET
如何为你的 .NET 应用程序添加自动更新功能?
AutoUpdater.NET:5步实现.NET桌面应用自动更新终极指南
轻松实现.NET应用自动更新:AutoUpdater.NET教程
喜欢的点个关注吧><!祝你永无bug~
/*
_ooOoo_
o8888888o
88" . "88
(| -_- |)
O\ = /O
____/`---'\____
.' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' | |
\ .-\__ `-` ___/-. /
___`. .' /--.--\ `. . __
."" '< `.___\_<|>_/___.' >'"".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `-. \_ __\ /__ _/ .-` / /
======`-.____`-.___\_____/___.-`____.-'======
`=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
佛祖保佑 永无BUG
*/

AutoUpdater.NET自动更新&spm=1001.2101.3001.5002&articleId=160661641&d=1&t=3&u=2a685b4602d14fd091dc57cc0ae5a809)
402

被折叠的 条评论
为什么被折叠?



