欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

c# 如何实现自动更新程序

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

主要功能介绍

实现文件的自动更新。主要功能:

  1. 支持整包完全更新,即客户端只需输入一个服务器地址,即可下载所有文件。
  2. 支持增量更新,即只更新指定的某几个文件。
  3. 支持自动更新程序的更新

更新界面如图:

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# 自动更新程序的资料请关注其它相关文章!