winform程序相对web程序而言,功能更强大,编程更方便,但软件更新却相当麻烦,要到客户端一台一台地升级,面对这个实际问题。
程序exe自己下载覆盖自己肯定是不行的,会报“当前程序正在被另一个进程所使用”的系统错误。因此我们需要做一个专门更新的程序来下载更新主程序以及其他相关文件。
在最近的一个小项目中,本人设计了一个通过软件实现自动升级技术方案,弥补了这一缺陷,有较好的参考价值
实现原理:主程序通过在WebServices中实现一个GetVersion方法或是直接访问服务器端的UpdateList.xml,获取服务器上的最新版本。 然后将现在版本与最新版本比较,如果有新版本或存在新添加的文件则下载文件。
解决方案:
首先准备一个UpdateList.xml文件。文件中包含程序中各个文件的基本信息。
Ver:版本号
Name:文件名
Url:文件在程序中的路径
<?xml version="1.0" encoding="utf-8" ?> <AutoUpdater> <description>程序自动更新</description> <Updater> <Url>http://localhost/testserver/</Url> <LastUpdateTime>2014-06-05</LastUpdateTime> </Updater> <Files Commemt="文件列表"> <File Ver="1.0.0.1" Name="XKGL.exe" Url="\"/> <File Ver="1.0.0.0" Name="XKGL.Model.dll" Url="\"/> <File Ver="1.0.0.0" Name="Config.xml" Url="\Config\"/> <File Ver="1.0.0.0" Name="Dic.xml" Url="\Config\"/> <File Ver="1.0.0.0" Name="cardapi3.dll" Url="\"/> <File Ver="1.0.0.0" Name="license.dat" Url="\"/> <File Ver="1.0.0.0" Name="sdtapi.dll" Url="\"/> <File Ver="1.0.0.0" Name="WltRS.dll" Url="\"/>
<File Ver="1.0.0.1" Name="UpdateList.xml" Url="\Config\"/> </Files> </AutoUpdater>
储存文件的实体类
/// <summary> /// 程序文件 /// </summary> class UpFile { public string Name { get; set; }//文件名 public string Version { get; set; }//版本号 public string Url { get; set; }//本地路径 }
主程序检查更新
class ProUpdate { static string isCheck = "//Configuration//IsCheck"; static string xmlIP = "//Configuration//ServerConfig//IP"; static string xmlPort = "//Configuration//ServerConfig//Port"; static string xmlFile = "//AutoUpdater//Files";
/// <summary>
/// 获取更新文件集合
/// </summary>
/// <returns>需要文件</returns> public static List<UpFile> CheckUpdate() { List<UpFile> fileList = new List<UpFile>(); XmlDocument XmlSerDoc = new XmlDocument(); //在配置文件中获取服务器IP和端口号 string ip = XmlHelper.GetXmlAttribute(PublicMembers.XmlSysDoc, xmlIP, "value").Value; string port = XmlHelper.GetXmlAttribute(PublicMembers.XmlSysDoc, xmlPort, "value").Value; //读取服务器端UpdateList.xml XmlSerDoc.Load("http://" + ip + ":" + port + "/XKGL/checkUpdate/UpdateList.xml"); //获取服务器端UpdateList.xml中的Files XmlNodeList nodeSerList = XmlSerDoc.SelectSingleNode(xmlFile).ChildNodes; XmlDocument XmlLocDoc = new XmlDocument(); //读取本地UpdateList.xml XmlLocDoc.Load(Application.StartupPath + "\\Config\\UpdateList.xml"); //获取本地UpdateList.xml中的Files XmlNodeList nodeLocList = XmlLocDoc.SelectSingleNode(xmlFile).ChildNodes; //获得需要更新的文件 foreach (XmlNode nodeSer in nodeSerList) { string[] newVersion = nodeSer.Attributes["Ver"].Value.Split('.');//服务器版本数组 string[] oldVersion;//本地版本数组 string Ver = ""; string serName = nodeSer.Attributes["Name"].Value; string url = nodeSer.Attributes["Url"].Value; //服务器文件信息 UpFile file = new UpFile(); file.Name = serName; file.Version = nodeSer.Attributes["Ver"].Value; file.Url = url; //判断本地是否存在该文件 if (File.Exists(Application.StartupPath + url + serName)) { //获取本地文件版本 foreach (XmlNode nodeLoc in nodeLocList) { string locName = nodeLoc.Attributes["Name"].Value; if (serName.Equals(locName)) { Ver = nodeLoc.Attributes["Ver"].Value; break; } } //文件版本比较 if (!Ver.Equals("")) { oldVersion = Ver.Split('.'); for (int i = 0; i < 4; i++) { if (Convert.ToInt32(newVersion[i]) > Convert.ToInt32(oldVersion[i])) { fileList.Add(file); break; } } } } else { fileList.Add(file); } } return fileList; } }
调用检查更新方法
private delegate void dlLoadData(); private void FrmProgress_Load(object sender, EventArgs e) { List<UpFile> fileList = new List<UpFile>(); //检查更新 fileList = ProUpdate.CheckUpdate(); Thread _bgThread = new Thread(() => { SyncProData(fileList); }); _bgThread.IsBackground = true; _bgThread.Start(); } /// <summary> /// 下载更新 /// </summary> /// <param name="_SerialNumber"></param> private void SyncProData(List<UpFile> _updateFiles) { try { if (this.InvokeRequired) { Thread.Sleep(100); this.Invoke(new dlLoadData(() => { SyncProData(_updateFiles); })); } else { if (_updateFiles.Count > 0) { bool result = MessageBox.Show("发现新版本,是否更新", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes; if (result) { string strFileName = Newtonsoft.Json.JavaScriptConvert.SerializeObject(_updateFiles);
/*
* 在程序之间传递参数是JSON字符串中的“\"”引号会被过滤掉,用其他字符代替
* */
//打开更新程序,关闭主程序 System.Diagnostics.Process.Start(Application.StartupPath + "AutoUpdate.exe", strFileName.Replace("\"", "@@")); this.DialogResult = DialogResult.Yes; } } } } catch (Exception ex) { MessageBox.Show("更新失败。\n\r错误信息:" + ex.Message); } }
下面是AutoUpdate. exe更新程序的方法
界面:
启动程序时接收主程序的参数
static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main(String[] arg) {try { if (arg.Length == 0) { MessageBox.Show("该程序无法启动", "提示"); return; } string FilesInfo = arg[0].Replace("@@", "\"");//接收JSON串 FrmAutoUpdate frmAutoUpdate = new FrmAutoUpdate(FilesInfo); frmAutoUpdate.ShowDialog(); frmAutoUpdate.Dispose(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } }
页面显示需要更新的文件信息
/// <summary> /// 接收参数 /// </summary> string strFileInfo = ""; public FrmAutoUpdate(string _filesinfo) { InitializeComponent(); strFileInfo = _filesinfo; } private void FrmAutoUpdate_Load(object sender, EventArgs e) { List<UpFile> update = new List<UpFile>(); update = Newtonsoft.Json.JavaScriptConvert.DeserializeObject<List<UpFile>>(strFileInfo);foreach (UpFile file in update) { ListViewItem lvi = new ListViewItem(); lvi.SubItems[0].Text = file.Name; lvi.SubItems.Add(file.Version); lvi.SubItems.Add("0%"); lvi.SubItems.Add(file.Url); lvwFileInfo.Items.Add(lvi); } }
开始下载更新文件
private void btnUpdate_Click(object sender, EventArgs e) { Thread bgThread = new Thread(() => { DownloadFile(); }); bgThread.IsBackground = true; bgThread.Start(); } public void DownloadFile() { try { if (this.InvokeRequired) { Thread.Sleep(100); this.Invoke(new dlLoadData(DownloadFile)); } else { if (!gbxComplete.Visible) { XmlDocument XmlDoc = new XmlDocument(); XmlDoc.Load("Config\\Config.xml");//加载XML文档 for (int i = 0; i < lvwFileInfo.Items.Count; i++) { string filename = lvwFileInfo.Items[i].SubItems[0].Text; string fileurl = lvwFileInfo.Items[i].SubItems[3].Text; string ip = GetXmlAttribute(XmlDoc, xmlIP, "value").Value; string port = GetXmlAttribute(XmlDoc, xmlPort, "value").Value; string serverFile = "http://" + ip + ":" + port + "/XKGL/checkUpdate/" + filename; string localFile = Application.StartupPath + fileurl + filename; httpDownload(serverFile, localFile, ProBar, lvwFileInfo.Items, i); lvwFileInfo.Items[i].SubItems[2].Text = "100%"; } //下载 btnUpdate.Text = "完成"; } } } catch { MessageBox.Show("文件下载出错,更新失败。", "警告", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1); } } /// <summary> /// 下载文件 /// </summary> /// <param name="URL">服务器文件路径</param> /// <param name="LocalPath">本地文件路径</param> /// <param name="filename">文件名</param> /// <param name="prog">进度条控件</param> /// <param name="item">ListView项</param> /// <param name="i">count</param> public void httpDownload(string URL, string LocalFile, System.Windows.Forms.ProgressBar prog, ListView.ListViewItemCollection item, int i) { float percent = 0; System.Net.HttpWebRequest Myrq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(URL); System.Net.HttpWebResponse myrp = (System.Net.HttpWebResponse)Myrq.GetResponse(); long totalBytes = myrp.ContentLength; if (prog != null) { prog.Maximum = (int)totalBytes; } System.IO.Stream st = myrp.GetResponseStream(); System.IO.Stream so = new System.IO.FileStream(LocalFile, System.IO.FileMode.Create); long totalDownloadedByte = 0; byte[] by = new byte[2048]; int osize = st.Read(by, 0, (int)by.Length); while (osize > 0) { totalDownloadedByte = osize + totalDownloadedByte; so.Write(by, 0, osize); if (prog != null) { prog.Value = (int)totalDownloadedByte; } osize = st.Read(by, 0, (int)by.Length); percent = (float)totalDownloadedByte / (float)totalBytes * 100; if (percent.ToString().Length > 4) { item[i].SubItems[2].Text = percent.ToString().Substring(0, 5) + "%"; } else { item[i].SubItems[2].Text = percent.ToString() + "%"; } Application.DoEvents(); } so.Close(); st.Close(); }
代码写完了,看一下效果