C# 实现客户端程序自动更新
看到一篇不错的帖子,可能以后会用到,果断收藏
文章来源 博客园 jenry(云飞扬)http://www.cnblogs.com/jenry/archive/2006/08/15/477302.html
由于微软提供的更新程序使用不方便,所以又写了此程序。此程序是本人一年前所写的一段程序,当时在开发一个CS版本报价系统,当时由于开发过程仓促,代码可能有点不是太规范此程序编译后只有一下AutoUpdate.exe文件与一个配置文件UpdateList.xml,主要通过本地程序与服务端程序文件的版本号来升级与更新本地程序文件。
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#自动更新程序
主要功能介绍
实现文件的自动更新。主要功能:
- 支持整包完全更新,即客户端只需输入一个服务器地址,即可下载所有文件。
- 支持增量更新,即只更新指定的某几个文件。
- 支持自动更新程序的更新
更新界面如图:
客户端
/// <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#