首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# WinForms 自動更新機制實作:三專案架構完整教學

     編輯:關於C語言

前言:桌面應用程式更新的挑戰

與 Web 應用程式不同,WinForms 部署在客戶端,更新機制直接影響維運成本。本文提出「啟動器 + 主程式 + 上傳工具」三專案架構,透過資料庫版本比對、背景下載、參數傳遞登入資訊,實現零人工干預的自動更新流程。完整範例包含 Access/SQL Server 双資料庫支援、註冊表版本記錄、多執行緒非阻塞 UI 等實戰技巧。

一、整體架構設計

三大角色

角色專案名稱職責部署對象
啟動器/登入器Login.exe版本檢查、下載更新、驗證帳密、啟動主程式所有用戶(桌面捷徑)
主程式MainPro.exe核心業務邏輯,僅接受參數啟動用戶(由 Login 啟動)
上傳工具UpLoad.exe開發端:將新版本主程式上傳至資料庫開發/維運人員

資料庫結構

-- 操作員表
CREATE TABLE Operators (
    SeqNo    AUTOINCREMENT PRIMARY KEY,
    Name     TEXT(50) NOT NULL,
    Password TEXT(50) NOT NULL
);

-- 版本表
CREATE TABLE Versions (
    SeqNo       AUTOINCREMENT PRIMARY KEY,
    VersionNo   LONG NOT NULL,          -- 版本號(整數遞增)
    FileName    TEXT(100) NOT NULL,     -- MainPro.exe
    FileContent OLEOBJECT (或 IMAGE)    -- 執行檔二進位內容
);

二、主程式:參數化啟動與安全防護

Program.cs 進入點修改

static class Program
{
    public static string UserName { get; private set; }
    public static string Password { get; private set; }

    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        if (args.Length == 2)
        {
            UserName = args[0];
            Password = args[1];
            Application.Run(new MainForm());
        }
        else
        {
            MessageBox.Show("禁止直接執行,請透過登入器啟動。", "安全性攔截", 
                MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
    }
}

關鍵設計點

  • 靜態屬性保存登入資訊,表單透過 Program.UserName 存取
  • 無參數啟動直接拒絕,防止繞過登入驗證
  • 可擴充參數傳遞(Token、權限碼、伺服器位址等)

三、上傳工具:版本管理與二進位儲存

核心上傳邏輯

private void btnOK_Click(object sender, EventArgs e)
{
    // 1. 驗證版本號格式
    if (!decimal.TryParse(txtVersion.Text, out _))
    { MessageBox.Show("版本號必須為數字"); return; }

    // 2. 檢查檔案存在
    if (!File.Exists(txtFileName.Text))
    { MessageBox.Show("檔案不存在"); return; }

    string connStr = GetConnectionString();
    using (var conn = new OleDbConnection(connStr))
    using (var cmd = new OleDbCommand("SELECT * FROM Versions", conn))
    using (var adapter = new OleDbDataAdapter(cmd))
    using (var builder = new OleDbCommandBuilder(adapter))
    {
        conn.Open();
        var ds = new DataSet();
        adapter.Fill(ds, "Versions");

        // 3. 取得或建立版本記錄
        DataRow row;
        if (ds.Tables["Versions"].Rows.Count == 0)
        {
            row = ds.Tables["Versions"].NewRow();
            row["SeqNo"] = 1;
            ds.Tables["Versions"].Rows.Add(row);
        }
        else row = ds.Tables["Versions"].Rows[0];

        // 4. 更新欄位
        row["FileName"] = Path.GetFileName(txtFileName.Text);
        row["VersionNo"] = txtVersion.Text;

        // 5. 讀取二進位內容
        byte[] fileData = File.ReadAllBytes(txtFileName.Text);
        row["FileContent"] = fileData;

        // 6. 寫回資料庫
        adapter.Update(ds, "Versions");
        MessageBox.Show("上傳成功!");
    }
}

安全性考量

  • 上傳工具絕不隨主程式部署給用戶
  • 資料庫連線字串可加密儲存或透過 Web API 間接上傳
  • 版本號採用整數遞增(1, 2, 3...),避免字串比較陷阱

四、登入器:自動更新核心邏輯

非同步初始化流程

public partial class LoginForm : Form
{
    private DataSet _operatorDs;
    private Thread _initThread;

    private void LoginForm_Load(object sender, EventArgs e)
    {
        btnOK.Enabled = false; // 更新完成前禁用登入
        _initThread = new Thread(InitializeAsync);
        _initThread.Start();
    }

    private void InitializeAsync()
    {
        try
        {
            LoadOperators();      // 載入帳號下拉選單
            CheckAndUpdateApp();  // 檢查並下載更新
        }
        catch (Exception ex)
        {
            Invoke(() => MessageBox.Show($"初始化失敗:{ex.Message}"));
        }
        finally
        {
            Invoke(() => btnOK.Enabled = true); // 允許登入
        }
    }

    private void LoadOperators()
    {
        // ... 從資料庫讀取操作員填入 combUserName ...
    }

