c# 如何實現自動更新程序
主要功能介紹
實現文件的自動更新。主要功能:
- 支持整包完全更新,即客戶端隻需輸入一個服務器地址,即可下載所有文件。
- 支持增量更新,即隻更新指定的某幾個文件。
- 支持自動更新程序的更新
更新界面如圖:
客戶端
main方法入口
/// <summary> /// 應用程序的主入口點。 /// </summary> [STAThread] static void Main() { //在主程序中 更新替換自動升級程序 //ReplaceAutoUpgrade(); bool isEnterMain = false; try { //設置默認更新地址,如果不設置,後面會從配置文件,或界面上進行設置 UpgradeHelper.Instance.DefaultUrl = "http://localhost:17580"; if (UpgradeHelper.Instance.Local_UpgradeModel != null) { UpgradeHelper.Instance.UpgradeUrl = UpgradeHelper.Instance.Local_UpgradeModel.UpgradeUrl; } if (UpgradeHelper.Instance.WillUpgrades.Count == 0 && UpgradeHelper.Instance.Local_UpgradeModel != null) { //沒有待更新,並且本地版本信息文件不為空,則直接啟動主程序 bool isSucced = UpgradeHelper.StartRunMain(UpgradeHelper.Instance.Local_UpgradeModel.RunMain); if (isSucced) { Application.Exit(); } else { //清理版本信息 以便重新檢測版本 UpgradeHelper.Instance.ClearUpgradeModel(); isEnterMain = true; } } else { isEnterMain = true; } } catch (Exception ex) { isEnterMain = true; MessageBox.Show("運行更新程序異常:\n" + ex.Message, "錯誤提示", MessageBoxButtons.OK, MessageBoxIcon.Error); } if (isEnterMain) { //進入更新主界面 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new FrmUpdate()); } }
主窗體代碼
public partial class FrmUpdate: Form { /// <summary> /// 構造函數 /// </summary> /// <param name="tempPath"></param> /// <param name="updateFiles"></param> public FrmUpdate() { InitializeComponent(); } /// <summary> /// 窗體加載事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void FrmUpdate_Load(object sender, EventArgs e) { try { //加載服務器地址 txtHostUrl.Text = UpgradeHelper.Instance.UpgradeUrl; BeginUpgrade(); } catch(Exception ex) { Output("初始化異常:" + ex.Message); } } /// <summary> /// 手動更新 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void butBegin_Click(object sender, EventArgs e) { try { if(string.IsNullOrWhiteSpace(txtHostUrl.Text)) { Output("請先輸入服務器地址!"); return; } UpgradeHelper.Instance.UpgradeUrl = txtHostUrl.Text.Trim(); //清理版本信息 以便重新檢測版本 UpgradeHelper.Instance.ClearUpgradeModel(); BeginUpgrade(); } catch(Exception ex) { Output("更新異常:" + ex.Message); } } private void BeginUpgrade() { try { if(string.IsNullOrWhiteSpace(UpgradeHelper.Instance.UpgradeUrl)) { return; } if(!(UpgradeHelper.Instance.UpgradeUrl.StartsWith("http://") || UpgradeHelper.Instance.UpgradeUrl.StartsWith("https://"))) { Output("錯誤的服務器地址,地址必須以http://或者https://開頭"); return; } //判斷是否有更新 if(UpgradeHelper.Instance.WillUpgrades.Count > 0 && UpgradeHelper.Instance.Server_UpgradeModel != null) { SetWinControl(false); //殺死主進程 UpgradeHelper.KillProcess(UpgradeHelper.Instance.Server_UpgradeModel.RunMain); RunUpgrade(); //啟動更新 } } catch(Exception ex) { Output("更新異常:" + ex.Message); } } /// <summary> /// 啟動更新 /// </summary> private void RunUpgrade() { //啟動更新 SetCaption(string.Format("共需更新文件{0}個,已更新0個。正在更新下列文件:", UpgradeHelper.Instance.WillUpgrades.Count)); Task.Factory.StartNew(() => { string curFile = ""; try { int idx = 0; foreach(KeyValuePair < string, string > item in UpgradeHelper.Instance.WillUpgrades) { curFile = item.Key; string filePath = string.Format("{0}\\{1}", Application.StartupPath, item.Key); if(item.Key.IndexOf(UpgradeHelper.Instance.Server_UpgradeModel.AutoUpgrade) >= 0) { //如果當前文件為更新主程序 filePath = string.Format("{0}\\AutoUpgradeTemp\\{1}", Application.StartupPath, item.Key); } string directory = Path.GetDirectoryName(filePath); if(!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } MyWebResquest.DownloadFile(UpgradeHelper.Instance.UpgradeUrl, item.Key, filePath); idx++; SetCaption(string.Format("共需更新文件{0}個,已更新{1}個。更新文件列表:", UpgradeHelper.Instance.WillUpgrades.Count, idx)); Output(string.Format("更新文件{0}完成", curFile)); } //保存版本文件 File.WriteAllText(UpgradeHelper.Instance.Local_UpgradeXmlPath, UpgradeHelper.Instance.Server_UpgradeXml); SetCaption(string.Format("更新完成,共更新文件{0}個", UpgradeHelper.Instance.WillUpgrades.Count)); Output(string.Format("更新完成,共更新文件{0}個", UpgradeHelper.Instance.WillUpgrades.Count)); //下載完成後處理 UpgradeHelper.StartRunMain(UpgradeHelper.Instance.Server_UpgradeModel.RunMain); //退出當前程序 ExitCurrent(); } catch(Exception ex) { Output(string.Format("更新文件{0}異常:{1}", curFile, ex.Message)); SetWinControl(true); } }); } /// <summary> /// 設置界面控件是否可用 /// </summary> /// <param name="enabled"></param> private void SetWinControl(bool enabled) { if(this.InvokeRequired) { Action < bool > d = new Action < bool > (SetWinControl); this.Invoke(d, enabled); } else { txtHostUrl.Enabled = enabled; butBegin.Enabled = enabled; } } /// <summary> /// 退出當前程序 /// </summary> private void ExitCurrent() { if(this.InvokeRequired) { Action d = new Action(ExitCurrent); this.Invoke(d); } else { Application.Exit(); } }# region 日志輸出 /// <summary> /// 設置跟蹤狀態 /// </summary> /// <param name="caption"></param> private void SetCaption(string caption) { if(this.lblCaption.InvokeRequired) { Action < string > d = new Action < string > (SetCaption); this.Invoke(d, caption); } else { this.lblCaption.Text = caption; } } /// <summary> /// 設置跟蹤狀態 /// </summary> /// <param name="caption"></param> private void Output(string log) { if(this.txtLog.InvokeRequired) { Action < string > d = new Action < string > (Output); this.Invoke(d, log); } else { txtLog.AppendText(string.Format("{0}:{1}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), log)); txtLog.ScrollToCaret(); } } private void ClearOutput() { if(this.txtLog.InvokeRequired) { Action d = new Action(ClearOutput); this.Invoke(d); } else { txtLog.Text = ""; } }# endregion private void FrmUpdate_FormClosing(object sender, FormClosingEventArgs e) { if(e.CloseReason == CloseReason.UserClosing) { if(MessageBox.Show("升級未完成,退出後將導致軟件無法正常使用,你確定要退出嗎?", "退出提示", MessageBoxButtons.YesNo) != System.Windows.Forms.DialogResult.Yes) { //取消"關閉窗口"事件 e.Cancel = true; } } } }
更新幫助類
/// <summary> /// 更新幫助類 /// </summary> public class UpgradeHelper { /// <summary> /// 默認服務器地址 /// 在配置文件中未找到地址時,使用此地址進行更新 /// </summary> public string DefaultUrl { get; set; } public string _upgradeUrl; /// <summary> /// 獲取或設置服務器地址 /// </summary> public string UpgradeUrl { get { if(string.IsNullOrWhiteSpace(_upgradeUrl)) { return DefaultUrl; } return _upgradeUrl; } set { _upgradeUrl = value; } } /// <summary> /// 本地配置文件路徑 /// </summary> public string Local_UpgradeXmlPath = Path.Combine(Application.StartupPath, "UpgradeList.xml"); private UpgradeModel _local_UpgradeModel; /// <summary> /// 本地版本信息 /// </summary> public UpgradeModel Local_UpgradeModel { get { try { if(_local_UpgradeModel == null) { if(File.Exists(Local_UpgradeXmlPath)) { _local_UpgradeModel = new UpgradeModel(); _local_UpgradeModel.LoadUpgrade(File.ReadAllText(Local_UpgradeXmlPath)); } } return _local_UpgradeModel; } catch(Exception ex) { throw new Exception(string.Format("獲取本地版本文件UpgradeList.xml異常:{0}", ex.Message)); } } } private UpgradeModel _server_UpgradeModel; /// <summary> /// 服務器版本信息 /// </summary> public UpgradeModel Server_UpgradeModel { get { try { if(_server_UpgradeModel == null && !string.IsNullOrWhiteSpace(UpgradeUrl)) { string resXml = MyWebResquest.GetUpgradeList(UpgradeUrl); if(!string.IsNullOrWhiteSpace(resXml)) { _server_UpgradeModel = new UpgradeModel(); _server_UpgradeModel.LoadUpgrade(resXml); _server_UpgradeXml = resXml; } } return _server_UpgradeModel; } catch(Exception ex) { throw new Exception(string.Format("獲取服務端版本文件UpgradeList.xml異常:{0}", ex.Message)); } } } private string _server_UpgradeXml; /// <summary> /// 服務端版本配置xml /// </summary> public string Server_UpgradeXml { get { return _server_UpgradeXml; } } private Dictionary < string, string > _willUpgrades; /// <summary> /// 待更新文件列表,如果為0,則表示不需要更新 /// </summary> public Dictionary < string, string > WillUpgrades { get { if(_willUpgrades == null) { _willUpgrades = new Dictionary < string, string > (); //如果服務器端未獲取到版本信息 則不更新 if(Server_UpgradeModel != null) { if(Local_UpgradeModel == null) //本地版本信息為空 全部更新 { _willUpgrades = Server_UpgradeModel.DictFiles; } else { //對比需要更新的文件 foreach(var item in Server_UpgradeModel.DictFiles) { //如果找到 if(Local_UpgradeModel.DictFiles.ContainsKey(item.Key)) { //如果版本不匹配 if(Local_UpgradeModel.DictFiles[item.Key] != item.Value) { _willUpgrades.Add(item.Key, item.Value); } } else { //沒有找到 _willUpgrades.Add(item.Key, item.Value); } } } } } return _willUpgrades; } } /// <summary> /// 清空版本信息 /// </summary> public void ClearUpgradeModel() { if(File.Exists(Local_UpgradeXmlPath)) { try { string xmlStr = File.ReadAllText(Local_UpgradeXmlPath); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xmlStr); XmlNode node = xmlDoc.SelectSingleNode("Upgrade/Files"); if(node != null && node.ChildNodes.Count > 0) { node.RemoveAll(); } File.WriteAllText(UpgradeHelper.Instance.Local_UpgradeXmlPath, xmlDoc.InnerXml); } catch(Exception) {} } _local_UpgradeModel = null; _server_UpgradeModel = null; _willUpgrades = null; }# region 單例對象 private static UpgradeHelper _instance; /// <summary> /// 單例對象 /// </summary> public static UpgradeHelper Instance { get { if(_instance == null) { _instance = new UpgradeHelper(); //初始化本地配置文件,以及服務器地址 if(_instance.Local_UpgradeModel != null) { _instance.UpgradeUrl = _instance.Local_UpgradeModel.UpgradeUrl; } } return _instance; } }# endregion# region 靜態方法 /// <summary> /// 啟動主程序 /// </summary> /// <param name="fileName"></param> public static bool StartRunMain(string fileName) { string fullPath = fileName; try { Process process = GetProcess(fileName); if(process != null) //以及存在運行中的主進程 { return true; } fullPath = string.Format("{0}\\{1}", Application.StartupPath, fileName); ProcessStartInfo main = new ProcessStartInfo(fullPath); Process.Start(fullPath); return true; } catch(Exception ex) { MessageBox.Show(string.Format("主程序{0}調用失敗:\n{1}", fullPath, ex.Message), "錯誤提示", MessageBoxButtons.OK, MessageBoxIcon.Error); } return false; } /// <summary> /// 殺死進程 /// </summary> /// <param name="process"></param> public static void KillProcess(string processName) { if(string.IsNullOrWhiteSpace(processName)) return; processName = processName.ToLower(); processName = processName.Replace(".exe", ""); //殺死主進程 Process[] processes = Process.GetProcesses(); foreach(Process process in processes) { if(!string.IsNullOrWhiteSpace(process.ProcessName)) { if(process.ProcessName.ToLower() == processName) { process.Kill(); } } } } /// <summary> /// 獲取進程 /// </summary> /// <param name="pName"></param> /// <returns></returns> public static Process GetProcess(string pName) { if(string.IsNullOrWhiteSpace(pName)) return null; pName = pName.ToLower(); pName = pName.Replace(".exe", ""); //殺死主進程 Process[] processes = Process.GetProcesses(); foreach(Process process in processes) { if(!string.IsNullOrWhiteSpace(process.ProcessName)) { if(process.ProcessName.ToLower() == pName) { return process; } } } return null; }# endregion }
版本xml文件解析
public class UpgradeModel { /// <summary> /// 初始化對象 /// </summary> /// <param name="xml"></param> public void LoadUpgrade(string xml) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xml); //讀取UpgradeUrl XmlNode node = xmlDoc.SelectSingleNode("//UpgradeUrl"); if(node != null) { this.UpgradeUrl = node.InnerText; } //讀取RunMain node = xmlDoc.SelectSingleNode("//RunMain"); if(node != null) { this.RunMain = node.InnerText; } //讀取RunMain node = xmlDoc.SelectSingleNode("//AutoUpgrade"); if(node != null) { this.AutoUpgrade = node.InnerText; } //讀取Files node = xmlDoc.SelectSingleNode("Upgrade/Files"); this.DictFiles = new Dictionary < string, string > (); if(node != null && node.ChildNodes.Count > 0) { foreach(XmlNode item in node.ChildNodes) { if(item.Name != "#comment") { string name = GetNodeAttrVal(item, "Name"); string version = GetNodeAttrVal(item, "Version"); if(!this.DictFiles.ContainsKey(name)) { this.DictFiles.Add(name, version); } } } } } private string GetNodeAttrVal(XmlNode node, string attr) { if(node != null && node.Attributes != null && node.Attributes[attr] != null) { string val = node.Attributes[attr].Value; if(!string.IsNullOrWhiteSpace(val)) { return val.Trim(); } return val; } return string.Empty; } /// <summary> /// 服務器地址 /// </summary> public string UpgradeUrl { get; set; } /// <summary> /// 更新完成後運行的主程序名稱 /// </summary> public string RunMain { get; set; } /// <summary> /// 更新程序名稱 /// </summary> public string AutoUpgrade { get; set; } /// <summary> /// 文件列表 /// string 文件名 /// string 版本號 /// </summary> public Dictionary < string, string > DictFiles { get; set; } }
服務端
服務端主Xml版本文件,包含所有的項目文件,客戶端根據每個文件的版本號進行判斷是否需要更新。如果需隻更新某幾個文件,則將對應文件的版本號更改隻更高的版本號即可
版本xml文件
<?xml version="1.0" encoding="utf-8" ?> <Upgrade> <!--服務器地址--> <UpgradeUrl>http://localhost:17580</UpgradeUrl> <!--更新完成後運行的主程序名稱--> <RunMain>ClientMain.exe</RunMain> <!--更新程序名稱--> <AutoUpgrade>AutoUpgrade.exe</AutoUpgrade> <Files> <!--更新文件列表,以Version為標志,當Version改變時,客戶端啟動會自動更新。子路徑格式:\image\index.jpg--> <File Version="01" Name="\image\index.jpg" /> <File Version="01" Name="ClientMain.exe" /> <File Version="01" Name="AutoUpgrade.exe" /> </Files> </Upgrade>
服務端主要提供連個可以通過Http的get或post訪問的路徑。一個用於獲取版本Xml文件內容,一個用於下載指定文件的路徑。以下代碼示例通過asp.net mvc進行實現。大傢可以根據自己技術方式參照實現。
自動升級服務Controller
/// <summary> /// 自動升級服務 /// </summary> public class UpgradeController: Controller { // // GET: /Upgrade/ /// <summary> /// 獲取更新文件列表 /// </summary> /// <returns></returns> public object UpgradeList() { string cacheKey = "Upgrade_UpgradeList.xml"; string resStr = CommonLibrary.CacheClass.GetCache < string > (cacheKey); if(string.IsNullOrWhiteSpace(resStr)) { string fileName = Server.MapPath(@"~\App_Data\UpgradeList.xml"); if(System.IO.File.Exists(fileName)) { resStr = System.IO.File.ReadAllText(fileName); CommonLibrary.CacheClass.SetCacheMins(cacheKey, resStr, 1); } } return resStr; } /// <summary> /// 生成更新文件 /// </summary> /// <returns></returns> public object Create() { UpgradeFileManager.CreateFiles(Server.MapPath("/App_Data")); return "ok"; } /// <summary> /// 下載文件 /// </summary> /// <param name="fileName"></param> /// <returns></returns> public object DownloadFile() { string fileName = PageRequest.GetString("fileName"); fileName = Server.MapPath(string.Format(@"~\App_Data\{0}", fileName)); return File(fileName, "application/octet-stream"); } /// <summary> /// 異常處理 /// </summary> /// <param name="filterContext"></param> protected override void OnException(ExceptionContext filterContext) { filterContext.HttpContext.Response.StatusCode = 400; filterContext.Result = Content(filterContext.Exception.GetBaseException().Message); filterContext.ExceptionHandled = true; } }
版本文件自動生成幫助類
/// <summary> /// 此類主要作用,對於項目文件非常多,自己手動編輯很麻煩,可以采用此方法,指定目錄自動生成初始化的版本文件 /// </summary> public class UpgradeFileManager { /// <summary> /// 創建版本文件 /// </summary> /// <param name="path"></param> public static void CreateFiles(string path) { List < string > dirList = new List < string > (); GetAllDirt(path, dirList); //獲取所有目錄 dirList.Add(path); System.Text.StringBuilder xml = new System.Text.StringBuilder(); xml.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); xml.AppendLine(" <Files>"); foreach(var diry in dirList) { string[] files = Directory.GetFiles(diry); foreach(string filePath in files) { FileInfo info = new FileInfo(filePath); string name = filePath.Replace(path, ""); if(info.Directory.FullName == path) { name = name.Remove(0, 1); } xml.AppendLine(string.Format(" <File Version=\"1\" Name=\"{0}\" />", name)); } } xml.AppendLine("</Files>"); using(StreamWriter sw = new StreamWriter(Path.Combine(path, "UpgradeList_Temp.xml"))) { sw.Write(xml); sw.Close(); } } /// <summary> /// 獲取所有子目錄 /// </summary> /// <param name="curDir"></param> /// <param name="list"></param> private static void GetAllDirt(string curDir, List < string > list) { string[] dirs = Directory.GetDirectories(curDir); if(dirs.Length > 0) { foreach(string item in dirs) { list.Add(item); GetAllDirt(item, list); } } } }
結語
源代碼托管於GitHub,供大夥學習參考,項目地址:https://github.com/keguoquan/AutoUpgrade。感興趣或覺得不錯的望賞個star,不勝感激!
若能順手點個贊,更加感謝!
以上就是用c# 自動更新程序的詳細內容,更多關於c# 自動更新程序的資料請關註WalkonNet其它相關文章!