翼度科技»论坛 编程开发 .net 查看内容

.NET C# 程序自动更新组件

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
引言

本来博主想偷懒使用AutoUpdater.NET组件,但由于博主项目有些特殊性和它的功能过于多,于是博主自己实现一个轻量级独立自动更新组件,可稍作修改集成到大家自己项目中,比如:WPF/Winform/Windows服务。大致思路:发现更新后,从网络上下载更新包并进行解压,同时在 WinForms 应用程序中显示下载和解压进度条,并重启程序。以提供更好的用户体验。
1. 系统架构概览

自动化软件更新系统主要包括以下几个核心部分:

  • 版本检查:定期或在启动时检查服务器上的最新版本。
  • 下载更新:如果发现新版本,则从服务器下载更新包。
  • 解压缩与安装:解压下载的更新包,替换旧文件。
  • 重启应用:更新完毕后,重启应用以加载新版本。


组件实现细节

独立更新程序逻辑:

1. 创建 WinForms 应用程序

首先,创建一个新的 WinForms 应用程序,用来承载独立的自动更新程序,界面就简单两个组件:添加一个 ProgressBar 和一个 TextBox 控件,用于显示进度和信息提示。
2. 主窗体加载事件

我们在主窗体的 Load 事件中完成以下步骤:

  • 解析命令行参数。
  • 关闭当前运行的程序。
  • 下载更新包并显示下载进度。
  • 解压更新包并显示解压进度。
  • 启动解压后的新版本程序。
下面是主窗体 Form1_Load 事件处理程序的代码:
  1. private async void Form1_Load(object sender, EventArgs e)
  2. {
  3.     // 读取和解析命令行参数
  4.     var args = Environment.GetCommandLineArgs();
  5.     if (!ParseArguments(args, out string downloadUrl, out string programToLaunch, out string currentProgram))
  6.     {
  7.         _ = MessageBox.Show("请提供有效的下载地址和启动程序名称的参数。");
  8.         Application.Exit();
  9.         return;
  10.     }
  11.     // 关闭当前运行的程序
  12.     Process[] processes = Process.GetProcessesByName(currentProgram);
  13.     foreach (Process process in processes)
  14.     {
  15.         process.Kill();
  16.         process.WaitForExit();
  17.     }
  18.     // 开始下载和解压过程
  19.     string downloadPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(downloadUrl));
  20.     progressBar.Value = 0;
  21.     textBoxInformation.Text = "下载中...";
  22.     await DownloadFileAsync(downloadUrl, downloadPath);
  23.     progressBar.Value = 0;
  24.     textBoxInformation.Text = "解压中...";
  25.     await Task.Run(() => ExtractZipFile(downloadPath, AppDomain.CurrentDomain.BaseDirectory));
  26.     textBoxInformation.Text = "完成";
  27.     // 启动解压后的程序
  28.     string programPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, programToLaunch);
  29.     if (File.Exists(programPath))
  30.     {
  31.         _ = Process.Start(programPath);
  32.         Application.Exit();
  33.     }
  34.     else
  35.     {
  36.         _ = MessageBox.Show($"无法找到程序:{programPath}");
  37.     }
  38. }
复制代码
3. 解析命令行参数

我们需要从命令行接收下载地址、启动程序名称和当前运行程序的名称。以下是解析命令行参数的代码:
查看代码
  1.         private bool ParseArguments(string[] args, out string downloadUrl, out string programToLaunch, out string currentProgram)
  2.         {
  3.             downloadUrl = null;
  4.             programToLaunch = null;
  5.             currentProgram = null;
  6.             for (int i = 1; i < args.Length; i++)
  7.             {
  8.                 if (args[i].StartsWith("--url="))
  9.                 {
  10.                     downloadUrl = args[i].Substring("--url=".Length);
  11.                 }
  12.                 else if (args[i] == "--url" && i + 1 < args.Length)
  13.                 {
  14.                     downloadUrl = args[++i];
  15.                 }
  16.                 else if (args[i].StartsWith("--launch="))
  17.                 {
  18.                     programToLaunch = args[i].Substring("--launch=".Length);
  19.                 }
  20.                 else if (args[i] == "--launch" && i + 1 < args.Length)
  21.                 {
  22.                     programToLaunch = args[++i];
  23.                 }
  24.                 else if (args[i].StartsWith("--current="))
  25.                 {
  26.                     currentProgram = args[i].Substring("--current=".Length);
  27.                 }
  28.                 else if (args[i] == "--current" && i + 1 < args.Length)
  29.                 {
  30.                     currentProgram = args[++i];
  31.                 }
  32.             }
  33.             return !string.IsNullOrEmpty(downloadUrl) && !string.IsNullOrEmpty(programToLaunch) && !string.IsNullOrEmpty(currentProgram);
  34.         }