    private void CheckAndUpdateApp()
    {
        string connStr = GetConnectionString();
        using (var conn = new OleDbConnection(connStr))
        {
            conn.Open();
            // 只查版本與檔名,不載入二進位內容
            using (var cmd = new OleDbCommand("SELECT FileName, VersionNo FROM Versions", conn))
            using (var reader = cmd.ExecuteReader())
            {
                if (reader.Read())
                {
                    string fileName = reader["FileName"].ToString();
                    int serverVersion = Convert.ToInt32(reader["VersionNo"]);
                    int localVersion = GetLocalVersion(fileName); // 從註冊表讀取

                    if (serverVersion > localVersion)
                    {
                        DownloadAndReplace(fileName, serverVersion, conn);
                    }
                }
            }
        }
    }

    private void DownloadAndReplace(string fileName, int newVersion, OleDbConnection conn)
    {
        // 重新查詢完整記錄含二進位資料
        using (var cmd = new OleDbCommand("SELECT FileContent FROM Versions WHERE FileName=?", conn))
        {
            cmd.Parameters.AddWithValue("?", fileName);
            var content = cmd.ExecuteScalar() as byte[];
            
            if (content != null)
            {
                string targetPath = Path.Combine(Application.StartupPath, fileName);
                File.WriteAllBytes(targetPath, content);
                SetLocalVersion(fileName, newVersion); // 寫入註冊表
            }
        }
    }

    // 註冊表版本記錄
    private int GetLocalVersion(string fileName)
    {
        using (var key = Registry.LocalMachine.CreateSubKey($@"Software\MyCompany\AutoUpdate"))
        {
            return int.TryParse(key?.GetValue(fileName)?.ToString(), out int v) ? v : 0;
        }
    }
    private void SetLocalVersion(string fileName, int version)
    {
        using (var key = Registry.LocalMachine.CreateSubKey($@"Software\MyCompany\AutoUpdate", true))
        {
            key.SetValue(fileName, version);
        }
    }
}

登入驗證與啟動主程式

private void btnOK_Click(object sender, EventArgs e)
{
    if (combUserName.SelectedIndex < 0) return;

    // 查找對應帳號
    DataRow userRow = _operatorDs.Tables["Operators"].AsEnumerable()
        .FirstOrDefault(r => r["Name"].ToString() == combUserName.Text);
    
    if (userRow == null) return;

    string correctPwd = userRow["Password"].ToString();
    if (txtPassword.Text == correctPwd)
    {
        string mainExe = Path.Combine(Application.StartupPath, "MainPro.exe");
        string args = $"{combUserName.Text} {txtPassword.Text}";
        
        Process.Start(mainExe, args);
        this.Close();
    }
    else
    {
        MessageBox.Show("密碼錯誤");
        txtPassword.Focus();
        txtPassword.SelectAll();
    }
}

五、進階擴充與最佳實踐

1. 共用工具類別

namespace CommonTools
{
    public static class AppConfig
    {
        public const string MainExeName = "MainPro.exe";
        public const string RegistryPath = @"Software\MyCompany\AutoUpdate";
        
        #if NETWORK_VERSION
        public static string ConnStr => "Server=...;Database=...;User=...;Password=...";
        #else
        public static string ConnStr => $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={Application.StartupPath}\\db.mdb";
        #endif
    }

    public static class RegistryHelper
    {
        public static string Read(string keyName) { /* ... */ }
        public static void Write(string keyName, string value) { /* ... */ }
    }
}

三個專案以「連結檔案」方式共用 AppConfig.cs,修改一處處處生效。

2. 網路版遷移策略

  • 條件編譯 #define NETWORK_VERSION 切換 Access/SQL Server
  • 連線字串、Command/Reader 型別透過 #if 區隔,變數名保持一致
  • 部署時改用 HTTPS Web API 取代直連資料庫,提升安全性

3. 登入器自身更新

由於執行中的程式無法覆寫自己,採用「主程式關閉時更新登入器」策略:

// MainForm.cs
protected override void OnFormClosing(FormClosingEventArgs e)
{
    base.OnFormClosing(e);
    // 檢查登入器版本並下載覆寫 Login.exe
    Updater.CheckAndUpdateLauncher();
}

4. 使用者體驗優化

  • 「記住密碼」:加密儲存至註冊表/設定檔,下次自動填入
  • 進度條顯示下載進度(需 WebClient.DownloadProgressChanged 或 HttpClient)
  • 強制更新模式:關鍵安全性修補時禁止跳過更新
  • 差分更新:僅下載二進位差異(需額外演算法支援)

六、部署與測試清單

  1. 建立共用輸出目錄(如 D:\Output),三專案均設定 Output Path
  2. 將 db.mdb 複製至輸出目錄
  3. 先執行 UpLoad.exe 上傳初始版本 MainPro.exe (版本 1)
  4. 建立安裝專案,僅包含 Login.exe、db.mdb、相關 DLL
  5. 桌面建立 Login.exe 捷徑作為用戶進入點
  6. 測試流程:修改主程式 → 上傳版本 2 → 執行登入器 → 驗證自動更新 → 登入成功

總結

本方案以「登入器掌控更新、主程式專注業務、上傳工具隔離部署」為核心,解決了 WinForms 自動更新的經典難題。關鍵技術點包括:資料庫二進位儲存、註冊表版本追蹤、多執行緒非阻塞 UI、參數化安全啟動、共用設定管理。團隊可根據專案規模,將資料庫直連升級為 Web API、引入差分更新、加入數位簽章驗證等進階功能,構建企業級桌面應用交付管線。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved