C#/WPF下的通用自动更新模块


前言

目前常见的中小型软件/开源软件的更新模式大致有以下几种:

指引用户前往站点下载安装包自行覆盖安装。用户体验不太好,感觉很麻烦。代表有DBeaver/VirtualBox等;
提醒用户执行更新,经过同意后,下载安装包并运行。用户体验稍微好了一些,代表有Bandizip/Notepad++等;
软件在后台自动下载安装包,适时提醒用户执行更新,经过同意后运行安装包。这样能够节省用户的等待时间,代表有PotPlayer/FileZilla等。
一个完善的软件应当有着合理的更新机制。为了让用户获得更好的体验,更新过程应当是简便的、无痛的;为了提高开发者本身的效率,更新模块应当是通用的,可以快速地扩展到不同的项目中去。

同时,我认为软件应当是「绿色」的,即尽可能不提权,不写注册表,删除目录即卸载,不留垃圾文件。注意到上述的几种更新模式都使用了安装包作为最终媒介,这并不符合我的需求,因此我需要另行设计。如果你的项目符合我对「绿色」的定义,那么可以接着向下阅读。

文末将附上本项目源码(.NET4.5以上)。

特点使用简单

整个模块包含两个可执行文件Uploader与Updater。开发者方面,编写一个简单的配置文件并使用Uploader执行,就可以将新版本与相关信息上传到服务器(需要具有用FTP与HTTP访问服务器上文件的权限);客户端方面,只需引用Updater并调用一个方法,即可自动完成检查/通知/下载/安装更新的整个过程。

节省空间和流量
上传的内容经过压缩(可选),节省服务器空间。同时,客户端在更新时仅下载有变化的文件,相比下载整个安装包的做法更加节省服务器流量与用户的时间。

可完全自定义的更新内容展示
通过XAML代码向用户展示更新内容。不仅能做出漂亮的展示界面,还可以在其中添加图片和视频。

可对自身进行更新
更新程序在临时文件夹中运行,不占用项目根目录中任何一个文件。

流程

开发者:

编写配置文件(包含更新内容描述等);
用Uploader打开配置文件;
等待上传完毕。

客户端:

在项目中引用Updater;
客户端程序运行时,调用Updater的检查更新方法,将在后台下载并检验更新信息;
若有新版本,把新版本描述展示给用户;
用户同意更新后,将Updater复制到临时目录并启动;
Updater将当前版本文件内容与新版本进行对比,仅下载有变化的文件,显示下载进度;
下载完成后将新文件覆盖到项目根目录,启动主程序并退出Updater,完成更新。

配置文件解析

以下是一个配置文件的例子:

//必需区段,指定服务器地址。更新模块与下载模块将从此地址检测更新和下载新版本。
[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上以执行。


客户端调用方法

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

原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/275365.html

(0)
上一篇 2022年7月19日
下一篇 2022年7月19日

相关推荐

发表回复

登录后才能评论