复制代码
4. 下载更新包并显示进度

使用 HttpClient 下载文件,并在下载过程中更新进度条:
  1. private async Task DownloadFileAsync(string url, string destinationPath)
  2. {
  3.     using (HttpClient client = new HttpClient())
  4.     {
  5.         using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
  6.         {
  7.             _ = response.EnsureSuccessStatusCode();
  8.             long? totalBytes = response.Content.Headers.ContentLength;
  9.             using (var stream = await response.Content.ReadAsStreamAsync())
  10.             using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
  11.             {
  12.                 var buffer = new byte[8192];
  13.                 long totalRead = 0;
  14.                 int bytesRead;
  15.                 while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
  16.                 {
  17.                     await fileStream.WriteAsync(buffer, 0, bytesRead);
  18.                     totalRead += bytesRead;
  19.                     if (totalBytes.HasValue)
  20.                     {
  21.                         int progress = (int)((double)totalRead / totalBytes.Value * 100);
  22.                         _ = Invoke(new Action(() => progressBar.Value = progress));
  23.                     }
  24.                 }
  25.             }
  26.         }
  27.     }
  28. }
复制代码
5. 解压更新包并显示进度

在解压过程中跳过 Updater.exe 文件(因为当前更新程序正在运行,大家可根据需求修改逻辑),并捕获异常以确保进度条和界面更新:
 
  1. private void ExtractZipFile(string zipFilePath, string extractPath)
  2. {
  3.     using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
  4.     {
  5.         int totalEntries = archive.Entries.Count;
  6.         int extractedEntries = 0;
  7.         foreach (ZipArchiveEntry entry in archive.Entries)
  8.         {
  9.             try
  10.             {
  11.                 // 跳过 Updater.exe 文件
  12.                 if (entry.FullName.Equals(CustConst.AppNmae, StringComparison.OrdinalIgnoreCase))
  13.                 {
  14.                     continue;
  15.                 }
  16.                 string destinationPath = Path.Combine(extractPath, entry.FullName);
  17.                 _ = Invoke(new Action(() => textBoxInformation.Text = $"解压中... {entry.FullName}"));
  18.                 if (string.IsNullOrEmpty(entry.Name))
  19.                 {
  20.                     // Create directory
  21.                     _ = Directory.CreateDirectory(destinationPath);
  22.                 }
  23.                 else
  24.                 {
  25.                     // Ensure directory exists
  26.                     _ = Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
  27.                     // Extract file
  28.                     entry.ExtractToFile(destinationPath, overwrite: true);
  29.                 }
  30.                 extractedEntries++;
  31.                 int progress = (int)((double)extractedEntries / totalEntries * 100);
  32.                 _ = Invoke(new Action(() => progressBar.Value = progress));
  33.             }
  34.             catch (Exception ex)
  35.             {
  36.                 _ = Invoke(new Action(() => textBoxInformation.Text = $"解压失败:{entry.FullName}, 错误: {ex.Message}"));
  37.                 continue;
  38.             }
  39.         }
  40.     }
  41. }
复制代码
6. 启动解压后的新程序

在解压完成后,启动新版本的程序,并且关闭更新程序:
查看代码
  1.  private void Form1_Load(object sender, EventArgs e)
  2. {
  3.     // 省略部分代码...
  4.     string programPath = Path.Combine(extractPath, programToLaunch);
  5.     if (File.Exists(programPath))
  6.     {
  7.         Process.Start(programPath);
  8.         Application.Exit();
  9.     }
  10.     else
  11.     {
  12.         MessageBox.Show($"无法找到程序:{programPath}");
  13.     }
  14. }
