C# 实现客户端程序自动更新

看到一篇不错的帖子,可能以后会用到,果断收藏

文章来源 博客园 jenry(云飞扬)http://www.cnblogs.com/jenry/archive/2006/08/15/477302.html

由于微软提供的更新程序使用不方便,所以又写了此程序。此程序是本人一年前所写的一段程序,当时在开发一个CS版本报价系统,当时由于开发过程仓促,代码可能有点不是太规范此程序编译后只有一下AutoUpdate.exe文件与一个配置文件UpdateList.xml,主要通过本地程序与服务端程序文件的版本号来升级与更新本地程序文件。

image.png

image.png

UpdateList.xml文件内容如下:

<?xml version="1.0" encoding="gb2312"?>

<description>Application autoUpdate </description>
<Updater>
<Url>http://10.0.5.98/SoftUpdate/</Url>
<LastUpdateTime>2005-09-05 </LastUpdateTime>
</Updater>
<Application applicationId="ItemSoft">
<EntryPoint>ItemSoft.exe </EntryPoint>
<Location>.</Location>
<Version>1.0.0.0 </Version>
</Application>
<Files>
<File Ver="1.0.0.0" Name="ItemSoft.exe" />
<File Ver="1.0.0.0" Name="Reports\test.txt"/>
<File Ver="1.0.0.0" Name="Interop.grproLib.dll"/>
<File Ver="1.0.0.0" Name="Reports\test.grf"/>
</Files>
</AutoUpdater>

说明:
<description></description> 程序的描述;
<Url></Url> 更新服务器地址,为一个虚拟目录或站点路径;
<EntryPoint></EntryPoint> 需要更新主程序文件,为exe;
<Location>.</Location> 需要更新主程序文件所在路径;
<Version>1.0.0.0 </Version> 主程序版本号;
<Files> </Files> 需要更新的文件列表;Ver:文件版本号,Name:文件名,包括路径(相对);

使用时,在更新服务器上新建Web虚拟目录或站点,然后将需要更新的文件与UpdateList.xml放在上面;
本地将AutoUpdate.exe与UpdateList.xml放在主程序的根目录下。本地UpdateList.xml中的版本号如果小于服务端,自动程序会自动下载所需要更新的文件。
本人的完整.Net打包程序下载中所带的项目中,就使用了此更新程序。
源码下载:/Files/jenry/AutoUpdate.rar
二进制文件下载:/Files/jenry/bin.rar
生成配置文件UpdateList.xml工具:/Files/jenry/AULWriter1.0.rar (由whatisgood 提供,感谢!!)New


c#自动更新程序

主要功能介绍

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

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

更新界面如图:

客户端

复制代码; "复制代码")

/// <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());
            }
        }

复制代码; "复制代码")

