主要功能介绍
实现文件的自动更新。主要功能:
- 支持整包完全更新,即客户端只需输入一个服务器地址,即可下载所有文件。
- 支持增量更新,即只更新指定的某几个文件。
- 支持自动更新程序的更新
更新界面如图:
客户端

/// <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()); } }

1 public partial class FrmUpdate : Form 2 { 3 /// <summary> 4 /// 构造函数 5 /// </summary> 6 /// <param name="tempPath"></param> 7 /// <param name="updateFiles"></param> 8 public FrmUpdate() 9 { 10 InitializeComponent(); 11 } 12 13 /// <summary> 14 /// 窗体加载事件 15 /// </summary> 16 /// <param name="sender"></param> 17 /// <param name="e"></param> 18 private void FrmUpdate_Load(object sender, EventArgs e) 19 { 20 try 21 { 22 //加载服务器地址 23 txtHostUrl.Text = UpgradeHelper.Instance.UpgradeUrl; 24 BeginUpgrade(); 25 } 26 catch (Exception ex) 27 { 28 Output("初始化异常:" + ex.Message); 29 } 30 } 31 32 /// <summary> 33 /// 手动更新 34 /// </summary> 35 /// <param name="sender"></param> 36 /// <param name="e"></param> 37 private void butBegin_Click(object sender, EventArgs e) 38 { 39 try 40 { 41 if (string.IsNullOrWhiteSpace(txtHostUrl.Text)) 42 { 43 Output("请先输入服务器地址!"); 44 return; 45 } 46 UpgradeHelper.Instance.UpgradeUrl = txtHostUrl.Text.Trim(); 47 //清理版本信息 以便重新检测版本 48 UpgradeHelper.Instance.ClearUpgradeModel(); 49 BeginUpgrade(); 50 } 51 catch (Exception ex) 52 { 53 Output("更新异常:" + ex.Message); 54 } 55 } 56 57 private void BeginUpgrade() 58 { 59 try 60 { 61 if (string.IsNullOrWhiteSpace(UpgradeHelper.Instance.UpgradeUrl)) 62 { 63 return; 64 } 65 if (!(UpgradeHelper.Instance.UpgradeUrl.StartsWith("http://") || UpgradeHelper.Instance.UpgradeUrl.StartsWith("https://"))) 66 { 67 Output("错误的服务器地址,地址必须以http://或者https://开头"); 68 return; 69 } 70 //判断是否有更新 71 if (UpgradeHelper.Instance.WillUpgrades.Count > 0 && UpgradeHelper.Instance.Server_UpgradeModel != null) 72 { 73 SetWinControl(false); 74 //杀死主进程 75 UpgradeHelper.KillProcess(UpgradeHelper.Instance.Server_UpgradeModel.RunMain); 76 RunUpgrade();//启动更新 77 } 78 } 79 catch (Exception ex) 80 { 81 Output("更新异常:" + ex.Message); 82 } 83 } 84 85 /// <summary> 86 /// 启动更新 87 /// </summary> 88 private void RunUpgrade() 89 { 90 //启动更新 91 SetCaption(string.Format("共需更新文件{0}个,已更新0个。正在更新下列文件:", UpgradeHelper.Instance.WillUpgrades.Count)); 92 Task.Factory.StartNew(() => 93 { 94 string curFile = ""; 95 try 96 { 97 int idx = 0; 98 foreach (KeyValuePair<string, string> item in UpgradeHelper.Instance.WillUpgrades) 99 { 100 curFile = item.Key; 101 string filePath = string.Format("{0}\\{1}", Application.StartupPath, item.Key); 102 if (item.Key.IndexOf(UpgradeHelper.Instance.Server_UpgradeModel.AutoUpgrade) >= 0) 103 { 104 //如果当前文件为更新主程序 105 filePath = string.Format("{0}\\AutoUpgradeTemp\\{1}", Application.StartupPath, item.Key); 106 } 107 string directory = Path.GetDirectoryName(filePath); 108 if (!Directory.Exists(directory)) 109 { 110 Directory.CreateDirectory(directory); 111 } 112 MyWebResquest.DownloadFile(UpgradeHelper.Instance.UpgradeUrl, item.Key, filePath); 113 idx++; 114 SetCaption(string.Format("共需更新文件{0}个,已更新{1}个。更新文件列表:", UpgradeHelper.Instance.WillUpgrades.Count, idx)); 115 Output(string.Format("更新文件{0}完成", curFile)); 116 } 117 //保存版本文件 118 File.WriteAllText(UpgradeHelper.Instance.Local_UpgradeXmlPath, UpgradeHelper.Instance.Server_UpgradeXml); 119 120 SetCaption(string.Format("更新完成,共更新文件{0}个", UpgradeHelper.Instance.WillUpgrades.Count)); 121 Output(string.Format("更新完成,共更新文件{0}个", UpgradeHelper.Instance.WillUpgrades.Count)); 122 123 //下载完成后处理 124 UpgradeHelper.StartRunMain(UpgradeHelper.Instance.Server_UpgradeModel.RunMain); 125 126 //退出当前程序 127 ExitCurrent(); 128 } 129 catch (Exception ex) 130 { 131 Output(string.Format("更新文件{0}异常:{1}", curFile, ex.Message)); 132 SetWinControl(true); 133 } 134 }); 135 } 136 137 /// <summary> 138 /// 设置界面控件是否可用 139 /// </summary> 140 /// <param name="enabled"></param> 141 private void SetWinControl(bool enabled) 142 { 143 if (this.InvokeRequired) 144 { 145 Action<bool> d = new Action<bool>(SetWinControl); 146 this.Invoke(d, enabled); 147 } 148 else 149 { 150 txtHostUrl.Enabled = enabled; 151 butBegin.Enabled = enabled; 152 } 153 } 154 155 /// <summary> 156 /// 退出当前程序 157 /// </summary> 158 private void ExitCurrent() 159 { 160 if (this.InvokeRequired) 161 { 162 Action d = new Action(ExitCurrent); 163 this.Invoke(d); 164 } 165 else 166 { 167 Application.Exit(); 168 } 169 } 170 171 #region 日志输出 172 173 /// <summary> 174 /// 设置跟踪状态 175 /// </summary> 176 /// <param name="caption"></param> 177 private void SetCaption(string caption) 178 { 179 if (this.lblCaption.InvokeRequired) 180 { 181 Action<string> d = new Action<string>(SetCaption); 182 this.Invoke(d, caption); 183 } 184 else 185 { 186 this.lblCaption.Text = caption; 187 } 188 } 189 190 /// <summary> 191 /// 设置跟踪状态 192 /// </summary> 193 /// <param name="caption"></param> 194 private void Output(string log) 195 { 196 if (this.txtLog.InvokeRequired) 197 { 198 Action<string> d = new Action<string>(Output); 199 this.Invoke(d, log); 200 } 201 else 202 { 203 txtLog.AppendText(string.Format("{0}:{1}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), log)); 204 txtLog.ScrollToCaret(); 205 } 206 } 207 208 private void ClearOutput() 209 { 210 if (this.txtLog.InvokeRequired) 211 { 212 Action d = new Action(ClearOutput); 213 this.Invoke(d); 214 } 215 else 216 { 217 txtLog.Text = ""; 218 } 219 } 220 221 #endregion 222 223 private void FrmUpdate_FormClosing(object sender, FormClosingEventArgs e) 224 { 225 if (e.CloseReason == CloseReason.UserClosing) 226 { 227 if (MessageBox.Show("升级未完成,退出后将导致软件无法正常使用,你确定要退出吗?", "退出提示", MessageBoxButtons.YesNo) != System.Windows.Forms.DialogResult.Yes) 228 { 229 //取消"关闭窗口"事件 230 e.Cancel = true; 231 } 232 } 233 } 234 }

1 /// <summary> 2 /// 更新帮助类 3 /// </summary> 4 public class UpgradeHelper 5 { 6 /// <summary> 7 /// 默认服务器地址 8 /// 在配置文件中未找到地址时,使用此地址进行更新 9 /// </summary> 10 public string DefaultUrl { get; set; } 11 12 public string _upgradeUrl; 13 /// <summary> 14 /// 获取或设置服务器地址 15 /// </summary> 16 public string UpgradeUrl 17 { 18 get 19 { 20 if (string.IsNullOrWhiteSpace(_upgradeUrl)) 21 { 22 return DefaultUrl; 23 } 24 return _upgradeUrl; 25 } 26 set 27 { 28 _upgradeUrl = value; 29 } 30 } 31 32 /// <summary> 33 /// 本地配置文件路径 34 /// </summary> 35 public string Local_UpgradeXmlPath = Path.Combine(Application.StartupPath, "UpgradeList.xml"); 36 37 private UpgradeModel _local_UpgradeModel; 38 /// <summary> 39 /// 本地版本信息 40 /// </summary> 41 public UpgradeModel Local_UpgradeModel 42 { 43 get 44 { 45 try 46 { 47 if (_local_UpgradeModel == null) 48 { 49 if (File.Exists(Local_UpgradeXmlPath)) 50 { 51 _local_UpgradeModel = new UpgradeModel(); 52 _local_UpgradeModel.LoadUpgrade(File.ReadAllText(Local_UpgradeXmlPath)); 53 } 54 } 55 return _local_UpgradeModel; 56 } 57 catch (Exception ex) 58 { 59 throw new Exception(string.Format("获取本地版本文件UpgradeList.xml异常:{0}", ex.Message)); 60 } 61 } 62 } 63 64 private UpgradeModel _server_UpgradeModel; 65 /// <summary> 66 /// 服务器版本信息 67 /// </summary> 68 public UpgradeModel Server_UpgradeModel 69 { 70 get 71 { 72 try 73 { 74 if (_server_UpgradeModel == null && !string.IsNullOrWhiteSpace(UpgradeUrl)) 75 { 76 string resXml = MyWebResquest.GetUpgradeList(UpgradeUrl); 77 if (!string.IsNullOrWhiteSpace(resXml)) 78 { 79 _server_UpgradeModel = new UpgradeModel(); 80 _server_UpgradeModel.LoadUpgrade(resXml); 81 _server_UpgradeXml = resXml; 82 } 83 } 84 return _server_UpgradeModel; 85 } 86 catch (Exception ex) 87 { 88 throw new Exception(string.Format("获取服务端版本文件UpgradeList.xml异常:{0}", ex.Message)); 89 } 90 } 91 } 92 93 private string _server_UpgradeXml; 94 /// <summary> 95 /// 服务端版本配置xml 96 /// </summary> 97 public string Server_UpgradeXml 98 { 99 get 100 { 101 return _server_UpgradeXml; 102 } 103 } 104 105 private Dictionary<string, string> _willUpgrades; 106 /// <summary> 107 /// 待更新文件列表,如果为0,则表示不需要更新 108 /// </summary> 109 public Dictionary<string, string> WillUpgrades 110 { 111 get 112 { 113 if (_willUpgrades == null) 114 { 115 _willUpgrades = new Dictionary<string, string>(); 116 //如果服务器端未获取到版本信息 则不更新 117 if (Server_UpgradeModel != null) 118 { 119 if (Local_UpgradeModel == null)//本地版本信息为空 全部更新 120 { 121 _willUpgrades = Server_UpgradeModel.DictFiles; 122 } 123 else 124 { 125 //对比需要更新的文件 126 foreach (var item in Server_UpgradeModel.DictFiles) 127 { 128 //如果找到 129 if (Local_UpgradeModel.DictFiles.ContainsKey(item.Key)) 130 { 131 //如果版本不匹配 132 if (Local_UpgradeModel.DictFiles[item.Key] != item.Value) 133 { 134 _willUpgrades.Add(item.Key, item.Value); 135 } 136 } 137 else 138 { 139 //没有找到 140 _willUpgrades.Add(item.Key, item.Value); 141 } 142 } 143 } 144 } 145 } 146 return _willUpgrades; 147 } 148 } 149 150 /// <summary> 151 /// 清空版本信息 152 /// </summary> 153 public void ClearUpgradeModel() 154 { 155 if (File.Exists(Local_UpgradeXmlPath)) 156 { 157 158 try 159 { 160 string xmlStr = File.ReadAllText(Local_UpgradeXmlPath); 161 XmlDocument xmlDoc = new XmlDocument(); 162 xmlDoc.LoadXml(xmlStr); 163 164 XmlNode node = xmlDoc.SelectSingleNode("Upgrade/Files"); 165 if (node != null && node.ChildNodes.Count > 0) 166 { 167 node.RemoveAll(); 168 } 169 File.WriteAllText(UpgradeHelper.Instance.Local_UpgradeXmlPath, xmlDoc.InnerXml); 170 } 171 catch (Exception) 172 { } 173 } 174 _local_UpgradeModel = null; 175 _server_UpgradeModel = null; 176 _willUpgrades = null; 177 } 178 179 180 181 #region 单例对象 182 183 private static UpgradeHelper _instance; 184 /// <summary> 185 /// 单例对象 186 /// </summary> 187 public static UpgradeHelper Instance 188 { 189 get 190 { 191 if (_instance == null) 192 { 193 _instance = new UpgradeHelper(); 194 //初始化本地配置文件,以及服务器地址 195 if (_instance.Local_UpgradeModel != null) 196 { 197 _instance.UpgradeUrl = _instance.Local_UpgradeModel.UpgradeUrl; 198 } 199 } 200 return _instance; 201 } 202 } 203 204 #endregion 205 206 #region 静态方法 207 208 /// <summary> 209 /// 启动主程序 210 /// </summary> 211 /// <param name="fileName"></param> 212 public static bool StartRunMain(string fileName) 213 { 214 string fullPath = fileName; 215 try 216 { 217 Process process = GetProcess(fileName); 218 if (process != null)//以及存在运行中的主进程 219 { 220 return true; 221 } 222 fullPath = string.Format("{0}\\{1}", Application.StartupPath, fileName); 223 224 ProcessStartInfo main = new ProcessStartInfo(fullPath); 225 Process.Start(fullPath); 226 return true; 227 } 228 catch (Exception ex) 229 { 230 MessageBox.Show(string.Format("主程序{0}调用失败:\n{1}", fullPath, ex.Message), "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error); 231 } 232 return false; 233 } 234 235 /// <summary> 236 /// 杀死进程 237 /// </summary> 238 /// <param name="process"></param> 239 public static void KillProcess(string processName) 240 { 241 if (string.IsNullOrWhiteSpace(processName)) return; 242 processName = processName.ToLower(); 243 processName = processName.Replace(".exe", ""); 244 //杀死主进程 245 Process[] processes = Process.GetProcesses(); 246 foreach (Process process in processes) 247 { 248 if (!string.IsNullOrWhiteSpace(process.ProcessName)) 249 { 250 if (process.ProcessName.ToLower() == processName) 251 { 252 process.Kill(); 253 } 254 } 255 } 256 } 257 258 /// <summary> 259 /// 获取进程 260 /// </summary> 261 /// <param name="pName"></param> 262 /// <returns></returns> 263 public static Process GetProcess(string pName) 264 { 265 if (string.IsNullOrWhiteSpace(pName)) return null; 266 pName = pName.ToLower(); 267 pName = pName.Replace(".exe", ""); 268 //杀死主进程 269 Process[] processes = Process.GetProcesses(); 270 foreach (Process process in processes) 271 { 272 if (!string.IsNullOrWhiteSpace(process.ProcessName)) 273 { 274 if (process.ProcessName.ToLower() == pName) 275 { 276 return process; 277 } 278 } 279 } 280 return null; 281 } 282 283 #endregion 284 285 }

1 public class UpgradeModel 2 { 3 /// <summary> 4 /// 初始化对象 5 /// </summary> 6 /// <param name="xml"></param> 7 public void LoadUpgrade(string xml) 8 { 9 XmlDocument xmlDoc = new XmlDocument(); 10 xmlDoc.LoadXml(xml); 11 12 //读取UpgradeUrl 13 XmlNode node = xmlDoc.SelectSingleNode("//UpgradeUrl"); 14 if (node != null) 15 { 16 this.UpgradeUrl = node.InnerText; 17 } 18 //读取RunMain 19 node = xmlDoc.SelectSingleNode("//RunMain"); 20 if (node != null) 21 { 22 this.RunMain = node.InnerText; 23 } 24 //读取RunMain 25 node = xmlDoc.SelectSingleNode("//AutoUpgrade"); 26 if (node != null) 27 { 28 this.AutoUpgrade = node.InnerText; 29 } 30 //读取Files 31 node = xmlDoc.SelectSingleNode("Upgrade/Files"); 32 this.DictFiles = new Dictionary<string, string>(); 33 if (node != null && node.ChildNodes.Count > 0) 34 { 35 foreach (XmlNode item in node.ChildNodes) 36 { 37 if (item.Name != "#comment") 38 { 39 string name = GetNodeAttrVal(item, "Name"); 40 string version = GetNodeAttrVal(item, "Version"); 41 if (!this.DictFiles.ContainsKey(name)) 42 { 43 this.DictFiles.Add(name, version); 44 } 45 } 46 } 47 } 48 } 49 50 private string GetNodeAttrVal(XmlNode node, string attr) 51 { 52 if (node != null && node.Attributes != null && node.Attributes[attr] != null) 53 { 54 string val = node.Attributes[attr].Value; 55 if (!string.IsNullOrWhiteSpace(val)) 56 { 57 return val.Trim(); 58 } 59 return val; 60 } 61 return string.Empty; 62 } 63 64 /// <summary> 65 /// 服务器地址 66 /// </summary> 67 public string UpgradeUrl { get; set; } 68 69 /// <summary> 70 /// 更新完成后运行的主程序名称 71 /// </summary> 72 public string RunMain { get; set; } 73 74 /// <summary> 75 /// 更新程序名称 76 /// </summary> 77 public string AutoUpgrade { get; set; } 78 79 /// <summary> 80 /// 文件列表 81 /// string 文件名 82 /// string 版本号 83 /// </summary> 84 public Dictionary<string, string> DictFiles { get; set; } 85 }
服务端
服务端主Xml版本文件,包含所有的项目文件,客户端根据每个文件的版本号进行判断是否需要更新。如果需只更新某几个文件,则将对应文件的版本号更改只更高的版本号即可

1 <?xml version="1.0" encoding="utf-8" ?> 2 <Upgrade> 3 <!--服务器地址--> 4 <UpgradeUrl>http://localhost:17580</UpgradeUrl> 5 <!--更新完成后运行的主程序名称--> 6 <RunMain>ClientMain.exe</RunMain> 7 <!--更新程序名称--> 8 <AutoUpgrade>AutoUpgrade.exe</AutoUpgrade> 9 <Files> 10 <!--更新文件列表,以Version为标志,当Version改变时,客户端启动会自动更新。子路径格式:\image\index.jpg--> 11 <File Version="01" Name="\image\index.jpg" /> 12 <File Version="01" Name="ClientMain.exe" /> 13 <File Version="01" Name="AutoUpgrade.exe" /> 14 </Files> 15 </Upgrade>
服务端主要提供连个可以通过Http的get或post访问的路径。一个用于获取版本Xml文件内容,一个用于下载指定文件的路径。以下代码示例通过asp.net mvc进行实现。大家可以根据自己技术方式参照实现。

1 /// <summary> 2 /// 自动升级服务 3 /// </summary> 4 public class UpgradeController : Controller 5 { 6 // 7 // GET: /Upgrade/ 8 9 /// <summary> 10 /// 获取更新文件列表 11 /// </summary> 12 /// <returns></returns> 13 public object UpgradeList() 14 { 15 string cacheKey = "Upgrade_UpgradeList.xml"; 16 string resStr = CommonLibrary.CacheClass.GetCache<string>(cacheKey); 17 if (string.IsNullOrWhiteSpace(resStr)) 18 { 19 string fileName = Server.MapPath(@"~\App_Data\UpgradeList.xml"); 20 if (System.IO.File.Exists(fileName)) 21 { 22 resStr = System.IO.File.ReadAllText(fileName); 23 CommonLibrary.CacheClass.SetCacheMins(cacheKey, resStr, 1); 24 } 25 } 26 return resStr; 27 } 28 29 /// <summary> 30 /// 生成更新文件 31 /// </summary> 32 /// <returns></returns> 33 public object Create() 34 { 35 UpgradeFileManager.CreateFiles(Server.MapPath("/App_Data")); 36 return "ok"; 37 } 38 39 /// <summary> 40 /// 下载文件 41 /// </summary> 42 /// <param name="fileName"></param> 43 /// <returns></returns> 44 public object DownloadFile() 45 { 46 string fileName = PageRequest.GetString("fileName"); 47 fileName = Server.MapPath(string.Format(@"~\App_Data\{0}", fileName)); 48 return File(fileName, "application/octet-stream"); 49 } 50 51 /// <summary> 52 /// 异常处理 53 /// </summary> 54 /// <param name="filterContext"></param> 55 protected override void OnException(ExceptionContext filterContext) 56 { 57 filterContext.HttpContext.Response.StatusCode = 400; 58 filterContext.Result = Content(filterContext.Exception.GetBaseException().Message); 59 filterContext.ExceptionHandled = true; 60 } 61 }

1 /// <summary> 2 /// 此类主要作用,对于项目文件非常多,自己手动编辑很麻烦,可以采用此方法,指定目录自动生成初始化的版本文件 3 /// </summary> 4 public class UpgradeFileManager 5 { 6 /// <summary> 7 /// 创建版本文件 8 /// </summary> 9 /// <param name="path"></param> 10 public static void CreateFiles(string path) 11 { 12 List<string> dirList = new List<string>(); 13 GetAllDirt(path, dirList);//获取所有目录 14 dirList.Add(path); 15 System.Text.StringBuilder xml = new System.Text.StringBuilder(); 16 xml.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); 17 xml.AppendLine(" <Files>"); 18 foreach (var diry in dirList) 19 { 20 string[] files = Directory.GetFiles(diry); 21 foreach (string filePath in files) 22 { 23 FileInfo info = new FileInfo(filePath); 24 string name = filePath.Replace(path, ""); 25 if (info.Directory.FullName == path) 26 { 27 name = name.Remove(0, 1); 28 } 29 xml.AppendLine(string.Format(" <File Version=\"1\" Name=\"{0}\" />", name)); 30 } 31 } 32 xml.AppendLine("</Files>"); 33 using (StreamWriter sw = new StreamWriter(Path.Combine(path, "UpgradeList_Temp.xml"))) 34 { 35 sw.Write(xml); 36 sw.Close(); 37 } 38 } 39 40 /// <summary> 41 /// 获取所有子目录 42 /// </summary> 43 /// <param name="curDir"></param> 44 /// <param name="list"></param> 45 private static void GetAllDirt(string curDir, List<string> list) 46 { 47 string[] dirs = Directory.GetDirectories(curDir); 48 if (dirs.Length > 0) 49 { 50 foreach (string item in dirs) 51 { 52 list.Add(item); 53 GetAllDirt(item, list); 54 } 55 } 56 } 57 }
结语
源代码托管于GitHub,供大伙学习参考,项目地址:https://github.com/keguoquan/AutoUpgrade。感兴趣或觉得不错的望赏个star,不胜感激!
若能顺手点个赞,更加感谢!
如有疑问可以QQ咨询:343798739