NETクライアント·プログラム自動更新{{. NETくらいあんとぷろしーじゃじどうこうしん}}
日々の開発で書いたクライアントプログラムを複数のホストにデプロイする必要がある場合、プログラムをアップグレードする必要がある場合、1つのアップグレードは非常に面倒になります。この時点で、この記事の. NETクライアントプログラム自動更新技術を使用できます。
本文所述的自动更新技术主要使用了开源的GeneralUpdate组件,可用于Winform/WPF/ConsoleApp等应用程序的自动更新。
GeneralUpdate组件是微软的一位 MVP 负责开发和维护的,仓库地址及截图如下。作者提供的使用文档和视频有些过于简单,而且不同版本还存在一定的兼容性问题,这些都没有很好地解释,所以初次接触这个组件的开发人员可能会有点懵。笔者结合自己在项目中实际的使用情况,更加详细地介绍一下该组件的使用方式。
GitHub:https://github.com/WELL-E/AutoUpdater

Gitee:https://gitee.com/Juster-zhu/GeneralUpdate

フローチャートを更新

オリジナル画像の説明が明確ではないため、上の画像に赤いフォントで説明を追加しました。上の図では、3つのコンポーネントまたはサービスの相互作用のように見えますが、正確には4つです。
- クライアントプログラムのバージョンチェックサービス(必須ではありません):このサービスは少なくとも2つのAPIを提供します。1つはクライアントプログラムの最新バージョンを確認するためのAPIで、もう1つは現在のクライアントのすべての更新バージョンを取得するためのAPIです。検証サービスを個別に記述してデプロイしたくない場合もありますが、代わりにデータベースを使用することができます。クライアントプログラムは、データベースに直接問い合わせ、現在のプログラムのすべての更新されたバージョンを判断し、取得する。
- クライアントプログラム(必須):自動更新機能を必要とする業務プログラムで、リフレクションを通じて自身のアセンブリのバージョン番号を取得し、サーバ/データベースと比較して新しいバージョンがあるかどうかを判断できます。
- コンポーネントの更新必須コンポーネントの更新は、実際には、およびクライアントプログラムの兄弟ディレクトリに置かれる別個の実行可能ファイルです。このコンポーネントの主な役割は、指定されたパスからクライアントプログラムのすべての更新された圧縮パッケージをダウンロードし、1つずつ解凍してクライアントプログラムのバージョンごとのアップグレードを実現することです。クライアントがサーバから更新するファイルへのパスを取得すると、プロセス間通信によって更新コンポーネントを起動する必要があり、更新コンポーネントの起動後にクライアントプログラムをシャットダウンして、一部のファイルが占有されて更新が失敗しないようにする必要があります。コンポーネントの更新更新更新に成功した後、クライアントを再起動し、コンポーネント自体をシャットダウンして自動更新を完了する。
- ファイルサーバ(必須):クライアントプログラムの更新圧縮パッケージがファイルサーバにアップロードされると圧縮パッケージごとのURLが得られ,更新コンポーネントはそのURLに従ってプログラムをダウンロードする.筆者が使用しているファイルサーバはHFSで、ダウンロードアドレスはHFSダウンロードです。
コード構造の分析

