前言 本来博主想偷懒使用AutoUpdater.NET组件,但由于博主项目有些特殊性和它的功能过于多,于是博主自己实现一个轻量级独立自动更新组件,可稍作修改集成到大家自己项目中,比如:WPF/Winform/Windows服务。 
大致思路: 发现更新后,从网络上下载更新包并进行解压,同时在 WinForms 应用程序中显示下载和解压进度条,并重启程序。以提供更好的用户体验。 
系统架构概览 
自动化软件更新系统主要包括以下几个核心部分: 
组件实现细节 独立更新程序逻辑 1、创建 WinForms 应用程序 
首先,创建一个新的 WinForms 应用程序,用来承载独立的自动更新程序,界面就简单两个组件: 
添加一个  ProgressBar  和一个  TextBox  控件,用于显示进度和信息提示。 
2、主窗体加载事件 
我们在主窗体的  Load  事件中完成以下步骤: 
下面是主窗体  Form1_Load  事件处理程序的代码: 
private  async  void  Form1_Load (object  sender, EventArgs e ) {     // 读取和解析命令行参数      var  args = Environment.GetCommandLineArgs();     if  (!ParseArguments(args, out  string  downloadUrl, out  string  programToLaunch, out  string  currentProgram))     {         _ = MessageBox.Show("请提供有效的下载地址和启动程序名称的参数。" );         Application.Exit();         return ;     }     // 关闭当前运行的程序      Process[] processes = Process.GetProcessesByName(currentProgram);     foreach  (Process process in  processes)     {         process.Kill();         process.WaitForExit();     }     // 开始下载和解压过程      string  downloadPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(downloadUrl));     progressBar.Value = 0 ;     textBoxInformation.Text = "下载中..." ;     await  DownloadFileAsync(downloadUrl, downloadPath);     progressBar.Value = 0 ;     textBoxInformation.Text = "解压中..." ;     await  Task.Run(() => ExtractZipFile(downloadPath, AppDomain.CurrentDomain.BaseDirectory));     textBoxInformation.Text = "完成" ;     // 启动解压后的程序      string  programPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, programToLaunch);     if  (File.Exists(programPath))     {         _ = Process.Start(programPath);         Application.Exit();     }     else      {         _ = MessageBox.Show($"无法找到程序:{programPath} " );     } }3、解析命令行参数 
我们需要从命令行接收下载地址、启动程序名称和当前运行程序的名称。以下是解析命令行参数的代码: 
private  bool  ParseArguments (string [] args, out  string  downloadUrl, out  string  programToLaunch, out  string  currentProgram ) {     downloadUrl = null ;     programToLaunch = null ;     currentProgram = null ;     for  (int  i = 1 ; i < args.Length; i++)     {         if  (args[i].StartsWith("--url=" ))         {             downloadUrl = args[i].Substring("--url=" .Length);         }         else  if  (args[i] == "--url"  && i + 1  < args.Length)         {             downloadUrl = args[++i];         }         else  if  (args[i].StartsWith("--launch=" ))         {             programToLaunch = args[i].Substring("--launch=" .Length);         }         else  if  (args[i] == "--launch"  && i + 1  < args.Length)         {             programToLaunch = args[++i];         }         else  if  (args[i].StartsWith("--current=" ))         {             currentProgram = args[i].Substring("--current=" .Length);         }         else  if  (args[i] == "--current"  && i + 1  < args.Length)         {             currentProgram = args[++i];         }     }     return  !string .IsNullOrEmpty(downloadUrl) && !string .IsNullOrEmpty(programToLaunch) && !string .IsNullOrEmpty(currentProgram); }4、下载更新包并显示进度 
使用  HttpClient  下载文件,并在下载过程中更新进度条: 
private  async  Task DownloadFileAsync (string  url, string  destinationPath ) {     using  (HttpClient client = new  HttpClient())     {         using  (HttpResponseMessage response = await  client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))         {             _ = response.EnsureSuccessStatusCode();             long ? totalBytes = response.Content.Headers.ContentLength;             using  (var  stream = await  response.Content.ReadAsStreamAsync())             using  (var  fileStream = new  FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192 , true ))             {                 var  buffer = new  byte [8192 ];                 long  totalRead = 0 ;                 int  bytesRead;                 while  ((bytesRead = await  stream.ReadAsync(buffer, 0 , buffer.Length)) != 0 )                 {                     await  fileStream.WriteAsync(buffer, 0 , bytesRead);                     totalRead += bytesRead;                     if  (totalBytes.HasValue)                     {                         int  progress = (int )((double )totalRead / totalBytes.Value * 100 );                         _ = Invoke(new  Action(() => progressBar.Value = progress));                     }                 }             }         }     } }5、解压更新包并显示进度 
在解压过程中跳过  Updater.exe  文件(因为当前更新程序正在运行,大家可根据需求修改逻辑),并捕获异常以确保进度条和界面更新: 
private  void  ExtractZipFile (string  zipFilePath, string  extractPath ) {     using  (ZipArchive archive = ZipFile.OpenRead(zipFilePath))     {         int  totalEntries = archive.Entries.Count;         int  extractedEntries = 0 ;         foreach  (ZipArchiveEntry entry in  archive.Entries)         {             try              {                 // 跳过 Updater.exe 文件                  if  (entry.FullName.Equals(CustConst.AppNmae, StringComparison.OrdinalIgnoreCase))                 {                     continue ;                 }                 string  destinationPath = Path.Combine(extractPath, entry.FullName);                 _ = Invoke(new  Action(() => textBoxInformation.Text = $"解压中... {entry.FullName} " ));                 if  (string .IsNullOrEmpty(entry.Name))                 {                     // Create directory                      _ = Directory.CreateDirectory(destinationPath);                 }                 else                  {                     // Ensure directory exists                      _ = Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));                     // Extract file                      entry.ExtractToFile(destinationPath, overwrite: true );                 }                 extractedEntries++;                 int  progress = (int )((double )extractedEntries / totalEntries * 100 );                 _ = Invoke(new  Action(() => progressBar.Value = progress));             }             catch  (Exception ex)             {                 _ = Invoke(new  Action(() => textBoxInformation.Text = $"解压失败:{entry.FullName} , 错误: {ex.Message} " ));                 continue ;             }         }     } }6、启动解压后的新程序 
在解压完成后,启动新版本的程序,并且关闭更新程序: 
private  void  Form1_Load (object  sender, EventArgs e ) {     // 省略部分代码...      string  programPath = Path.Combine(extractPath, programToLaunch);     if  (File.Exists(programPath))     {         Process.Start(programPath);         Application.Exit();     }     else      {         MessageBox.Show($"无法找到程序:{programPath} " );     } }检查更新逻辑 1、创建  UpdateChecker  类 
创建一个  UpdateChecker  类,对外提供引用,用于检查更新并启动更新程序: 
public  static  class  UpdateChecker  {     public  static  string  UpdateUrl { get ; set ; }     public  static  string  CurrentVersion { get ; set ; }     public  static  string  MainProgramRelativePath { get ; set ; }     public  static  void  CheckForUpdates ()     {         try          {             using  (HttpClient client = new  HttpClient())             {                 string  xmlContent = client.GetStringAsync(UpdateUrl).Result;                 XDocument xmlDoc = XDocument.Parse(xmlContent);                 var  latestVersion = xmlDoc.Root.Element("version" )?.Value;                 var  downloadUrl = xmlDoc.Root.Element("url" )?.Value;                 if  (!string .IsNullOrEmpty(latestVersion) && !string .IsNullOrEmpty(downloadUrl) && latestVersion != CurrentVersion)                 {                     // 获取当前程序名称                      string  currentProcessName = Process.GetCurrentProcess().ProcessName;                     // 启动更新程序并传递当前程序名称                      string  arguments = $"--url \"{downloadUrl} \" --launch \"{MainProgramRelativePath} \" --current \"{currentProcessName} \"" ;                     _ = Process.Start(CustConst.AppNmae, arguments);                     // 关闭当前主程序                      Application.Exit();                 }             }         }         catch  (Exception ex)         {             _ = MessageBox.Show($"检查更新失败:{ex.Message} " );         }     } }2、服务器配置XML 
服务器上存放一个XML文件配置当前最新版本、安装包下载地址等,假设服务器上的 XML 文件内容如下: 
<?xml version="1.0" encoding="utf-8"?> <update >      <version > 1.0.2</version >      <url > https://example.com/yourfile.zip</url > </update > 主程序调用更新检查 主程序可以通过定时器或者手动调用检查更新的逻辑,博主使用定时检查更新: 
internal  static  class  AutoUpdaterHelp  {     private  static  readonly  System.Timers.Timer timer;     static  AutoUpdaterHelp ()     {         UpdateChecker.CurrentVersion = "1.0.1" ;         UpdateChecker.UpdateUrl = ConfigurationManager.AppSettings["AutoUpdaterUrl" ].ToString();         UpdateChecker.MainProgramRelativePath = "Restart.bat" ;         timer = new  System.Timers.Timer         {             Interval = 10  * 1000 //2 * 60 * 1000          };         timer.Elapsed += delegate          {             UpdateChecker.CheckForUpdates();         };     }     public  static  void  Start ()     {         timer.Start();     }     public  static  void  Stop ()     {         timer.Stop();     } }思考:性能与安全考量 在实现自动化更新时,还应考虑性能和安全因素。例如,为了提高效率,可以添加断点续传功能;为了保证安全,应验证下载文件的完整性,例如使用SHA256校验和,这些博主就不做实现与讲解了,目前的功能已经完成了基本的自动更新逻辑 
总结 
自动化软件更新是现代软件开发不可或缺的一部分,它不仅能显著提升用户体验,还能减轻开发者的维护负担。 
通过上述C#代码示例,你可以快速搭建一个基本的自动化更新框架,进一步完善和定制以适应特定的应用场景。 
阅读原文:原文链接 
 该文章在 2025/5/6 15:21:10 编辑过