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# 自动更新程序的资料请关注编程宝库其它相关文章!
0. 前言在之前的几篇内容中,我们了解了如何通过ADO.NET 访问数据库,如何修改、新增数据。如何通过DataSet和DataAdapter获取数据,我们将在这一篇试试自己实现一个简单的ORM框架 ...