复制代码; "复制代码")

  1 public partial class FrmUpdate : Form
  2     {
  3         /// <summary>
  4         /// 构造函数
  5         /// </summary>
  6         /// <param name="tempPath"></param>
  7         /// <param name="updateFiles"></param>
  8         public FrmUpdate()
  9         {
 10             InitializeComponent();
 11         }
 12 
 13         /// <summary>
 14         /// 窗体加载事件
 15         /// </summary>
 16         /// <param name="sender"></param>
 17         /// <param name="e"></param>
 18         private void FrmUpdate_Load(object sender, EventArgs e)
 19         {
 20             try
 21             {
 22                 //加载服务器地址
 23                 txtHostUrl.Text = UpgradeHelper.Instance.UpgradeUrl;
 24                 BeginUpgrade();
 25             }
 26             catch (Exception ex)
 27             {
 28                 Output("初始化异常:" + ex.Message);
 29             }
 30         }
 31 
 32         /// <summary>
 33         /// 手动更新
 34         /// </summary>
 35         /// <param name="sender"></param>
 36         /// <param name="e"></param>
 37         private void butBegin_Click(object sender, EventArgs e)
 38         {
 39             try
 40             {
 41                 if (string.IsNullOrWhiteSpace(txtHostUrl.Text))
 42                 {
 43                     Output("请先输入服务器地址!");
 44                     return;
 45                 }
 46                 UpgradeHelper.Instance.UpgradeUrl = txtHostUrl.Text.Trim();
 47                 //清理版本信息 以便重新检测版本
 48                 UpgradeHelper.Instance.ClearUpgradeModel();
 49                 BeginUpgrade();
 50             }
 51             catch (Exception ex)
 52             {
 53                 Output("更新异常:" + ex.Message);
 54             }
 55         }
 56 
 57         private void BeginUpgrade()
 58         {
 59             try
 60             {
 61                 if (string.IsNullOrWhiteSpace(UpgradeHelper.Instance.UpgradeUrl))
 62                 {
 63                     return;
 64                 }
 65                 if (!(UpgradeHelper.Instance.UpgradeUrl.StartsWith("http://") || UpgradeHelper.Instance.UpgradeUrl.StartsWith("https://")))
 66                 {
 67                     Output("错误的服务器地址,地址必须以http://或者https://开头");
 68                     return;
 69                 }
 70                 //判断是否有更新
 71                 if (UpgradeHelper.Instance.WillUpgrades.Count > 0 && UpgradeHelper.Instance.Server_UpgradeModel != null)
 72                 {
 73                     SetWinControl(false);
 74                     //杀死主进程
 75                     UpgradeHelper.KillProcess(UpgradeHelper.Instance.Server_UpgradeModel.RunMain);
 76                     RunUpgrade();//启动更新
 77                 }
 78             }
 79             catch (Exception ex)
 80             {
 81                 Output("更新异常:" + ex.Message);
 82             }
 83         }
 84 
 85         /// <summary>
 86         /// 启动更新
 87         /// </summary>
 88         private void RunUpgrade()
 89         {
 90             //启动更新
 91             SetCaption(string.Format("共需更新文件{0}个,已更新0个。正在更新下列文件:", UpgradeHelper.Instance.WillUpgrades.Count));
 92             Task.Factory.StartNew(() =>
 93             {
 94                 string curFile = "";
 95                 try
 96                 {
 97                     int idx = 0;
 98                     foreach (KeyValuePair<string, string> item in UpgradeHelper.Instance.WillUpgrades)
 99                     {
100                         curFile = item.Key;
101                         string filePath = string.Format("{0}\\{1}", Application.StartupPath, item.Key);
102                         if (item.Key.IndexOf(UpgradeHelper.Instance.Server_UpgradeModel.AutoUpgrade) >= 0)
103                         {
104                             //如果当前文件为更新主程序
105                             filePath = string.Format("{0}\\AutoUpgradeTemp\\{1}", Application.StartupPath, item.Key);
106                         }
107                         string directory = Path.GetDirectoryName(filePath);
108                         if (!Directory.Exists(directory))
109                         {
110                             Directory.CreateDirectory(directory);
111                         }
112                         MyWebResquest.DownloadFile(UpgradeHelper.Instance.UpgradeUrl, item.Key, filePath);
113                         idx++;
114                         SetCaption(string.Format("共需更新文件{0}个,已更新{1}个。更新文件列表:", UpgradeHelper.Instance.WillUpgrades.Count, idx));
115                         Output(string.Format("更新文件{0}完成", curFile));
116                     }
117                     //保存版本文件
118                     File.WriteAllText(UpgradeHelper.Instance.Local_UpgradeXmlPath, UpgradeHelper.Instance.Server_UpgradeXml);
119 
120                     SetCaption(string.Format("更新完成,共更新文件{0}个", UpgradeHelper.Instance.WillUpgrades.Count));
121                     Output(string.Format("更新完成,共更新文件{0}个", UpgradeHelper.Instance.WillUpgrades.Count));
122 
123                     //下载完成后处理
124                     UpgradeHelper.StartRunMain(UpgradeHelper.Instance.Server_UpgradeModel.RunMain);
125 
126                     //退出当前程序
127                     ExitCurrent();
128                 }
129                 catch (Exception ex)
130                 {
131                     Output(string.Format("更新文件{0}异常:{1}", curFile, ex.Message));
132                     SetWinControl(true);
133                 }
134             });
135         }
136 
137         /// <summary>
138         /// 设置界面控件是否可用
139         /// </summary>
140         /// <param name="enabled"></param>
141         private void SetWinControl(bool enabled)
142         {
143             if (this.InvokeRequired)
144             {
145                 Action<bool> d = new Action<bool>(SetWinControl);
146                 this.Invoke(d, enabled);
147             }
148             else
149             {
150                 txtHostUrl.Enabled = enabled;
151                 butBegin.Enabled = enabled;
152             }
153         }
154 
155         /// <summary>
156         /// 退出当前程序
157         /// </summary>
158         private void ExitCurrent()
159         {
160             if (this.InvokeRequired)
161             {
162                 Action d = new Action(ExitCurrent);
163                 this.Invoke(d);
164             }
165             else
166             {
167                 Application.Exit();
168             }
169         }
170 
171         #region 日志输出
172 
173         /// <summary>
174         /// 设置跟踪状态
175         /// </summary>
176         /// <param name="caption"></param>
177         private void SetCaption(string caption)
178         {
179             if (this.lblCaption.InvokeRequired)
180             {
181                 Action<string> d = new Action<string>(SetCaption);
182                 this.Invoke(d, caption);
183             }
184             else
185             {
186                 this.lblCaption.Text = caption;
187             }
188         }
189 
190         /// <summary>
191         /// 设置跟踪状态
192         /// </summary>
193         /// <param name="caption"></param>
194         private void Output(string log)
195         {
196             if (this.txtLog.InvokeRequired)
197             {
198                 Action<string> d = new Action<string>(Output);
199                 this.Invoke(d, log);
200             }
201             else
202             {
203                 txtLog.AppendText(string.Format("{0}:{1}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), log));
204                 txtLog.ScrollToCaret();
205             }
206         }
207 
208         private void ClearOutput()
209         {
210             if (this.txtLog.InvokeRequired)
211             {
212                 Action d = new Action(ClearOutput);
213                 this.Invoke(d);
214             }
215             else
216             {
217                 txtLog.Text = "";
218             }
219         }
220 
221         #endregion
222 
223         private void FrmUpdate_FormClosing(object sender, FormClosingEventArgs e)
224         {
225             if (e.CloseReason == CloseReason.UserClosing)
226             {
227                 if (MessageBox.Show("升级未完成,退出后将导致软件无法正常使用,你确定要退出吗?", "退出提示", MessageBoxButtons.YesNo) != System.Windows.Forms.DialogResult.Yes)
228                 {
229                     //取消"关闭窗口"事件
230                     e.Cancel = true;
231                 }
232             }
233         }
234     }

复制代码; "复制代码")

复制代码; "复制代码")

  1 /// <summary>
  2     /// 更新帮助类
  3     /// </summary>
  4     public class UpgradeHelper
  5     {
  6         /// <summary>
  7         /// 默认服务器地址
  8         /// 在配置文件中未找到地址时,使用此地址进行更新
  9         /// </summary>
 10         public string DefaultUrl { get; set; }
 11 
 12         public string _upgradeUrl;
 13         /// <summary>
 14         /// 获取或设置服务器地址
 15         /// </summary>
 16         public string UpgradeUrl
 17         {
 18             get
 19             {
 20                 if (string.IsNullOrWhiteSpace(_upgradeUrl))
 21                 {
 22                     return DefaultUrl;
 23                 }
 24                 return _upgradeUrl;
 25             }
 26             set
 27             {
 28                 _upgradeUrl = value;
 29             }
 30         }
 31 
 32         /// <summary>
 33         /// 本地配置文件路径
 34         /// </summary>
 35         public string Local_UpgradeXmlPath = Path.Combine(Application.StartupPath, "UpgradeList.xml");
 36 
 37         private UpgradeModel _local_UpgradeModel;
 38         /// <summary>
 39         /// 本地版本信息
 40         /// </summary>
 41         public UpgradeModel Local_UpgradeModel
 42         {
 43             get
 44             {
 45                 try
 46                 {
 47                     if (_local_UpgradeModel == null)
 48                     {
 49                         if (File.Exists(Local_UpgradeXmlPath))
 50                         {
 51                             _local_UpgradeModel = new UpgradeModel();
 52                             _local_UpgradeModel.LoadUpgrade(File.ReadAllText(Local_UpgradeXmlPath));
 53                         }
 54                     }
 55                     return _local_UpgradeModel;
 56                 }
 57                 catch (Exception ex)
 58                 {
 59                     throw new Exception(string.Format("获取本地版本文件UpgradeList.xml异常:{0}", ex.Message));
 60                 }
 61             }
 62         }
 63 
 64         private UpgradeModel _server_UpgradeModel;
 65         /// <summary>
 66         /// 服务器版本信息
 67         /// </summary>
 68         public UpgradeModel Server_UpgradeModel
 69         {
 70             get
 71             {
 72                 try
 73                 {
 74                     if (_server_UpgradeModel == null && !string.IsNullOrWhiteSpace(UpgradeUrl))
 75                     {
 76                         string resXml = MyWebResquest.GetUpgradeList(UpgradeUrl);
 77                         if (!string.IsNullOrWhiteSpace(resXml))
 78                         {
 79                             _server_UpgradeModel = new UpgradeModel();
 80                             _server_UpgradeModel.LoadUpgrade(resXml);
 81                             _server_UpgradeXml = resXml;
 82                         }
 83                     }
 84                     return _server_UpgradeModel;
 85                 }
 86                 catch (Exception ex)
 87                 {
 88                     throw new Exception(string.Format("获取服务端版本文件UpgradeList.xml异常:{0}", ex.Message));
 89                 }
 90             }
 91         }
 92 
 93         private string _server_UpgradeXml;
 94         /// <summary>
 95         /// 服务端版本配置xml
 96         /// </summary>
 97         public string Server_UpgradeXml
 98         {
 99             get
100             {
101                 return _server_UpgradeXml;
102             }
103         }
104 
105         private Dictionary<string, string> _willUpgrades;
106         /// <summary>
107         /// 待更新文件列表,如果为0,则表示不需要更新
108         /// </summary>
109         public Dictionary<string, string> WillUpgrades
110         {
111             get
112             {
113                 if (_willUpgrades == null)
114                 {
115                     _willUpgrades = new Dictionary<string, string>();
116                     //如果服务器端未获取到版本信息  则不更新
117                     if (Server_UpgradeModel != null)
118                     {
119                         if (Local_UpgradeModel == null)//本地版本信息为空 全部更新
120                         {
121                             _willUpgrades = Server_UpgradeModel.DictFiles;
122                         }
123                         else
124                         {
125                             //对比需要更新的文件
126                             foreach (var item in Server_UpgradeModel.DictFiles)
127                             {
128                                 //如果找到
129                                 if (Local_UpgradeModel.DictFiles.ContainsKey(item.Key))
130                                 {
131                                     //如果版本不匹配
132                                     if (Local_UpgradeModel.DictFiles[item.Key] != item.Value)
133                                     {
134                                         _willUpgrades.Add(item.Key, item.Value);
135                                     }
136                                 }
137                                 else
138                                 {
139                                     //没有找到
140                                     _willUpgrades.Add(item.Key, item.Value);
141                                 }
142                             }
143                         }
144                     }
145                 }
146                 return _willUpgrades;
147             }
148         }
149 
150         /// <summary>
151         /// 清空版本信息
152         /// </summary>
153         public void ClearUpgradeModel()
154         {
155             if (File.Exists(Local_UpgradeXmlPath))
156             {
157 
158                 try
159                 {
160                     string xmlStr = File.ReadAllText(Local_UpgradeXmlPath);
161                     XmlDocument xmlDoc = new XmlDocument();
162                     xmlDoc.LoadXml(xmlStr);
163 
164                     XmlNode node = xmlDoc.SelectSingleNode("Upgrade/Files");
165                     if (node != null && node.ChildNodes.Count > 0)
166                     {
167                         node.RemoveAll();
168                     }
169                     File.WriteAllText(UpgradeHelper.Instance.Local_UpgradeXmlPath, xmlDoc.InnerXml);
170                 }
171                 catch (Exception)
172                 { }
173             }
174             _local_UpgradeModel = null;
175             _server_UpgradeModel = null;
176             _willUpgrades = null;
177         }
178 
179 
180 
181         #region 单例对象
182 
183         private static UpgradeHelper _instance;
184         /// <summary>
185         /// 单例对象
186         /// </summary>
187         public static UpgradeHelper Instance
188         {
189             get
190             {
191                 if (_instance == null)
192                 {
193                     _instance = new UpgradeHelper();
194                     //初始化本地配置文件,以及服务器地址
195                     if (_instance.Local_UpgradeModel != null)
196                     {
197                         _instance.UpgradeUrl = _instance.Local_UpgradeModel.UpgradeUrl;
198                     }
199                 }
200                 return _instance;
201             }
202         }
203 
204         #endregion
205 
206         #region 静态方法
207 
208         /// <summary>
209         /// 启动主程序
210         /// </summary>
211         /// <param name="fileName"></param>
212         public static bool StartRunMain(string fileName)
213         {
214             string fullPath = fileName;
215             try
216             {
217                 Process process = GetProcess(fileName);
218                 if (process != null)//以及存在运行中的主进程
219                 {
220                     return true;
221                 }
222                 fullPath = string.Format("{0}\\{1}", Application.StartupPath, fileName);
223 
224                 ProcessStartInfo main = new ProcessStartInfo(fullPath);
225                 Process.Start(fullPath);
226                 return true;
227             }
228             catch (Exception ex)
229             {
230                 MessageBox.Show(string.Format("主程序{0}调用失败:\n{1}", fullPath, ex.Message), "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
231             }
232             return false;
233         }
234 
235         /// <summary>
236         /// 杀死进程
237         /// </summary>
238         /// <param name="process"></param>
239         public static void KillProcess(string processName)
240         {
241             if (string.IsNullOrWhiteSpace(processName)) return;
242             processName = processName.ToLower();
243             processName = processName.Replace(".exe", "");
244             //杀死主进程
245             Process[] processes = Process.GetProcesses();
246             foreach (Process process in processes)
247             {
248                 if (!string.IsNullOrWhiteSpace(process.ProcessName))
249                 {
250                     if (process.ProcessName.ToLower() == processName)
251                     {
252                         process.Kill();
253                     }
254                 }
255             }
256         }
257 
258         /// <summary>
259         /// 获取进程
260         /// </summary>
261         /// <param name="pName"></param>
262         /// <returns></returns>
263         public static Process GetProcess(string pName)
264         {
265             if (string.IsNullOrWhiteSpace(pName)) return null;
266             pName = pName.ToLower();
267             pName = pName.Replace(".exe", "");
268             //杀死主进程
269             Process[] processes = Process.GetProcesses();
270             foreach (Process process in processes)
271             {
272                 if (!string.IsNullOrWhiteSpace(process.ProcessName))
273                 {
274                     if (process.ProcessName.ToLower() == pName)
275                     {
276                         return process;
277                     }
278                 }
279             }
280             return null;
281         }
282 
283         #endregion
284 
285     }

复制代码; "复制代码")

