Windows程序通用自动更新模块(C#,.NET4.5以上)
本通用自动更新模块适合所有Windows桌面程序的自动更新,不论语言,无论Winform还是wpf。
一、工作流程:
1. 主程序A调起升级程序B
2. B从服务器获取更新程序列表,打印更新信息。
3. B杀死A进程(此步骤可以放在步骤2~5任意位置)
4. B根据更新信息中指示的地址,下载更新程序包(.zip文件)
5. 解压缩.zip文件到一个新创建的文件夹
6. 将解压后的文件拷贝到原始文件目录,做替换。
7. 删除下载的.zip文件以及解压后创建的文件夹
8. B打开A
二、源码介绍:
升级程序B的实现:
更新信息列表用于存储版本信息,以及更新说明信息。通常为json或xml文件。本文为json文件。
存储列表信息的类
public class UpdateItem { public string Version { get; set; } //版本号 public string UpdateContent { get; set; } //更新信息 public string DownloadUri { get; set; } //更新包的下载地址 public string Time { get; set; } //更新时间 public string Size { get; set; } //更新包大小 }
获取更新信息使用WebClient.DownloadData(Uri),其中使用Newtonsoft.Json进行json序列化及反序列化。
WebClient client = new WebClient(); byte[] data = client.DownloadData(uri); //json转为UpdateItem类对象 UpdateInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateItem>(Encoding.UTF8.GetString(data));
获取更新信息以后在界面上进行输出。
下面介绍一下生成更新信息的json文件并使用FTP上传到服务器的代码。json文件也可以手动写,手动上传。不是重点,不想看可以跳到下一部分。
UpdateItem UpdateInfo = new UpdateItem(); ... //赋值 string json = JsonConvert.SerializeObject(UpdateInfo); //连接服务器 FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://..."); request.Method = WebRequestMethods.Ftp.UploadFile; request.Credentials = new NetworkCredential("账号", "密码"); // 复制字符 byte[] fileContents = Encoding.UTF8.GetBytes(json); request.ContentLength = fileContents.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write(fileContents, 0, fileContents.Length); requestStream.Close(); FtpWebResponse response = (FtpWebResponse)request.GetResponse(); response.Close();
下载更新包zip文件
下载使用WebClient.DownloadFile(Uri, string)
string dir = System.IO.Directory.GetCurrentDirectory(); //程序所在文件夹路径 string zipfile = System.IO.Path.Combine(dir, "file.zip"); //下载后zip文件的完整路径 WebClient client = new WebClient(); client.DownloadFile(UpdateInfo.DownloadUri, zipfile); return true;
解压缩zip文件
解压缩需要用到System.IO.Compression.ZipFile,需要.NET4.5及以上。如果用不了(找不到ZipFile),请检查是否已添加引用System.IO.Compression.FileSystem程序集。
代码只有一行,两个参数分别为zip文件完整路径,解压后的文件夹完整路径。
ZipFile.ExtractToDirectory(zipfile, extractPath);
使用第三方压缩软件生成的.zip文件有可能会解压失败,建议使用下方代码生成。两个参数分别为需要压缩的文件夹完整路径,生成的zip文件完整路径。
ZipFile.CreateFromDirectory(startPath, zipfile);
拷贝解压后的文件到原始目录
foreach (string item in Directory.GetFiles(extractPath)) { File.Copy(item, System.IO.Path.Combine(dir, System.IO.Path.GetFileName(item)), true); }
删除临时文件
File.Delete(zipfile); DirectoryInfo di = new DirectoryInfo(extractPath); di.Delete(true);
B进程杀死A进程
string appname = "file"; //A名字,不要路径,不要.exe Process[] processes = Process.GetProcessesByName(appname); foreach (var p in processes) p.Kill();
B进程调起A进程
System.Diagnostics.Process.Start(@"path"); //完整路径
主要流程就是这些,建议用异步操作实现。
现在上号比较少,私信很多要代码的都没回…以后都会把完整代码附上的。
下面是完整代码。
private Uri uri = new Uri(@"http://..."); //更新信息列表文件路径 private UpdateItem UpdateInfo; //UpdateItem类的定义在前面第一部分说明 public MainWindow() { InitializeComponent(); LoadingData(); } //用户按下更新按钮 private async void Button_Click(object sender, RoutedEventArgs e) { //下载 Tb_State.Text = "正在下载新版本文件,请耐心等待"; string dir = System.IO.Directory.GetCurrentDirectory(); string zipfile = System.IO.Path.Combine(dir, "File.zip"); bool success = await Task.Run(() => { try { WebClient client = new WebClient(); client.DownloadFile(UpdateInfo.DownloadUri, zipfile); return true; } catch (Exception) { return false; } }); if (success) Tb_State.Text = "文件已下载,正在复制文件"; else { Tb_State.Text = "下载新版本文件失败,请重试"; return; } //杀死主程序进程 string appname = "File"; Process[] processes = Process.GetProcessesByName(appname); foreach (var p in processes) p.Kill(); //解压缩+拷贝+删除 bool success2 = await Task.Run(() => { try { string extractPath = System.IO.Path.Combine(dir, "NewVersion"); ZipFile.ExtractToDirectory(zipfile, extractPath); foreach (string item in Directory.GetFiles(extractPath)) File.Copy(item, System.IO.Path.Combine(dir, System.IO.Path.GetFileName(item)), true); File.Delete(zipfile); DirectoryInfo di = new DirectoryInfo(extractPath); di.Delete(true); return true; } catch (Exception) { return false; } }); if (success2) Tb_State.Text = "更新完成,您可以点击下方按钮启动应用"; else Tb_State.Text = "复制更新文件出错,请重试"; } //读取更新列表文件 private async Task<bool> LoadingData() { Tb_State.Text = "正在下载更新文件信息"; bool success = await Task.Run(() => { try { WebClient client = new WebClient(); byte[] data = client.DownloadData(uri); UpdateInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateItem>(Encoding.UTF8.GetString(data)); return true; } catch (Exception) { return false; } }); if (success) Tb_State.Text = "已获取新版本信息,可进行更新"; else Tb_State.Text = "无法获取更新信息"; return success; } //更新完成,更新程序B调起主程序A private void Btn_Open_Click(object sender, RoutedEventArgs e) { System.Diagnostics.Process.Start(@"D:\..."); //A程序完整路径 }
再贴一个用于下载过程中,能实时显示已下载文件大小的代码
此部分需写在下载部分之前。
System.Windows.Threading.DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer(); dt.Interval = TimeSpan.FromMilliseconds(100); //100毫秒 dt.Tick += (x, y) => { if (File.Exists(zipfile) == false) return; string size = ((new FileInfo(zipfile).Length) / 1024.0 / 1024).ToString("f2"); if (download == false) //是否下载完毕 Tb_State.Text = size + "MB / " + UpdateInfo.Size; //输出:已下载/总大小 else dt.Stop(); }; dt.Start();
代码部分执行时,再次执行代码可能会报错。可能原因包括下载zip文件后未删除再次下载报错等。如要做项目还有很多地方需要进行判断,如检测路径的合法性等。为了代码便于阅读,没有加入这些错误检测部分,需自行补充。几乎所有操作前都应判断路径合法性,以及目标位置的文件是否存在(是否需要先删除)等。只能说是在输入合法的情况下,完整执行此代码是没有问题的。
代码通过Visual Studio 2019测试,.NET4.5。