c# 如何实现自动更新程序
程序员文章站
2022-04-05 14:20:37
主要功能介绍实现文件的自动更新。主要功能: 支持整包完全更新,即客户端只需输入一个服务器地址,即可下载所有文件。 支持增量更新,即只更新指定的某几个文件。 支持自动更新程序的更新 更新界面如图...
主要功能介绍
实现文件的自动更新。主要功能:
- 支持整包完全更新,即客户端只需输入一个服务器地址,即可下载所有文件。
- 支持增量更新,即只更新指定的某几个文件。
- 支持自动更新程序的更新
更新界面如图:
客户端
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# 自动更新程序的资料请关注其它相关文章!