复制代码; "复制代码")

 1 public class UpgradeModel
 2     {
 3         /// <summary>
 4         /// 初始化对象
 5         /// </summary>
 6         /// <param name="xml"></param>
 7         public void LoadUpgrade(string xml)
 8         {
 9             XmlDocument xmlDoc = new XmlDocument();
10             xmlDoc.LoadXml(xml);
11 
12             //读取UpgradeUrl
13             XmlNode node = xmlDoc.SelectSingleNode("//UpgradeUrl");
14             if (node != null)
15             {
16                 this.UpgradeUrl = node.InnerText;
17             }
18             //读取RunMain
19             node = xmlDoc.SelectSingleNode("//RunMain");
20             if (node != null)
21             {
22                 this.RunMain = node.InnerText;
23             }
24             //读取RunMain
25             node = xmlDoc.SelectSingleNode("//AutoUpgrade");
26             if (node != null)
27             {
28                 this.AutoUpgrade = node.InnerText;
29             }
30             //读取Files
31             node = xmlDoc.SelectSingleNode("Upgrade/Files");
32             this.DictFiles = new Dictionary<string, string>();
33             if (node != null && node.ChildNodes.Count > 0)
34             {
35                 foreach (XmlNode item in node.ChildNodes)
36                 {
37                     if (item.Name != "#comment")
38                     {
39                         string name = GetNodeAttrVal(item, "Name");
40                         string version = GetNodeAttrVal(item, "Version");
41                         if (!this.DictFiles.ContainsKey(name))
42                         {
43                             this.DictFiles.Add(name, version);
44                         }
45                     }
46                 }
47             }
48         }
49 
50         private string GetNodeAttrVal(XmlNode node, string attr)
51         {
52             if (node != null && node.Attributes != null && node.Attributes[attr] != null)
53             {
54                 string val = node.Attributes[attr].Value;
55                 if (!string.IsNullOrWhiteSpace(val))
56                 {
57                     return val.Trim();
58                 }
59                 return val;
60             }
61             return string.Empty;
62         }
63 
64         /// <summary>
65         /// 服务器地址
66         /// </summary>
67         public string UpgradeUrl { get; set; }
68 
69         /// <summary>
70         /// 更新完成后运行的主程序名称
71         /// </summary>
72         public string RunMain { get; set; }
73 
74         /// <summary>
75         /// 更新程序名称
76         /// </summary>
77         public string AutoUpgrade { get; set; }
78 
79         /// <summary>
80         /// 文件列表
81         /// string 文件名
82         /// string 版本号
83         /// </summary>
84         public Dictionary<string, string> DictFiles { get; set; }
85     }