复制代码
检查更新逻辑

1. 创建 UpdateChecker 类

创建一个 UpdateChecker 类,对外提供引用,用于检查更新并启动更新程序:
  1. public static class UpdateChecker
  2. {
  3.     public static string UpdateUrl { get; set; }
  4.     public static string CurrentVersion { get; set; }
  5.     public static string MainProgramRelativePath { get; set; }
  6.     public static void CheckForUpdates()
  7.     {
  8.         try
  9.         {
  10.             using (HttpClient client = new HttpClient())
  11.             {
  12.                 string xmlContent = client.GetStringAsync(UpdateUrl).Result;
  13.                 XDocument xmlDoc = XDocument.Parse(xmlContent);
  14.                 var latestVersion = xmlDoc.Root.Element("version")?.Value;
  15.                 var downloadUrl = xmlDoc.Root.Element("url")?.Value;
  16.                 if (!string.IsNullOrEmpty(latestVersion) && !string.IsNullOrEmpty(downloadUrl) && latestVersion != CurrentVersion)
  17.                 {
  18.                     // 获取当前程序名称
  19.                     string currentProcessName = Process.GetCurrentProcess().ProcessName;
  20.                     // 启动更新程序并传递当前程序名称
  21.                     string arguments = $"--url "{downloadUrl}" --launch "{MainProgramRelativePath}" --current "{currentProcessName}"";
  22.                     _ = Process.Start(CustConst.AppNmae, arguments);
  23.                     // 关闭当前主程序
  24.                     Application.Exit();
  25.                 }
  26.             }
  27.         }
  28.         catch (Exception ex)
  29.         {
  30.             _ = MessageBox.Show($"检查更新失败:{ex.Message}");
  31.         }
  32.     }
  33. }
复制代码
2. 服务器配置XML

服务器上存放一个XML文件配置当前最新版本、安装包下载地址等,假设服务器上的 XML 文件内容如下:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <update>
  3.     <version>1.0.2</version>
  4.     <url>https://example.com/yourfile.zip</url>
  5. </update>
复制代码
主程序调用更新检查

主程序可以通过定时器或者手动调用检查更新的逻辑,博主使用定时检查更新:
查看代码
  1.  internal static class AutoUpdaterHelp
  2.   {
  3.       private static readonly System.Timers.Timer timer;
  4.       static AutoUpdaterHelp()
  5.       {
  6.           UpdateChecker.CurrentVersion = "1.0.1";
  7.           UpdateChecker.UpdateUrl = ConfigurationManager.AppSettings["AutoUpdaterUrl"].ToString();
  8.           UpdateChecker.MainProgramRelativePath = "Restart.bat";
  9.           timer = new System.Timers.Timer
  10.           {
  11.               Interval = 10 * 1000//2 * 60 * 1000
  12.           };
  13.           timer.Elapsed += delegate
  14.           {
  15.               UpdateChecker.CheckForUpdates();
  16.           };
  17.       }
  18.       public static void Start()
  19.       {
  20.           timer.Start();
  21.       }
  22.       public static void Stop()
  23.       {
  24.           timer.Stop();
  25.       }
  26.   }
复制代码
思考:性能与安全考量

在实现自动化更新时,还应考虑性能和安全因素。例如,为了提高效率,可以添加断点续传功能;为了保证安全,应验证下载文件的完整性,例如使用SHA256校验和,这些博主就不做实现与讲解了,目前的功能已经完成了基本的自动更新逻辑
结论

自动化软件更新是现代软件开发不可或缺的一部分,它不仅能显著提升用户体验,还能减轻开发者的维护负担。通过上述C#代码示例,你可以快速搭建一个基本的自动化更新框架,进一步完善和定制以适应特定的应用场景。
本文提供了构建自动化软件更新系统的C#代码实现,希望对开发者们有所帮助。如果你有任何疑问或建议,欢迎留言讨论!
 

来源:https://www.cnblogs.com/Bob-luo/p/18231510
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具