最近做的一个项目中需要用到客户端自动更新功能,最初的想法是利用ClickOnce技术来完成,但在实践中发现根本行不能,原因如下:
1)项目应用到了DevExpress控件包,用ClickOnce发布的自动更新程序,客户在安装时报在GAC中找不到控件dll的错。
2)ClickOnce安装无法实现根据用户安装时录入的参数(比如数据库服务器名、数据库用户名和密码等)来动态修改配置文件的功能。
3)最后一下其实不重要了,就是ClickOnce无法实现用户自定义安装文件夹。
最后决定放弃使用ClickOnce,使用ftp方式进行,实现思路如下:用户启动程序时,先运行update.exe,该文件会自动比较本地配置文件和ftp服务器上配置文件的异同,会自动下载上次更新后变化的文件以及新加入的文件。(因为都是基本配置文件,所以开发了一个配置文件生成工具,用户只需要选择根目录后,就会自动生成配置文件。)文件下载结束后,再启动实际的客户端程序。
程序的主要代码如:

Code
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics;
4
using System.IO;
5
using System.Net;
6
using System.Threading;
7
using System.Windows.Forms;
8
9
namespace 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
Fields#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
Delegates#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
Constructor#region Constructor
50
51
public frmUpdate()
52
{
53
InitializeComponent();
54
OnUpdateComplete += new UpdateComplete(frmUpdate_OnUpdateComplete);
55
}
56
57
#endregion
58
59
Event Handler#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
Private Methods#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
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)