上图中以GeneralUpdate开头的工程是自动更新功能的核心代码,在 nuget 服务器上能看到各个工程的包。具体使用哪个包取决于你是想实现更新组件自更新还是更新客户端程序还是编写版本校验服务,可参考框架 README.md 中的介绍。
这里要说明的是,上述组件不是向下兼容的!3.x.x 版本的组件的很多方法都进行了更名,因此不能直接从 2.x.x 版本直接升级。
上图中以AutoUpdate开头的工程是对自动更新流程图中 3 个主要组件的简单实现:
ConsoleApp:更新组件的控制台版本 DEMO(需要和文件服务器配合使用,引入了 GeneralUpdate.Core)MauiApp-Sample:未仔细研究,不清楚MinimalService:客户端版本校验服务 DEMO(引入了 GeneralUpdate.AspNetCore)Test:更新组件自更新的 WPF 版本 DEMO(需要和 MinimalService 配合使用,引入了 GeneralUpdate.ClientCore)WpfApp:GeneralUpdate.Single包的使用 DEMO,用于构建单例版本的更新组件(引入了 GeneralUpdate.Single)WpfNet6-Sample:更新更新组件的 WPF 版本程序。
Winformアプリケーションの自動更新
从上节的描述可知,如果我们不想编写客户端版本校验服务,只想通过文件服务器来更新客户端程序,那么我们只需要一个控制台版本的更新组件即可,所以可参考ConsoleApp工程下的代码。
コンポーネントのコンソール実装の更新
说明:本示例使用的是GeneralUpdate.Core的 2.1.6 版本。因为 GitHub 上的源码已升级到 3.x.x 版本,支持了.NET 6.0,但笔者电脑上的缺乏相关框架,无法编译通过,所以检出到了源码的某次提交,这样即使使用的时候出了问题也可以通过调试源码的方式来解决。如果大家充分理解了本文的意思,直接安装最新版本的 nuget 包也可以,直接参考最新版源码的相关示例。
using System;
using System.ComponentModel;
using System.Diagnostics;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Strategys;
using GeneralUpdate.Core.Update;
using ProgressChangedEventArgs = GeneralUpdate.Core.Update.ProgressChangedEventArgs;
namespace AutoUpdate.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
// args = new []{
// "1.0.1",
// "1.0.2",
// "",
// "http://127.0.0.1:7000/client_v1.0.2.zip",
// @"D:\Project",
// "36aad55a19f85ee6e1fbdc26510a26c1"
// };
KillProcess("你的客户端程序名,不用加exe");
GeneralUpdateBootstrap bootstrap = new GeneralUpdateBootstrap();
bootstrap.DownloadStatistics += OnDownloadStatistics;
bootstrap.ProgressChanged += OnProgressChanged;
bootstrap.Strategy<DefultStrategy>().
Option(UpdateOption.Format, "zip").
Option(UpdateOption.MainApp, "你的客户端程序名,不用加exe").
Option(UpdateOption.DownloadTimeOut, 60).
RemoteAddress(args).
Launch();
Console.ReadKey();
}
private static void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.Type == ProgressType.Updatefile)
{
var str = $"当前更新第:{e.ProgressValue}个,更新文件总数:{e.TotalSize}";
Console.WriteLine(str);
}
if (e.Type == ProgressType.Done)
{
Console.WriteLine("更新完成");
}
if (e.Type == ProgressType.Fail)
{
Console.WriteLine(e.Message);
}
}
private static void OnDownloadStatistics(object sender, DownloadStatisticsEventArgs e)
{
Console.WriteLine($"下载速度:{e.Speed},剩余时间:{e.Remaining.Minute}:{e.Remaining.Second}");
}
private static void KillProcess(string processName)
{
foreach (var process in Process.GetProcesses())
{
if (!process.ProcessName.ToUpper().Contains(processName.ToUpper())) continue;
try
{
process.Kill();
process.WaitForExit();
}
catch (Win32Exception)
{
}
}
}
}
}
クライアントの呼び出し
Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
var ver = $"{version.Major}.{version.Minor}.{version.Build}";
//从数据库获取比当前程序集版本更高的版本信息
var versionInfo = TOSBll.Instance.GetLastUpdateVersionInfo(1, ver);
if (versionInfo != null)
{
string para =
$"{ver} {versionInfo.VERSION} \"\" {versionInfo.URL} {Environment.CurrentDirectory} {versionInfo.MD5}";
ExecuteAsAdmin("AutoUpdate.ConsoleApp.exe", para);
return;
}
private static void ExecuteAsAdmin(string fileName, string args)
{
Process proc = new Process();
proc.StartInfo.FileName = fileName;
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.Verb = "runas";
proc.StartInfo.Arguments = args;
proc.Start();
}
由上述代码可知,客户端使用进程间通信的方式来启动更新组件,并传入更新参数信息。这里通过管理员权限启动更新组件,以免更新失败(组件在更新时需要把文件拷贝到系统的临时目录,更新成功后删除,权限不足时会出错)。不过笔者测试中发现这种方式启动仍然失败,还是通过右键AutoUpdate.ConsoleApp.exe程序并附加管理员权限才成功的。
いくつかのスロット。
- クリティカルリリースはラベル付けされておらず、nugetパッケージのバージョン2.1.6に切り替えたいユーザは、どのコミットをチェックするかを知りません。
- 新しいバージョンは古いバージョンと互換性がありません。
- ユニットテストファイルで使用されるコードは古いバージョンですが、コンポーネントのソースコードは新しいバージョンであり、コンポーネントに直接接触した人は混乱の輪になります。
- 目前还存在一些小 bug,比如
FileUtil.Update32Or64Libs()就会抛出异常,因为把一个目录删除了两遍,从而导致第一次启动更新的时候更新失败,但是第二次更新的时候却能成功,因为目前已经删了。笔者已提 Issue,不知作者何时能解决。 - アーカイブは単純すぎる。
まとめまとめまとめ
虽然GeneralUpdate组件有一些不足,但相信经过本文的介绍,大家已经知道如何避坑来使用该组件。总体来说,该组件的功能还是蛮好用的。考虑到该组件只有作者一个人维护,其实已经做得蛮好了,还是要感谢作者的付出的。