复制代码; "复制代码")

服务端

服务端主Xml版本文件,包含所有的项目文件,客户端根据每个文件的版本号进行判断是否需要更新。如果需只更新某几个文件,则将对应文件的版本号更改只更高的版本号即可

复制代码; "复制代码")

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <Upgrade>
 3   <!--服务器地址-->
 4   <UpgradeUrl>http://localhost:17580</UpgradeUrl>
 5   <!--更新完成后运行的主程序名称-->
 6   <RunMain>ClientMain.exe</RunMain>
 7   <!--更新程序名称-->
 8   <AutoUpgrade>AutoUpgrade.exe</AutoUpgrade>
 9   <Files>
10     <!--更新文件列表,以Version为标志,当Version改变时,客户端启动会自动更新。子路径格式:\image\index.jpg-->
11     <File Version="01" Name="\image\index.jpg" />
12     <File Version="01" Name="ClientMain.exe" />
13     <File Version="01" Name="AutoUpgrade.exe" />
14   </Files>
15 </Upgrade>

复制代码; "复制代码")

服务端主要提供连个可以通过Http的get或post访问的路径。一个用于获取版本Xml文件内容,一个用于下载指定文件的路径。以下代码示例通过asp.net mvc进行实现。大家可以根据自己技术方式参照实现。

