c# 怎么实现自动更新程序

 

主要功能介绍

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

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

更新界面如图:

 

客户端

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

 0. 前言在之前的几篇内容中,我们了解了如何通过ADO.NET 访问数据库,如何修改、新增数据。如何通过DataSet和DataAdapter获取数据,我们将在这一篇试试自己实现一个简单的ORM框架 ...