C# 中的应用程序在线文件自动更新






4.89/5 (77投票s)
检测可用更新并通过在线下载安装更新 - ClickOnce 的替代方案
引言
如果您想为用户提供更新应用程序的方法,有几种不同的选择,其中包括:
- 让用户手动下载和安装更新 - 通过电子邮件通知他们,或者让他们自己在线检查。
- 使用 ClickOnce。
- 允许应用程序在线检查更新,并自动更新,或者提供一键更新功能 - 无需使用 ClickOnce。
本文演示了以上列表中的最后一种方法 - 在不使用 ClickOnce 的情况下,为您的应用程序提供一键更新或自动更新功能。
更新是通过一个类和一个应用程序完成的,该应用程序安装在线更新,并通过UpdateMe
应用程序(随本文下载提供)进行说明。
通过UpdateMe
应用程序展示的类和update
应用程序,您可以毫不费力地为您的软件用户提供自动化更新。
我已将UpdateMe
应用程序保持得尽可能简单,以便您能够使用代码,而无需花费太多时间来筛选与您的应用程序相关的内容。
要使用UpdateMe
应用程序,您需要能够在线存储文件(请阅读下一行)。
注意:在本文首次发布时,我将相关文件(版本和更新)放在了一个公开的Dropbox文件夹中,该文件夹由UpdateMe
文本框中的默认值指向 - 由于一切事物都是短暂的,此文件夹可能无法长期可用,因此我建议您使用自己的网络空间。
提示:使用本文所述和包含的类、方法和应用程序的一个优点是,您可以将版本信息和下载文件存储在免费文件共享网站(如Dropbox
)上,而无需花费一分钱来托管更新信息或下载本身。
请参阅下载文件中包含的readme.txt文档,了解如何在您自己的网络空间中设置版本和下载文件。
背景
此代码源于我编写的 TeboCam 摄像头安全应用程序,该应用程序即将开源。
实现形式为以下三个关键组件:
- 一个在线版本文件 -
update
类使用它来检查在线更新,并且还包含在线更新的位置信息。 - 一个类 -
update
- 包含查询在线版本文件的方法。对于本文,该类包含在示例UpdateMe
应用程序中。 - 一个应用程序 -
update
- 用于从在线文件夹下载更新,并重新启动指定的应用程序(在此例中为UpdateMe
)。
本文主要介绍update
类及其在示例UpdateMe
应用程序中的使用。
处理顺序如下:
- 主应用程序(在此例中为
UpdateMe
)下载版本文件并检查是否有可用更新。 - 如果存在可用更新,则调用
update
应用程序,并将更新文件的位置和名称以及可选的命令行和更新完成后要启动的应用程序名称传递给它。然后主应用程序关闭。 update
应用程序确保主应用程序未运行,并在运行时终止它,然后下载文件并使用可选的命令行参数启动指定的应用程序。
这真的很简单(他在花了整个周末整理代码后说道……)。
最初,当我编写这段代码时,有人问:“如何更新更新应用程序?” - 于是我回到代码中 - 解决方案其实很简单。
与update
应用程序相关的任何文件都以一个前缀保存在线 - update
类包含一个方法,该方法只需重命名这些文件以覆盖update
应用程序。
因此,通过一个按钮,就可以更新您的主应用程序以及update
应用程序本身。
有关处理此问题的更多信息,请参见下文。
更新有两种模式:
- 您可以立即下载文件 - 这是我们用来下载版本文件以检查是否有在线更新的方法。
- 您可以关闭程序以进行应用程序更新。
此应用程序使用了Ionic zip开源库来解压缩文件。
我还使用了通过 Google 找到的方法等 - 如果您发现任何需要归功于他人的内容,请告诉我。
Using the Code
update
类包含用于查询版本文件的方法。
您需要在线放置的版本文件采用管道分隔格式。
处理顺序如下:
- 检查是否存在带有指定前缀的新
update
应用程序文件,如果存在,则重命名这些文件 - 这使我们能够更新update
应用程序。 - 从在线版本文件中获取更新信息。
然后,我们可以选择,如果版本文件包含指向新文件的信息: - 下载文件而不重启应用程序,或
- 调用
update
应用程序下载文件。
获取更新可用性信息:getUpdateInfo
方法下载一个管道分隔文件,将其分割成列,并放入一个List
对象。
需要注意的一点是读取数据的起始行号 - 这是一个零基索引,意味着第一行为 0。
在下面的示例中,我们从第二行开始读取 - 我更喜欢有一个标题行,因为当我修改版本文件时,我可以查看文件就知道哪些信息对应哪里。
info = update.getUpdateInfo(downloadsurl.Text, versionfilename.Text,
Application.StartupPath + @"\", 1);
由于读取版本文件的结果是getUpdateInfo
方法返回的List
对象,因此您可以将任何您需要的信息放入此文件中用于您的更新信息。
我建议一开始保持简单,只包含版本、下载 URL 和下载文件名作为更新所需的唯一重要信息。
app|version|release date|url|file
updateme|1.2|24/09/2011|http://changeThisUrl.com/downloadFiles/|updateme.zip
关于保存此在线版本文件中的信息,有一点需要注意,它允许您将用户的应用程序指向您想要的任何位置获取新应用程序。
提示 - 运行UpdateMe
应用程序时,请修改“此版本号。”文本框以测试更新是否被拾取。
关于安装更新,存在两种方法:
installUpdateNow
- 这将安装下载的文件,而无需重启应用程序;installUpdateRestart
- 这将通过更新应用程序安装下载的文件,并导致应用程序重启。
installUpdateRestart
方法是UpdateMe
应用程序的核心功能 - 通过update
应用程序安装更新并重启新版本的UpdateMe
应用程序。
此方法启动update
应用程序 - 传递参数,指示要下载哪个文件以及文件下载完成后要启动哪个进程。
public static void installUpdates(string downloadsURL, string filename,
string destinationFolder, string processToEnd, string postProcess,
string startupCommand, string updater)
{
string cmdLn = "";
cmdLn += "|downloadFile|" + filename;
cmdLn += "|URL|" + downloadsURL;
cmdLn += "|destinationFolder|" + destinationFolder;
cmdLn += "|processToEnd|" + processToEnd;
cmdLn += "|postProcess|" + postProcess;
cmdLn += "|command|" + @" / " + startupCommand;
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = updater;
startInfo.Arguments = cmdLn;
Process.Start(startInfo);
}
webdata
类由UpdateMe
应用程序和update
应用程序共同使用。
此类包含从在线位置下载数据的方法。
我认为值得指出的是自定义bytesDownloaded
事件的使用。
如果您对 .NET 比较陌生,那么自定义事件是我强烈推荐使用和理解的内容(即理解如何使用它们以及它们为何如此方便)。
为了使update
应用程序中的进度条能够正确显示数据下载的进度,我们需要能够指定已下载字节数以及下载的总字节数。
我们首先定义一个委托和一个保存自定义事件参数的类 - ByteArgs
类继承自EventArgs
类。
public delegate void bytesDownloaded(ByteArgs e);
public class ByteArgs : EventArgs
{
private int _downloaded;
private int _total;
public int downloaded
{
get
{
return _downloaded;
}
set
{
_downloaded = value;
}
}
public int total
{
get
{
return _total;
}
set
{
_total = value;
}
}
}
当我们开始下载数据时,我们会创建一个ByteArgs
类的新实例,并分配下载和总字节数。
调用事件时需要注意的一点是 - 我们首先测试它是否为null
- 如果我们不测试事件是否为null
,而事件未被使用,我们将收到一个NullReferenceException
。
这一点很重要,因为update
应用程序为了进度条而使用了bytesDownloaded
事件,而UpdateMe
应用程序则没有。
//let us declare our downloaded bytes event args
ByteArgs byteArgs = new ByteArgs();
byteArgs.downloaded = 0;
byteArgs.total = dataLength;
//we need to test for a null as if an event is not consumed, we will get an exception
if (bytesDownloaded != null) bytesDownloaded(byteArgs);
在本篇文章前面,我提到了这个问题 - “如何更新更新应用程序?” Update
类中的一个名为updateMe
的方法解决了这个问题。
update.updateMe(updaterPrefix, Application.StartupPath + @"\");
我们只需将update
应用程序文件的前缀及其位置传递给此方法 - 前缀允许我们在update
应用程序运行时下载这些文件,而不会遇到任何文件共享/锁定问题。
在UpdateMe
启动时调用此方法,可以安装与update
应用程序相关的任何新文件。
提示:您会注意到,本文提供的下载文件um.zip中包含带有M1234_
前缀的文件 - 这些文件将在更新发布后,在运行UpdateMe
应用程序时,移除前缀并替换或补充应用程序文件夹中的任何文件。
关注点
最初,我使用一个 HTML 网页来存放更新信息 - 代码会检查这个页面是否有更新。
这有时会导致连接失败的问题 - 所以我决定将更新信息放在一个可以下载的管道分隔的在线文件中,不知何故,文件下载选项更可靠 - 103 字节的下载(版本文件应该在这个大小左右)不会消耗太多带宽。
本文未涵盖的一个领域是清理应用程序文件夹中的旧文件问题 - 这可以在主应用程序运行updateMe
方法时处理(我所说的旧文件是指前一个版本应用程序使用过但已不再使用的文件)。
或者,您可以将旧文件留在用户机器上 - 我承认这并不理想,但是考虑到删除用户故意放在应用程序文件夹中的文件的风险,允许这些旧文件继续存在可能更好。
我真心希望这能对他人有所帮助,并请告诉我哪些方面可以改进。
历史
- 2011年10月8日:初版
- 2021年2月9日:文章更新