复制代码; "复制代码")

 1 /// <summary>
 2     /// 自动升级服务
 3     /// </summary>
 4     public class UpgradeController : Controller
 5     {
 6         //
 7         // GET: /Upgrade/
 8 
 9         /// <summary>
10         /// 获取更新文件列表
11         /// </summary>
12         /// <returns></returns>
13         public object UpgradeList()
14         {
15             string cacheKey = "Upgrade_UpgradeList.xml";
16             string resStr = CommonLibrary.CacheClass.GetCache<string>(cacheKey);
17             if (string.IsNullOrWhiteSpace(resStr))
18             {
19                 string fileName = Server.MapPath(@"~\App_Data\UpgradeList.xml");
20                 if (System.IO.File.Exists(fileName))
21                 {
22                     resStr = System.IO.File.ReadAllText(fileName);
23                     CommonLibrary.CacheClass.SetCacheMins(cacheKey, resStr, 1);
24                 }
25             }
26             return resStr;
27         }
28 
29         /// <summary>
30         /// 生成更新文件
31         /// </summary>
32         /// <returns></returns>
33         public object Create()
34         {
35             UpgradeFileManager.CreateFiles(Server.MapPath("/App_Data"));
36             return "ok";
37         }
38 
39         /// <summary>
40         /// 下载文件
41         /// </summary>
42         /// <param name="fileName"></param>
43         /// <returns></returns>
44         public object DownloadFile()
45         {
46             string fileName = PageRequest.GetString("fileName");
47             fileName = Server.MapPath(string.Format(@"~\App_Data\{0}", fileName));
48             return File(fileName, "application/octet-stream");
49         }
50 
51         /// <summary>
52         /// 异常处理
53         /// </summary>
54         /// <param name="filterContext"></param>
55         protected override void OnException(ExceptionContext filterContext)
56         {
57             filterContext.HttpContext.Response.StatusCode = 400;
58             filterContext.Result = Content(filterContext.Exception.GetBaseException().Message);
59             filterContext.ExceptionHandled = true;
60         }
61     }

