最近做的一个项目中需要用到客户端自动更新功能,最初的想法是利用ClickOnce技术来完成,但在实践中发现根本行不能,原因如下:
1)项目应用到了DevExpress控件包,用ClickOnce发布的自动更新程序,客户在安装时报在GAC中找不到控件dll的错。
2)ClickOnce安装无法实现根据用户安装时录入的参数(比如数据库服务器名、数据库用户名和密码等)来动态修改配置文件的功能。
3)最后一下其实不重要了,就是ClickOnce无法实现用户自定义安装文件夹。
最后决定放弃使用ClickOnce,使用ftp方式进行,实现思路如下:用户启动程序时,先运行update.exe,该文件会自动比较本地配置文件和ftp服务器上配置文件的异同,会自动下载上次更新后变化的文件以及新加入的文件。(因为都是基本配置文件,所以开发了一个配置文件生成工具,用户只需要选择根目录后,就会自动生成配置文件。)文件下载结束后,再启动实际的客户端程序。
程序的主要代码如:
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.IO;
5using System.Net;
6using System.Threading;
7using System.Windows.Forms;
8
9namespace Update
10{
11 /// <summary>
12 /// Description:
13 /// Author: ZhangRongHua
14 /// Create DateTime: 2009-6-21 12:25
15 /// UpdateHistory:
16 /// </summary>
17 public partial class frmUpdate : Form
18 {
19 #region Fields
20
21 private const string CONFIGFILE = "update.xml";
22 private const string UPDATEDIR = "PMS";
23 private string appPath = Application.StartupPath;
24 private List<ErrorInfo> errorList = new List<ErrorInfo>();
25 private string locFile = String.Concat(Application.StartupPath, "\\", CONFIGFILE);
26 private string tmpUpdateFile = "TmpUpdate.xml";
27 private List<string> updateList;
28 private string updateTmpPath = string.Concat(Path.GetTempPath(), "\\", UPDATEDIR);
29 private string url = String.Empty;
30
31 private FTP ftp = null;
32
33 #endregion
34
35 #region Delegates
36
37 public delegate void AsycDownLoadFile(string srcFile, string destFile, int i);
38
39 public delegate void ExecuteUpdateFiles(string srcPath, string destPath);
40
41 public delegate void UpdateComplete();
42
43 public delegate void UpdateUI(int i, string message);
44
45 #endregion
46
47 public event UpdateComplete OnUpdateComplete;
48
49 #region Constructor
50
51 public frmUpdate()
52 {
53 InitializeComponent();
54 OnUpdateComplete += new UpdateComplete(frmUpdate_OnUpdateComplete);
55 }
56
57 #endregion
58
59 #region Event Handler
60
61 private void frmUpdate_Load(object sender, EventArgs e)
62 {
63 if(Directory.Exists(updateTmpPath))
64 {
65 Directory.Delete(updateTmpPath, true);
66 }
67
68 // 如果有主程序启动,则关闭
69 Process[] ps = Process.GetProcesses();
70 foreach (Process p in ps)
71 {
72 //MessageBox.Show(p.ProcessName);
73 if (p.ProcessName.ToLower() == "wat.pms.winform")
74 {
75 p.Kill();
76 break;
77 }
78 }
79
80 GetUpdateFiles();
81 }
82
83 private void frmUpdate_OnUpdateComplete()
84 {
85 ExecuteUpdateFiles dExecuteUpdateFiles = new ExecuteUpdateFiles(ExecuteUpdate);
86 Invoke(dExecuteUpdateFiles, new object[] {updateTmpPath, appPath});
87 }
88
89 private void frmUpdate_Shown(object sender, EventArgs e)
90 {
91 Thread updateThread = new Thread(new ThreadStart(DownLoadUpdateFiles));
92 updateThread.SetApartmentState(ApartmentState.STA);
93 updateThread.IsBackground = true;
94 Thread.Sleep(500);
95 updateThread.Start();
96
97
98 }
99
100 #endregion
101
102 #region Private Methods
103
104 /// <summary>
105 /// 将目标文件替换为本地文件
106 /// <remark>
107 /// Author:ZhangRongHua
108 /// Create DateTime: 2009-6-21 10:28
109 /// Update History:
110 /// </remark>
111 /// </summary>
112 /// <param name="srcFile">The SRC file.</param>
113 /// <param name="destFile">The dest file.</param>
114 private void DownLoadFile(string srcFile, string destFile)
115 {
116 if(ftp == null )
117 {
118 ftp = new FTP(Updater.URL, "", Updater.User, Updater.Password);
119 }
120
121
122
123 if(!ftp.Connected)
124 {
125 MessageBox.Show("无法连接远程服务器,无法更新程序", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
126 Process.Start(Updater.MainProgram);
127 Close();
128
129 }
130
131 // 得到服务器端的配置文件
132 ftp.Get(srcFile, updateTmpPath, destFile);
133
134
135
136 }
137
138 /// <summary>
139 /// 得到需要更新的文件清单
140 /// <remark>
141 /// Author:ZhangRongHua
142 /// Create DateTime: 2009-6-21 10:29
143 /// Update History:
144 /// </remark>
145 /// </summary>
146 private void GetUpdateFiles()
147 {
148 Updater.GetBaseInfo(locFile);
149 url = Updater.URL;
150 string svrFile = String.Concat(url, CONFIGFILE);
151
152 if (String.IsNullOrEmpty(svrFile))
153 {
154 BroadCastOnUpdateComplete();
155 return;
156 }
157
158 Directory.CreateDirectory(updateTmpPath);
159 try
160 {
161 DownLoadFile(CONFIGFILE, tmpUpdateFile);
162 }
163 catch (Exception ex)
164 {
165 MessageBox.Show("无法连接远程服务器,无法更新程序", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
166 Process.Start(Updater.MainProgram);
167 Close();
168 }
169
170 updateList = Updater.GetUpdateFileList(locFile, tmpUpdateFile);
171 if (updateList == null || updateList.Count < 1)
172 {
173 BroadCastOnUpdateComplete();
174 return;
175 }
176
177 pbUpdate.Maximum = updateList.Count;
178 pbUpdate.Minimum = 0;
179 lblInfo.Text = String.Concat(updateList.Count, "个文件需要更新!");
180 lblInfo.BringToFront();
181 lblState.BringToFront();
182 lblInfo.Update();
183
184 pbUpdate.Maximum = updateList.Count;
185 pbUpdate.Minimum = 0;
186
187 for (int i = 0; i < updateList.Count; i++)
188 {
189 string file = updateList[i];
190 ListViewItem lvItem = new ListViewItem();
191 lvItem.Text = file;
192 lvItem.SubItems.Add(Updater.MainProgramVersion);
193 lvUpdateList.Items.Add(lvItem);
194 }
195 }
196
197 private void UpdateUIInfo(int i, string message)
198 {
199 pbUpdate.Value = i + 1;
200 lblState.Text = message;
201 lblState.Update();
202 }
203
204 private void BroadCastOnUpdateComplete()
205 {
206 if (OnUpdateComplete != null)
207 {
208 OnUpdateComplete();
209 }
210 }
211
212 private void DownLoadUpdateFiles()
213 {
214 if (updateList == null || updateList.Count < 1)
215 {
216 BroadCastOnUpdateComplete();
217 return;
218 }
219
220 try
221 {
222 UpdateUI dUpdateUI = new UpdateUI(UpdateUIInfo);
223 AsycDownLoadFile dAsycDownLoadFile = new AsycDownLoadFile(DownLoadFile);
224 for (int i = 0; i < updateList.Count; i++)
225 {
226 string file = updateList[i];
227 string destFile = String.Concat(updateTmpPath, "\\", file);
228 string destFileName = destFile.Substring(destFile.LastIndexOf("/") + 1);
229 string srcFile = String.Concat(url, file);
230 var srcFileName = file.Trim('/');
231 destFile = destFile.Replace("/", "\\");
232 Directory.CreateDirectory(destFile.Substring(0, destFile.LastIndexOf("\\")));
233 string curentFile = String.Concat("正在更新第", i + 1, "/", updateList.Count, "个", file);
234
235 Invoke(dAsycDownLoadFile, new object[] { srcFileName, srcFileName, i });
236 Thread.Sleep(50);
237 Invoke(dUpdateUI, new object[] {i, curentFile});
238 }
239 }
240 catch (Exception ex)
241 {
242 Debug.WriteLine(ex.Message);
243 }
244
245 BroadCastOnUpdateComplete();
246 }
247
248
249
250 private void CopyUpdateFiles(string srcPath, string destPath)
251 {
252 string[] files = Directory.GetFiles(srcPath);
253 for (int i = 0; i < files.Length; i++)
254 {
255 string srcFile = files[i];
256 string destFile = string.Concat(destPath, "\\", Path.GetFileName(srcFile));
257 try
258 {
259 File.Copy(srcFile, destFile, true);
260 }
261 catch (System.IO.IOException ex)
262 {
263
264
265 }
266
267 }
268
269 string[] dirs = Directory.GetDirectories(srcPath);
270 for (int i = 0; i < dirs.Length; i++)
271 {
272 srcPath = dirs[i];
273 string tmpDestPath = String.Concat(destPath, "\\", Path.GetFileName(srcPath));
274 Directory.CreateDirectory(tmpDestPath);
275 CopyUpdateFiles(srcPath, tmpDestPath);
276 }
277 }
278
279
280
281 /// <summary>
282 /// 更新完成后,要执行的动作(将下载的文件从临时目录复制到主目录,重启主程序)
283 /// <remark>
284 /// Author:ZhangRongHua
285 /// Create DateTime: 2009-6-21 12:25
286 /// Update History:
287 /// </remark>
288 /// </summary>
289 /// <param name="srcPath">The SRC path.</param>
290 /// <param name="destPath">The dest path.</param>
291 private void ExecuteUpdate(string srcPath, string destPath)
292 {
293 if (errorList != null && errorList.Count < 1)
294 {
295 lblInfo.Text = "正在执行更新";
296 lblInfo.Update();
297 CopyUpdateFiles(srcPath, destPath);
298 File.Copy(tmpUpdateFile, locFile, true);
299 }
300 Process.Start(Updater.MainProgram);
301
302 Close();
303 }
304
305 private void DownLoadFile(string srcFile, string destFile, ListViewItem lvItem)
306 {
307 try
308 {
309 DownLoadFile(srcFile, destFile);
310 lvItem.SubItems.Add("Ok");
311 }
312 catch (Exception ex)
313 {
314 Debug.WriteLine(ex.Message);
315 lvItem.SubItems.Add("fail");
316 ErrorInfo errorInfo = new ErrorInfo();
317 errorInfo.File = srcFile;
318 errorInfo.ErrorLevel = ErrorLevel.Serious;
319 errorInfo.Message = ex.Message;
320 errorList.Add(errorInfo);
321 }
322 }
323
324 private void DownLoadFile(string srcFile, string destFile, int i)
325 {
326 ListViewItem lvItem = lvUpdateList.Items[i];
327
328 lvUpdateList.Items[i].EnsureVisible();
329 try
330 {
331 DownLoadFile(srcFile, destFile);
332 lvItem.SubItems.Add("Ok");
333 }
334 catch (Exception ex)
335 {
336 Debug.WriteLine(ex.Message);
337 lvItem.SubItems.Add("fail");
338 ErrorInfo errorInfo = new ErrorInfo();
339 errorInfo.File = srcFile;
340 errorInfo.ErrorLevel = ErrorLevel.Serious;
341 errorInfo.Message = ex.Message;
342 errorList.Add(errorInfo);
343 MessageBox.Show(destFile);
344 }
345 }
346
347 #endregion
348 }
349}