C#/WPF下的通用自动更新模块
前言
目前常见的中小型软件/开源软件的更新模式大致有以下几种:
指引用户前往站点下载安装包自行覆盖安装。用户体验不太好,感觉很麻烦。代表有DBeaver/VirtualBox等;
提醒用户执行更新,经过同意后,下载安装包并运行。用户体验稍微好了一些,代表有Bandizip/Notepad++等;
软件在后台自动下载安装包,适时提醒用户执行更新,经过同意后运行安装包。这样能够节省用户的等待时间,代表有PotPlayer/FileZilla等。
一个完善的软件应当有着合理的更新机制。为了让用户获得更好的体验,更新过程应当是简便的、无痛的;为了提高开发者本身的效率,更新模块应当是通用的,可以快速地扩展到不同的项目中去。
同时,我认为软件应当是「绿色」的,即尽可能不提权,不写注册表,删除目录即卸载,不留垃圾文件。注意到上述的几种更新模式都使用了安装包作为最终媒介,这并不符合我的需求,因此我需要另行设计。如果你的项目符合我对「绿色」的定义,那么可以接着向下阅读。
文末将附上本项目源码(.NET4.5以上)。
特点使用简单
整个模块包含两个可执行文件Uploader与Updater。开发者方面,编写一个简单的配置文件并使用Uploader执行,就可以将新版本与相关信息上传到服务器(需要具有用FTP与HTTP访问服务器上文件的权限);客户端方面,只需引用Updater并调用一个方法,即可自动完成检查/通知/下载/安装更新的整个过程。
节省空间和流量
上传的内容经过压缩(可选),节省服务器空间。同时,客户端在更新时仅下载有变化的文件,相比下载整个安装包的做法更加节省服务器流量与用户的时间。
可完全自定义的更新内容展示
通过XAML代码向用户展示更新内容。不仅能做出漂亮的展示界面,还可以在其中添加图片和视频。
可对自身进行更新
更新程序在临时文件夹中运行,不占用项目根目录中任何一个文件。
流程
开发者:
编写配置文件(包含更新内容描述等);
用Uploader打开配置文件;
等待上传完毕。
客户端:
在项目中引用Updater;
客户端程序运行时,调用Updater的检查更新方法,将在后台下载并检验更新信息;
若有新版本,把新版本描述展示给用户;
用户同意更新后,将Updater复制到临时目录并启动;
Updater将当前版本文件内容与新版本进行对比,仅下载有变化的文件,显示下载进度;
下载完成后将新文件覆盖到项目根目录,启动主程序并退出Updater,完成更新。
配置文件解析
以下是一个配置文件的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | //必需区段,指定服务器地址。更新模块与下载模块将从此地址检测更新和下载新版本。 [DownloadRootAddress] www.example.com/content //必需区段,指定协议类型。取决于域名,在http与https中选择。 [DownloadSchema] http //必需区段,指定FTP服务器地址。 [FtpServerAddress] www.example.com //必需区段,指定FTP服务器端口。 [FtpServerPort] 21 //必需区段,连接FTP服务器的账号。该账号必须具有完整的读写权限。 [FtpServerAccount] example_account //必需区段,连接FTP服务器的密码。 [FtpServerPassword] example_password //必需区段,指定软件名称。在FTP根目录中将创建一个以[AppName]为名的文件夹,所有的更新内容都将存放在这一文件夹中。 [AppName] example_app //必需区段,指定软件版本号。如果在后面的[File]区段中指定了VERSIONSOURCE::True选项则此项无效。 [AppVersion] 1.0.0.0 //必需区段,指定强制更新。若用户的软件版本与最新版本之间有任何一个版本要求强制更新,则用户将不能忽略这次更新。 [ForceUpdate] False //必需区段,用XAML代码对更新的内容进行描述。向用户展示更新提示时,这段XAML将被解析为具体的UI。 [UpdateDescription] <StackPanel xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" > <StackPanel.Resources> <Style TargetType= "TextBlock" > <Setter Property= "Margin" Value= "20,4,4,4" /> </Style> <Style TargetType= "Image" > <Setter Property= "Margin" Value= "20,4" /> </Style> </StackPanel.Resources> <TextBlock Text= "新增" FontWeight= "Bold" /> <TextBlock Text= "1. xxxxx" /> <TextBlock Text= "修复" FontWeight= "Bold" /> <TextBlock Text= "1. yyyy" /> <TextBlock Text= "2. zzzz" /> </StackPanel> //可选区段,指定一个项目中的文件,该文件将被上传。 //第一行:文件在本地计算机中的绝对路径; //第二行:文件相对**项目**根目录的路径(可以指定其他文件名); //第三行:文件相对**FTP**根目录的路径(可以指定其他文件名)。 //COMPRESS选项:是否在上传时对这个文件进行压缩,默认为True; //COVERABLE选项:更新时本地若已存在此文件,是否允许覆盖,默认为True; //VERSIONSOURCE选项:是否将此文件作为版本号的来源,仅限对exe、dll文件使用,默认为False; //RUNAFTER选项:是否在更新完成时运行此文件,一般在主程序上使用,默认为False。 [File] D:\AnimeWallpaper\Release\AnimeWallpaper.exe \AnimeWallpaper.exe /AnimeWallpaper.exe COMPRESS::True COVERABLE::True VERSIONSOURCE::True RUNAFTER::True //可选区段,指定一个项目中的文件夹,该文件夹与其中文件(取决于后续选项)将被上传。 //第一行:文件夹在本地计算机中的绝对路径; //第二行:文件夹相对**项目**根目录的路径(不可指定文件夹名称); //第三行:文件夹相对**FTP**根目录的路径(不可指定文件夹名称)。 //INCLUDE选项:要包含的文件夹中的文件,可使用通配符(*),用分号隔开。若不指定此选项则包含全部文件; //COMPRESS选项:是否在上传时对这个文件夹中的文件进行压缩,默认为True; //COVERABLE选项:更新时本地若已存在此文件夹(与其中的文件),是否允许覆盖,默认为True; //SUBFOLDER选项:是否包含全部的子文件夹与其中的文件,默认为True; //RANDOMNAME选项:是否将文件夹中的文件名随机化(可避免服务器禁止上传某类文件等问题),默认为False; [Folder] D:\AnimeWallpaper\Release \ / INCLUDE::*.dll;EnneaCode.*.exe COMPRESS::True COVERABLE::True SUBFOLDER::False RANDOMNAME::True [Folder] D:\AnimeWallpaper\Release\x64 \x64 /x64 INCLUDE::*.dll COMPRESS::True COVERABLE::True SUBFOLDER::False [Folder] D:\AnimeWallpaper\Release\x86 \x86 /x86 INCLUDE::*.dll COMPRESS::True COVERABLE::True SUBFOLDER::False |
将配置文件保存为纯文本文档,拖动到Uploader上以执行。
客户端调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | using EnneaCode.UniversalUpdater; private async void Window_Loaded( object sender, RoutedEventArgs e) { var currentVersion = this .GetType().Assembly.GetName().Version; /* * public static Task<ecCheckUpdateState> CheckUpdateAsync(string appUpdateBaseUri, string appName, Version currentAppVersion) * appUpdateBaseUri:为配置文件中的[DownloadSchema] + "://" + [DownloadRootAddress]; * appName:为配置文件中的[AppName]; * currentAppVersion:当前程序版本。 */ var result = await ecUpdater.CheckUpdateAsync( "http://www.example.com/content" , "example_app" , currentVersion); switch (result) { case ecCheckUpdateState.UpdateConfirmed: /* * 更新确认时会尝试通过关闭主窗口的方式退出程序,如果程序不能这样退出, * 则必须在此执行退出程序的逻辑 */ //Environment.Exit(0); break ; case ecCheckUpdateState.UpToDate: MessageBox.Show( "已经是最新版本" ); break ; case ecCheckUpdateState.Error: MessageBox.Show( "无法获取更新" ); break ; } } |
过程是异步的,所以需要async/await。
源码https://download.csdn.net/download/q408774831/10616315
Uploader所需的辅助模块源码
https://download.csdn.net/download/q408774831/10616311
原文链接:https://blog.csdn.net/q408774831/article/details/81876761
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!