复制代码; "复制代码")

复制代码; "复制代码")

 1 /// <summary>
 2     /// 此类主要作用,对于项目文件非常多,自己手动编辑很麻烦,可以采用此方法,指定目录自动生成初始化的版本文件
 3     /// </summary>
 4     public class UpgradeFileManager
 5     {
 6         /// <summary>
 7         /// 创建版本文件
 8         /// </summary>
 9         /// <param name="path"></param>
10         public static void CreateFiles(string path)
11         {
12             List<string> dirList = new List<string>();
13             GetAllDirt(path, dirList);//获取所有目录
14             dirList.Add(path);
15             System.Text.StringBuilder xml = new System.Text.StringBuilder();
16             xml.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
17             xml.AppendLine("  <Files>");
18             foreach (var diry in dirList)
19             {
20                 string[] files = Directory.GetFiles(diry);
21                 foreach (string filePath in files)
22                 {
23                     FileInfo info = new FileInfo(filePath);
24                     string name = filePath.Replace(path, "");
25                     if (info.Directory.FullName == path)
26                     {
27                         name = name.Remove(0, 1);
28                     }
29                     xml.AppendLine(string.Format("    <File Version=\"1\" Name=\"{0}\" />", name));
30                 }
31             }
32             xml.AppendLine("</Files>");
33             using (StreamWriter sw = new StreamWriter(Path.Combine(path, "UpgradeList_Temp.xml")))
34             {
35                 sw.Write(xml);
36                 sw.Close();
37             }
38         }
39 
40         /// <summary>
41         /// 获取所有子目录
42         /// </summary>
43         /// <param name="curDir"></param>
44         /// <param name="list"></param>
45         private static void GetAllDirt(string curDir, List<string> list)
46         {
47             string[] dirs = Directory.GetDirectories(curDir);
48             if (dirs.Length > 0)
49             {
50                 foreach (string item in dirs)
51                 {
52                     list.Add(item);
53                     GetAllDirt(item, list);
54                 }
55             }
56         }
57     }

复制代码; "复制代码")

结语

源代码托管于GitHub,供大伙学习参考,项目地址:https://github.com/keguoquan/AutoUpgrade。感兴趣或觉得不错的望赏个star,不胜感激!

若能顺手点个赞,更加感谢!

如有疑问可以QQ咨询:343798739


C# Winform 自动更新程序实例

第一步:检查更新

检查更新其实无非就是去比较更新包的版本和本地软件版本,如果高则更新、低则不更新。怎么获取版本号方法很多,本案例是获取软件的配置文件。

复制代码; "复制代码")

private bool CheckUpdate()
        {
            bool result = false;
            try
            {
                string Cfg = TxtRead(exePath   "\\Config.txt");
                ConfigLocal = JsonConvert.DeserializeObject<DTO_Config>(Cfg);
 
                CheckUpdateURL = ConfigLocal.AutoUpdateURL;
 
                Cfg = TxtRead(CheckUpdateURL   "\\Config.txt");
                ConfigRemote = JsonConvert.DeserializeObject<DTO_Config>(Cfg);              
 
                VersionR = ConfigRemote.Version;
                VersionL = ConfigLocal.Version;
                int VersionRemote = int.Parse(ConfigRemote.Version.Replace(".", ""));
                int VersionLocal = int.Parse(ConfigLocal.Version.Replace(".", ""));
 
                result = VersionRemote > VersionLocal;
            }
            catch { }
            return result;
        }

复制代码; "复制代码")

第二步:下载更新包

因为C/S的软件更新是面对所有用户,S端除了给C端提供基本的服务外,还可以给C端提供更新包。而这个S端可以是网络上的一个固定地址,也可以是局域网内一个公共盘。那下载更新包无非就是去访问服务端的文件,然后Copy下来或下载下来。下面给出访问网络和访问局域网两个案例:

A、访问远程网络地址这里采用的是WebClient

复制代码; "复制代码")

