原理:
工具生成更新配置节xml放到文件服务器上,外网可访问;
能过本地配置文件与服务器配置文件日期属性对比及配置节版本与大小属性判断有无更新;
存在更新,将文件从服务器下载到客户端,并替换原程序重启;
实现时,更新程序与原主程序是两个不同的启动程序,不存在文件占用,避免替换时文件被占用
如果做成一个程序,下载替换时需要通过外部批处理脚本关闭当前应用,并替换程序重启应用。在系统盘时要注意权限问题;
服务端生成xml代码块:
public partial class FormMain : Form { public FormMain() { InitializeComponent(); txtWebUrl.Text = "localhost:8011"; txtWebUrl.ForeColor = Color.Gray; } //获取当前目录 //string currentDirectory = AppDomain.CurrentDomain.BaseDirectory; string currentDirectory = System.Environment.CurrentDirectory; //服务端xml文件名称 string serverXmlName = "AutoupdateService.xml"; //更新文件URL前缀 string url = string.Empty; void CreateXml() { //创建文档对象 XmlDocument doc = new XmlDocument(); //创建更新文件根节点 XmlElement root = doc.CreateElement("Files"); //设置更新节点日期 root.SetAttribute("Date", DateTime.Now.ToString("yyyyMMdd")); ////创建日期根节点 //XmlElement versionDate = doc.CreateElement("UpDate"); //versionDate.InnerText = DateTime.Now.ToString("yyyyMMdd"); //doc.AppendChild(versionDate); //头声明 XmlDeclaration xmldecl = doc.CreateXmlDeclaration("1.0", "utf-8", null); doc.AppendChild(xmldecl); DirectoryInfo dicInfo = new DirectoryInfo(currentDirectory); //调用递归方法组装xml文件 PopuAllDirectory(doc, root, dicInfo); //追加节点 doc.AppendChild(root); //保存文档 doc.Save(serverXmlName); } //递归组装xml文件方法 private void PopuAllDirectory(XmlDocument doc, XmlElement root, DirectoryInfo dicInfo) { foreach (FileInfo f in dicInfo.GetFiles()) { //排除当前目录中生成服务端配置文件、此工具文件、后缀为pdb、config、ssk文件、包含vshost的文件 List<string> notMimefile = new List<string>() { "pdb", "config", "ssk" }; if (!f.Name.Contains("CreateXmlTools") && f.Name != "AutoupdateService.xml"&&f.Name != "AutoUpdater.exe" && !notMimefile.Contains(f.Name.Substring(f.Name.LastIndexOf(".") + 1)) && f.Name.IndexOf("vshost")==-1) { string path = dicInfo.FullName.Replace(currentDirectory, "").Replace("\\", "/"); string folderPath=string.Empty; if (path != string.Empty) { folderPath = path.TrimStart('/') + "/"; } XmlElement child = doc.CreateElement("File"); child.SetAttribute("path", folderPath + f.Name); child.SetAttribute("url", url + path + "/" + f.Name); child.SetAttribute("version", FileVersionInfo.GetVersionInfo(f.FullName).FileVersion); child.SetAttribute("size", f.Length.ToString()); root.AppendChild(child); } } foreach (DirectoryInfo di in dicInfo.GetDirectories()) PopuAllDirectory(doc, root, di); } private void btnCreate_Click(object sender, EventArgs e) { string host = txtWebUrl.Text.Trim().TrimEnd('/').ToLower(); url = (host.StartsWith("http") || host.StartsWith("https"))? host:"http://" + host; CreateXml(); ReadXml(); } private void ReadXml() { string path="AutoupdateService.xml"; rtbXml.ReadOnly = true; if (File.Exists(path)) { rtbXml.Text = File.ReadAllText(path); } } private void txtWebUrl_Enter(object sender, EventArgs e) { txtWebUrl.ForeColor = Color.Black; if (txtWebUrl.Text.Trim() == "localhost:8011") { txtWebUrl.Text = string.Empty; } } }
主程序更新相关代码块:
private static string strUpdateConfigPath = Application.StartupPath + @"\PrintLocal.config"; private static string strUpdaterProPath = Application.StartupPath + @"\AutoUpdater.exe"; //process.StartInfo.FileName = Application.StartupPath + "//AutoUpdater.exe"; private PopTip _tip; private void PrintService_Load(object sender, EventArgs e) { string strUpdateURL = GetConfigValue(strUpdateConfigPath, "ServerUrl"); LocalVersion.Text = GetConfigValue(strUpdateConfigPath, "Date"); RemoteVersion.Text = GetTheLastUpdateTime(strUpdateURL); skin.SkinFile = System.Environment.CurrentDirectory + @"\Skins\DeepCyan.ssk"; System.Timers.Timer time =new System.Timers.Timer(); int intervaltime = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["checkinterval"]); time.Elapsed +=new System.Timers.ElapsedEventHandler(IntervalCheck); time.Interval = intervaltime * 1000;//时间间隔为intervaltime秒钟 time.Start(); } private void IntervalCheck(object source, System.Timers.ElapsedEventArgs e) { if (HasNewVersion()) { _tip = new PopTip(); Action c = () => _tip.ShowDialog(); c.BeginInvoke(null, c); } } private void 更新ToolStripMenuItem_Click(object sender, EventArgs e) { CheckUpdate(); } /// <summary> /// 检查更新. /// </summary> private void CheckUpdateBt_Click(object sender, EventArgs e) { CheckUpdate(); } private void CheckUpdate() { if (HasNewVersion()) { if (MessageBox.Show("是否下载更新?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK) { Process process = new Process(); process.StartInfo.FileName = strUpdaterProPath;//更新程序所在位置 process.Start();//启动更新程序 Process.GetCurrentProcess().Kill(); //Kill当前程序 }; } else { MessageBox.Show("未检测到新版本."); } } #region 检测版本与获取版本信息 internal static bool HasNewVersion() { bool hasNewVersion = false; string strUpdateURL = GetConfigValue(strUpdateConfigPath, "ServerUrl"); //读取本地xml中配置的更新服务器的URL string strLastUpdateDate = GetConfigValue(strUpdateConfigPath, "Date"); //读取本地Config中配置的最近配置日期 bool ConfigEnabled = Convert.ToBoolean(GetConfigValue(strUpdateConfigPath, "Enabled")); string strTheUpdateDate = GetTheLastUpdateTime(strUpdateURL); //获得更新服务器端的此次更新日期 if (ConfigEnabled && (DateTime.Compare(DateTime.ParseExact(strTheUpdateDate, "yyyyMMdd", null), DateTime.ParseExact(strLastUpdateDate, "yyyyMMdd", null)) > 0)) { hasNewVersion = true; } return hasNewVersion; } internal static string GetConfigValue(string path, string appKey) { XmlDocument xDoc = new XmlDocument(); XmlNode xNode; XmlElement xElem = null; try { xDoc.Load(path); xNode = xDoc.SelectSingleNode("//Config"); xElem = (XmlElement)xNode.SelectSingleNode(appKey); } catch (XmlException ex) { MessageBox.Show(ex.Message); } if (xElem != null) return xElem.InnerText; else return ""; } private static string GetTheLastUpdateTime(string path) { string Date = ""; var xml = string.Empty; HttpWebRequest request = WebRequest.Create(path) as HttpWebRequest; var response = request.GetResponse(); using (var stream = response.GetResponseStream()) { using (var reader = new StreamReader(stream, Encoding.UTF8)) { xml = reader.ReadToEnd(); } } response.Close(); var element = XElement.Parse(xml); Date = element.Attribute("Date").Value; return Date; } #endregion
PopTip代码块:
public partial class PopTip : Form { public PopTip() { InitializeComponent(); } private int _count; private void OKBt_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { Hide(); Process process = new Process(); process.StartInfo.FileName = Application.StartupPath + @"\AutoUpdater.exe";//更新程序所在位置 //process.StartInfo.FileName = Application.StartupPath + "//AutoUpdater.exe";//更新程序所在位置 process.Start();//启动更新程序 Process.GetCurrentProcess().Kill(); } private void CancelBt_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { Hide(); } private void PopTip_Load(object sender, EventArgs e) { Location = new Point(Screen.PrimaryScreen.Bounds.Width - Width, Screen.PrimaryScreen.Bounds.Height - Height-40); TipLb.Text = "发现新版本,是否马上更新"; OKBt.Text = "是,马上更新"; timer1.Start(); } private void timer1_Tick(object sender, EventArgs e) { _count++; //提示信息显示8秒就关闭 if (_count == 8) { Close(); } // timer1.Stop(); } }
更新程序相关代码块:
/// <summary> /// Class Config /// </summary> public class Config { public bool Enabled { get; set; } public string ServerUrl { get; set; } public string Date { get; set; } public List<LocalFile> Files { get; set; } /// <summary> /// Loads the config. /// </summary> /// <param name="file">The file.</param> /// <returns>Config.</returns> public static Config LoadConfig(string file) { Config config=new Config(); if (File.Exists(file)) { var xs = new XmlSerializer(typeof(Config)); var sr = new StreamReader(file); config = xs.Deserialize(sr) as Config;// 这里是序列化 sr.Close(); } return config; } /// <summary> /// Saves the config. /// </summary> /// <param name="file">The file.</param> public void SaveConfig(string file) { var xs = new XmlSerializer(typeof(Config)); var sw = new StreamWriter(file); xs.Serialize(sw, this); sw.Close(); } } public class LocalFile { [XmlAttribute("path")] public string Path { get; set; } [XmlAttribute("version")] public string Version { get; set; } [XmlAttribute("size")] public long Size { get; set; } }
public class RemoteFile { public string Path { get; set; } public string Url { get; set; } public string Version { get; set; } public long Size { get; set; } }
public VersionDetails LoadNewVersion() { var xml = string.Empty; HttpWebRequest request = WebRequest.Create(this.ServerUrl) as HttpWebRequest; var response = request.GetResponse(); using (var stream = response.GetResponseStream()) { using (var reader = new StreamReader(stream, Encoding.UTF8)) { xml = reader.ReadToEnd(); } } response.Close(); return new VersionDetails(xml); }
public class VersionDetails { public VersionDetails(string xml) { var element = XElement.Parse(xml); Date = element.Attribute("Date").Value; Files = this.LoadFiles(element); } public string Date { get; internal set; } public List<RemoteFile> Files { get; internal set; } private List<RemoteFile> LoadFiles(XElement element) { var files = new List<RemoteFile>(); foreach (var el in element.Elements("File")) { var file = new RemoteFile() { Path = el.Attribute("path").Value, Url = el.Attribute("url").Value, Version = el.Attribute("version").Value, Size = Convert.ToInt64(el.Attribute("size").Value) }; files.Add(file); } return files; } }
public bool HasNewVersion { get { return LocalVersion.Enabled && (NewVersion.Date != LocalVersion.Date); } }
http://blog.csdn.net/learning_hard/article/details/17456751 [你必须知道的异步编程]——基于任务的异步模式 异步下载
文件下载时可采用WebRequest或WebClient下载文件
参考:
http://www.cnblogs.com/KnightsWarrior/archive/2010/10/20/1856255.html#!comments
http://www.cnblogs.com/stoneniqiu/p/3806558.html
http://www.cnblogs.com/iyond/archive/2007/06/14/783301.html
http://www.cnblogs.com/sparkdev/p/6031920.html
winform更新解决办法:
思路一:
生成一个批处理文件
执行批处理文件并且自身退出
批处理文件中执行覆盖操作
批处理中最后一句启动本程序
完成更新
思路二:
主程序A更新自动更新程序B,自动更新程序B更新主程序A
C# Timer用法及实例详解 http://developer.51cto.com/art/200909/149829.htm
System.Timers.Timer t = new System.Timers.Timer(10000); //实例化Timer类,设置间隔时间为10000毫秒; t.Elapsed += new System.Timers.ElapsedEventHandler(theout); //到达时间的时候执行事件; t.AutoReset = true; //设置是执行一次(false)还是一直执行(true); t.Enabled = true; //是否执行System.Timers.Timer.Elapsed事件; public void theout( object source, System.Timers.ElapsedEventArgs e) { MessageBox.Show("OK!"); }
Timer timer1 = new Timer(); timer1.Interval = 1000; timer1.Enabled = true; timer1.Tick += new EventHandler(timer1EventProcessor);//添加事件
https://msdn.microsoft.com/zh-cn/library/3840csdc.aspx Timer.Tick 事件
http://www.cnblogs.com/ManchesterUnitedFootballClub/p/4596465.html Winfrom 提示消息框公共类
关于退出程序
this.Close(); 只是关闭当前窗口,若不是主窗体的话,是无法退出程序的,另外若有托管线程(非主线程),也无法干净地退出;
Application.Exit(); 方法停止在所有线程上运行的所有消息循环,并关闭应用程序的所有窗口
强制直接退出了整个程序,不只是关闭子窗体:
Process.GetCurrentProcess().Kill(); //终止当前正在运行的线程
或者System.Threading.Thread.CurrentThread.Abort();
或者Application.ExitThread();
System.Environment.Exit(0); 这是最彻底的退出方式,不管什么线程都被强制退出,把程序结束的很干净。