.NET C# 程式自動更新組件

来源:https://www.cnblogs.com/Bob-luo/p/18231510
-Advertisement-
Play Games

引言 本來博主想偷懶使用AutoUpdater.NET組件,但由於博主項目有些特殊性和它的功能過於多,於是博主自己實現一個輕量級獨立自動更新組件,可稍作修改集成到大家自己項目中,比如:WPF/Winform/Windows服務。大致思路:發現更新後,從網路上下載更新包併進行解壓,同時在 WinFor ...


引言

本來博主想偷懶使用AutoUpdater.NET組件,但由於博主項目有些特殊性和它的功能過於多,於是博主自己實現一個輕量級獨立自動更新組件,可稍作修改集成到大家自己項目中,比如:WPF/Winform/Windows服務。大致思路:發現更新後,從網路上下載更新包併進行解壓,同時在 WinForms 應用程式中顯示下載和解壓進度條,並重啟程式。以提供更好的用戶體驗。

1. 系統架構概覽

自動化軟體更新系統主要包括以下幾個核心部分:

  • 版本檢查:定期或在啟動時檢查伺服器上的最新版本。
  • 下載更新:如果發現新版本,則從伺服器下載更新包。
  • 解壓縮與安裝:解壓下載的更新包,替換舊文件。
  • 重啟應用:更新完畢後,重啟應用以載入新版本。

組件實現細節

獨立更新程式邏輯:

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#代碼示例,你可以快速搭建一個基本的自動化更新框架,進一步完善和定製以適應特定的應用場景。


本文提供了構建自動化軟體更新系統的C#代碼實現,希望對開發者們有所幫助。如果你有任何疑問或建議,歡迎留言討論!


 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這一期的話題有點深奧,不過按照老周一向的作風,儘量講一些人鬼都能懂的知識。 咱們先來整個小活開開胃,這個小活其實老周在 N 年前寫過水文的,常閱讀老周水文的伙伴可能還記得。通常,咱們按照正常思路構建的應用程式,第一個啟動的線程為主線程,而且還是 UI 線程(當然,WPF 預設會創建輔助線程。這都是運 ...
  • 1,背景 工作流思想在上世紀60年代就有人提出過;70年代就有人開始嘗試,但是由於當時許多的限制,工作流一直沒有成功的被實現;80年代才出現第一批成功的工作流系統;90年代工作流技術走向了第一個發展高峰期;90年代後至今工作流出現了很多版本,但是主旨還是不變的,為了使我們的工作變得更加高效。 通俗點 ...
  • 一:背景 1. 講故事 今天分享的dump是訓練營里一位學員的,從一個啥也不會到現在分析的有模有樣,真的是看他成長起來的,調試技術學會了就是真真實實自己的,話不多說,上windbg說話。 二:WinDbg 分析 1. 為什麼會卡死 這位學員是從事工控大類下的視覺自動化,也是目前.NET的主戰場,這個 ...
  • 字元串是日常編碼中最常用的引用類型了,可能沒有之一,加上字元串的不可變性、駐留性,很容易產生性能問題,因此必須全面瞭解一下。 ...
  • 安裝1.0.10以及以上版本的 Wesky.Net.OpenTools 包 包內,該功能的核心代碼如下: 自定義屬性: 實體類JSON模式生成器: 使用方式:引用上面的1.0.10版本或以上的包。如果實體類有特殊需求,例如映射為其他名稱,可以用OpenJson屬性來實現。實體類對象案例如下: 上面實 ...
  • 上一次我們講了 OpenTelemetry Logs。今天繼續來說說 OpenTelemetry Traces。 在今天的微服務和雲原生環境中,理解和監控系統的行為變得越來越重要。在當下我們實現一個功能可能需要調用了 N 個方法,涉及到 N 個服務。方法之間的調用如蜘蛛網一樣。分散式追蹤這個時候就至 ...
  • 通常我們在做一些數據過濾的操作的時候,經常需要做一些判斷再進行是否要對其進行條件過濾。 普通做法 最原始的做法我們是先通過If()判斷是否需要進行數據過濾,然後再對數據源使用Where來過濾數據。 示例如下: if(!string.IsNullOrWhiteSpace(str)) { query = ...
  • Kong和Konga攻堅一、安裝Kong 參考網址:在碼頭工人上安裝孔網關 - v3.3.x |孔文檔 (konghq.com)1、創建自定義 Docker 網路以允許容器發現和 相互溝通: docker network create kong-net您可以根據需要將此網路命名為任何名稱。我們使用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...