public void DownLoadFile()
        {
            if (!Directory.Exists(UpdateFiles))
            {
                Directory.CreateDirectory(UpdateFiles);
            }
            using (WebClient webClient = new WebClient())
            {
                try
                {
                    webClient.DownloadFileCompleted  = new AsyncCompletedEventHandler(client_DownloadFileCompleted);
                    webClient.DownloadProgressChanged  = new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
                    webClient.DownloadFileAsync(new Uri(CheckUpdateURL   "\\UpdateFile.rar"), UpdateFiles   "\\UpdateFile.rar");
                }
                catch (WebException ex)
                {
                    MessageBox.Show(ex.Message, "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

复制代码; "复制代码")

这里面应用到两个方法,DownloadProgressChanged,监听异步下载的进度;DownloadFileCompleted,监听完成异步文件下载;

复制代码; "复制代码")

private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            this.progressBarUpdate.Minimum = 0;
            this.progressBarUpdate.Maximum = (int)e.TotalBytesToReceive;
            this.progressBarUpdate.Value = (int)e.BytesReceived;
            this.lblPercent.Text = e.ProgressPercentage   "%";
        }

复制代码; "复制代码")

复制代码; "复制代码")

private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message, "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                this.lblMessage.Text = "下载完成";
                //复制更新文件替换旧文件
                DirectoryInfo TheFolder = new DirectoryInfo(UpdateFiles);
                foreach (FileInfo NextFile in TheFolder.GetFiles())
                {
                    File.Copy(NextFile.FullName, Application.StartupPath   NextFile.Name, true);
                }
              
            }
        }

复制代码; "复制代码")

B、访问服务端公共盘,直接采用File.Copy

复制代码; "复制代码")

public void GetRemoteFile()
        {
            try
            {
                DirectoryInfo TheFolder = new DirectoryInfo(CheckUpdateURL);
                FileInfo[] FileList = TheFolder.GetFiles();
                this.progressBarUpdate.Minimum = 0;
                this.progressBarUpdate.Maximum = FileList.Length;
 
                foreach (FileInfo NextFile in FileList)
                {
                    if (NextFile.Name != "Config.txt")
                    {
                        File.Copy(NextFile.FullName, exePath   "\\"   NextFile.Name, true);                      
                    }
                    this.lblMessage.Text = "更新"   NextFile.Name;
                    this.progressBarUpdate.Value  = 1;
                    this.lblPercent.Text = "更新进度... "   (this.progressBarUpdate.Value / FileList.Length) * 100   "%";
                }
                this.lblMessage.Text = "更新完成";
                //更改本地版本号为最新版本号
                ConfigLocal.Version = VersionR;
                string cfgs = JsonConvert.SerializeObject(ConfigLocal);
                TxtWrite(Application.StartupPath   "\\Config.txt", cfgs);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

复制代码; "复制代码")

第三步:替换本地文件

这一步或许在第二步中已经实现了,如果你采用的是File.Copy。替换也就是复制粘贴的问题。采用WebClient下载了zip包,那还需解压一下压缩包然后再File.Copy。


C#实现之(自动更新)

做开发的人,尤其是做客户端(C/S)系统开发的人都会遇到一个头疼的问题,就是软件的自动更新;系统发布后怎样自动的更新程序,在下有幸开发过一个自动更新程序,更新程序与任何宿主程序是完全独立的;只要在主程序里面启动更新程序就行了;更新程序也是一个可执行文件,在启动的时候可以设置是否是自动更新和是否是手动更新,自动更新的意思就是说不需要人工的干预实现从远程服务器下载更新包,而如果是手动更新就会涉及到用户点击程序中的按钮实现更新;在自动更新与手动更新中可以根据项目的需要进行选择,有的程序必须要求用户进行更新才能继续使用,所以程序自动更新是有必要的;手动更新就是用户可以随时更新程序,不需要严格的控制版本问题;下面本人就来讲一下具体的实现细节,我贴出部分代码,源码属公司财产本人不宜上传;

自动更新的目的就是将服务器上的DLL文件拷贝到本地执行目录中,并且覆盖本地同名的文件;流程很简单,但是实现起来有几个地方需要注意:

1.大批量的DLL文件怎么下载到本地来,有多个DLL文件在下载过程中如果网速慢的情况下可能出现丢包、丢文件等情况;本人的实现是将多个文件通过ICSharpCode.SharpZipLib组件进行打包,这样可以省很多事;(如:动态连接库文件dll的名称在传输过程中大小写可能会变化)

2.下载到本地了,怎么覆盖原有的同名文件;本人的实现是先同名的文件的支持删除,然后解压缩;这个过程需要临时保存删除的文件,防止操作失败程序无法启动,要注意有事务性的原理;

3.如果更新的文件不只是单单的DLL文件可能还有一些无限极的文件夹;本人的实现是如果存在同名的文件夹,直接递归的删除,然后将其解压缩到目录中;由于压缩包解压后的顶级目录是压缩文件的名称,所有在复制的过程中需要注意目录的层次关系;

下面我们来走一下实现的整个流程,虽然没有给出整个源码,但是如果看完这篇文章的你基本实现起来没什么大问题了;

为了部署方便我建议大家麻烦点实现一个部署文件的工具,将所有的文件直接打包在里面同时生成服务器端的版本信息文件;

利用这个工具就很方便的实现了对文件进行压缩、生成HASH值、版本文件、更新地址等信息;

这个XML中保存的是服务当前的版本信息、更新文件的名称、更新文件的HASH值,为什么需要HASH就是怕更新文件在某些情况下被人调包了,如果所有的客户端更新后后果很严重;所以我们必须带上HASH值;

工具生成两个文件,一个是版本文件一个是更新包,服务器的任务已经完成,下面就是具体的客户端的实现;

为了知道何时需要进行版本更新所以要在客户端程序目录中保存一份用来记录版本信息的文件;

文件中保存着当前本地的版本号、服务器的更新地址、宿主程序的名称,需要宿主的名称就能在更新的时候将宿主程序重进程中枚举出来然后关掉,这样就不影响我们更新了,当然也可以实现宿主程序不关闭的情况下更新,如果用到某些已经被宿主程序占用的情况会直接影响更新流程,所以以防万一关了为妙;

这是客户端版本文件中保存的信息;

我们上面说了,更新分为手动和自动,我们先来说手动更新吧,手动更新就是需要用户自己去点击更新按钮然后开始更新,这个问题我们可以利用进程的参数传递解决;

当然在更新程序里面需要有这方面的逻辑判断;

入口的地方我们进行判断,更新方式;这里的下载远程更新包是用WebClient对象,也可以用其他的基于Socket的对象;更新开始之前需要先判断本地的版本号是否小于远程版本号,如果小于在进行更新;

因为下载的过程是异步的所以需要用到后台线程建议大家使用System.ComponentModel.BackgroundWorker这个后台线程对象,他对Thread进行了很好的封装;下面来看一下核心的流程代码:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869//开始辅助线程操作`        private` `void` `Back_thread_DoWork(object sender, DoWorkEventArgs e)        {            try            {                //实例化下载对象                downclient = new` `WebClient();                downclient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(downclient_DownloadProgressChanged);                downclient.DownloadFileCompleted += new` `AsyncCompletedEventHandler(downclient_DownloadFileCompleted);                ``//下载远程更新包down.zip压缩文件\放在应用程序目录下\相应界面事件                downclient.DownloadFileAsync(new` `Uri(Util.GetUpdateUrl() + "down.zip"), Util.GetDictiory() + "\down.zip");            }            catch` `(Exception err) { System.Diagnostics.Debug.WriteLine(err); }        }        //在异步下载结束时触发该事件        void` `downclient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)        {            try            {                 if` `(e.Error != null)                {                    eventLog1.WriteEntry(e.Error.ToString());                    MessageBox.Show("在进行远程更新时,发生错误""信息提示", MessageBoxButtons.OK, MessageBoxIcon.Error);                }                else                {                    Util.KillProcess();//关闭主进程                    //验证哈希值                    if (Util.IsHash(Util.GetHash(Util.GetDictiory() + "\\down.zip"), FileWork.GetDownHash()))                    {                        //删除无用压缩文件                        File.Delete(Util.GetDictiory() + "\\down.zip");                        //删除无用版本文件                        File.Delete(Util.GetDictiory() + "\\ServerUpdateFiles.xml");                        MessageBox.Show("远程服务器更新包已发生变化,无法更新""信息提示", MessageBoxButtons.OK, MessageBoxIcon.Error);                        eventLog1.WriteEntry("远程服务器中的更新包在制作和下载时间段中数据包发生变化,为了安全期间不给予下载!");                        this.Close();                    }                    else                    {                        //解压压缩包文件                        ReduceToUnReduceFile.unZipFile(Util.GetDictiory() + "\down.zip", Util.GetDictiory());                        //删除压缩包文件                        File.Delete(Util.GetDictiory() + "\down.zip");                        //检查文件夹层次结构                        FileWork.LookFiles(Util.GetDictiory() + "\down", Util.GetDictiory());                        //订阅复制文件事件                        FileWork.CopyFileEvent += new FileWork.CopyFileDelegate(FileWork_CopyFileEvent);                        //递归复制文件                        FileWork.CopyFiles(Util.GetDictiory() + "\\down", Util.GetDictiory());                        //删除临时文件夹                        FileWork.DeleteFiles(Util.GetDictiory() + "\\down");                        //如果库结构更新成功,则才能更新程序的版本号,否则下次继续更新                        if (EventChainReference.GlobalEventChain.OnAutoUpdateDb())                            //更新本地版本号信息                            Util.UpdateLocalXml();                        File.Delete(Util.GetDictiory() + "\\ServerUpdateFiles.xml");                         MessageBox.Show("升级成功!""信息提示");                        Util.StartProcess();                        isupdate = true;` `                    }                }            }            catch (Exception err) { eventLog1.WriteEntry(err.ToString()); }            Application.Exit();        }`

这部分代码是串联整个过程的代码;

自动更新大概就讲完了,几个关键的地方都给出了,希望对大家开发自动更新程序有帮助;

分类: C#

Last modification:January 14, 2023
如果觉得我的文章对你有用,请随意赞赏