C# .NET 自动更新应用程序启动器






4.53/5 (16投票s)
轻松地允许您发布应用程序的更新版本供远程客户端下载,而无需运行另一个安装程序。
引言
当我们开发基于 Web 的解决方案时,通常由我们负责更新安装程序以使用当前版本。我们修复一些错误,添加一个功能,创建一个补丁,然后进行部署,以便我们的客户可以在不中断的情况下继续使用我们的软件。但当我们有需要安装我们软件的客户时,我们仍然需要为他们提供更新。即使是独立应用程序,也会出现旧版本导致的问题。如果一个应用程序连接到外部系统来推送或拉取数据,问题可能会更糟——甚至导致数据丢失。您的应用程序的消费者都很忙,他们并不总是记得检查您的软件更新。
为什么我需要一个更新程序?
我们需要一个 .NET 解决方案,允许我们的客户使用我们软件的最新版本。我们一直在处理一个移动应用程序跨多个版本同步其数据的问题,因此我们制定了一项政策,只允许从最新版本的软件进行同步操作。这意味着我们必须有一种轻松快速地更新远程安装的方法。
它做什么?
简而言之,启动器会检查更新位置以获取版本更改,下载并解压任何更新,然后执行主应用程序。如果没有版本更新或更新位置不可用,启动器将简单地执行当前版本的主应用程序。
我该如何编写一个?
我们将使用 Visual Studio 2010 和 C#,在 .NET 4.0 中进行编写。单元测试是用 Visual Studio Team Test 编写的。您可以使用您选择的任何单元测试框架,但下面的教程将使用 Team Test 中的命令。
我们将当前版本信息存储在一个文本文件中。该文件位于更新位置,并且在成功下载和解压更新后,它将被保存到客户端。当客户端的文本文件与更新位置的文本文件匹配时,启动器就知道当前版本是最新的。如果不匹配,我们需要下载并解压更新。由于我们的存档可能包含多个文件,因此使用比 GZip 更方便的解压缩库来解压它。我们将使用 Ionic DotNetZip 库(http://dotnetzip.codeplex.com/)。
让我们创建我们的新项目。创建一个名为 ArticleAutoUpdater 的新 WPF 应用程序。
我们的过程范围很小,可以轻松地包含在应用程序本身中,但当然您将对代码进行全面的单元测试,对吗?测试类库更容易,所以让我们创建它。
版本文本文件是什么样的?
0.11.67.1
正如我们所见,这是一个非常简单的文件。它只有一行,并且只包含一个完整的四值版本号。这只是记录版本的一种方法。您还可以使用 Reflection 或文件系统调用来识别特定文件的版本,但我们使用这种文件方法是为了包含那些不总是由程序集或应用程序管理的文件更新。
我们如何检查我们的版本?
让我们先创建一个名为 ApplicationUpdate 的类库。将默认的 Class1
重命名为 Versions
。将其更改为一个 public static
类,因为我们的版本操作都是由参数驱动的。
我们将编写的第一个过程是检查版本文件以获取当前版本号。我们知道我们需要检查两个文件:一个是通过 URL 的远程文件,一个是通过文件路径的本地文件。让我们从本地版本开始。
public static string LocalVersion(string path)
{
string lv = "";
return lv;
}
现在让我们为我们的方法创建单元测试。
我们的第一个测试用例是打开一个不存在的文件,并确保版本返回 null
。让我们为此编写一个测试。
右键单击方法,然后单击“创建单元测试...”。确保选中了 LocalVersion
方法,并选择“创建新的 Visual C# 测试项目...”。单击“确定”。将新项目命名为 ApplicationUpdate.Test。现在将生成的测试从 LocalVersionTest 重命名为 LocalVersion_BadFile_Test,因为我们将测试一个坏文件。
由于测试框架为我们生成了如此好的测试,我们将只填充一些值并删除不确定的行。我们还将检查路径实际上并不指向真实文件,因为这会使测试无效。
[TestMethod()]
public void LocalVersionTest()
{
string path = @"C:\BadFolder\BadVersionFile.version";
Assert.IsFalse(new System.IO.FileInfo(path).Exists,
"File should not exist.");
string expected = null;
string actual;
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
}
运行测试并确保我们出现失败。Assert.AreEqual
失败。预期:<(null)>。实际:<>。这正是我们所期望的。现在让我们编写我们的方法。
如果路径指向一个不存在的文件,我们希望返回 null
。这很容易,所以让我们来实现它。
public static string LocalVersion(string path)
{
string lv = "";
if (!new System.IO.FileInfo(path).Exists)
{
lv = null;
}
return lv;
}
再次运行测试。通过!让我们为测试添加另外几个坏路径,并确保它们都失败。我们将添加一个不存在的驱动器、一个看起来不像路径的字符串和一个 null 值。所有这些都是好的失败案例。我们的测试最终看起来像这样
[TestMethod()]
public void LocalVersion_BadFile_Test()
{
string path = @"C:\BadFolder\BadVersionFile.version";
Assert.IsFalse(new System.IO.FileInfo(path).Exists,
"File should not exist.");
string expected = null;
string actual;
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
path = @"Z:\ASDcjaksl\fasjldjvalwer";
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
path = null;
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
path = @"!@#411j320vjal;sdkua@!#^%*(@#AVEW VRLQ#";
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
}
情况 2,路径 = @"Z:\ASDcjaksl\fasjldjvalwer",已经通过。对于情况 3,路径 = null,我们需要做一些验证。我们只需添加 string.IsNullOrEmpty(path)
。
public static string LocalVersion(string path)
{
string lv = "";
if (string.IsNullOrEmpty(path) ||
!new System.IO.FileInfo(path).Exists)
{
lv = null;
}
return lv;
}
null 路径通过了!但我们仍然在非法字符处失败。LINQ 来拯救!再添加一个条件
System.IO.Path. GetInvalidPathChars().Intersect(path.ToCharArray()).Count() != 0
现在 LocalVersion
看起来像这样
public static string LocalVersion(string path)
{
string lv = "";
if (string.IsNullOrEmpty(path)
|| System.IO.Path.GetInvalidPathChars().Intersect(
path.ToCharArray()).Count() != 0
|| !new System.IO.FileInfo(path).Exists)
{
lv = null;
}
return lv;
}
并且我们的所有测试都通过了。成功!当然,每次操作都获取新的 FileInfo
是效率低下的——这只是为了方便编码。毕竟,这些测试范围很小,只执行少量操作。如果您想编写使用单个 FileInfo
实例的测试,请随时这样做。
是时候继续了。现在让我们打开一个实际存在的文件,并确保我们正确处理它。再次右键单击方法,然后单击“创建单元测试...”。确保选中了方法和测试项目,然后单击“确定”。现在将测试重命名为 LocalVersion_GoodFile_Test。让我们使用该测试实际创建(并清理)一个版本文件,以便我们知道它正在准确测试。我们不做任何花哨的事情——我们将创建一个文件夹并将文件放在那里。
[TestMethod()]
public void LocalVersion_GoodFile_Test()
{
string folderPath = "C:\\VersionsTest";
if (!new System.IO.DirectoryInfo(folderPath).Exists)
{
System.IO.Directory.CreateDirectory(folderPath);
}
string fileName = "app.version";
string path = folderPath + "\\" + fileName;
string expected = "1.2.3.4";
if (!new System.IO.FileInfo(path).Exists)
{
System.IO.File.WriteAllText(path, expected);
Assert.IsTrue(new System.IO.FileInfo(path).Exists,
"File should exist now.");
}
string actual;
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
}
当我们运行这个时,它失败了。我们预料到了这一点,现在我们知道我们没有意外检索到任何东西。让我们完成这个方法。
public static string LocalVersion(string path)
{
string lv = "";
if (string.IsNullOrEmpty(path)
|| System.IO.Path.GetInvalidPathChars().Intersect(
path.ToCharArray()).Count() != 0
|| !new System.IO.FileInfo(path).Exists)
{
lv = null;
}
else if (new System.IO.FileInfo(path).Exists)
{
string s = System.IO.File.ReadAllText(path);
if (!string.IsNullOrEmpty(s))
lv = s.Trim();
}
return lv;
}
我们的测试通过了!但是如果我们尝试使用一个不是我们的版本文件的文件会怎样?我们希望版本返回 null
,因为我们正在检查的文件并不符合我们的目的。
[TestMethod()]
public void LocalVersion_GoodFile_Test()
{
string folderPath = "C:\\VersionsTest";
if (!new System.IO.DirectoryInfo(folderPath).Exists)
{
System.IO.Directory.CreateDirectory(folderPath);
}
string fileName = "app.version";
string path = folderPath + "\\" + fileName;
string expected = "1.2.3.4";
if (!new System.IO.FileInfo(path).Exists)
{
System.IO.File.WriteAllText(path, expected);
Assert.IsTrue(new System.IO.FileInfo(path).Exists,
"File should exist now.");
}
string actual;
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual);
path = @"C:\Program Files\Microsoft Visual " +
@"Studio 10.0\Common7\Tools\errlook.exe";
expected = null;
actual = Versions.LocalVersion(path);
Assert.AreEqual(expected, actual, "This is not a good version file!");
}
我们需要验证它!重构创建文件并写入文件的代码。我们稍后会使用它。
public static string CreateLocalVersionFile(string folderPath,
string fileName, string version)
{
if (!new System.IO.DirectoryInfo(folderPath).Exists)
{
System.IO.Directory.CreateDirectory(folderPath);
}
string path = folderPath + "\\" + fileName;
if (new System.IO.FileInfo(path).Exists)
{
new System.IO.FileInfo(path).Delete();
}
if (!new System.IO.FileInfo(path).Exists)
{
System.IO.File.WriteAllText(path, version);
}
return path;
}
更新您的测试以运行 CreateLocalVersionFile
,而不是重复此代码。是时候进行另一个方法了。创建你的外壳
public static bool ValidateFile(string contents)
{
bool val = false;
return val;
}
现在创建一个单元测试。首先要做的是——让我们检查一个有效的版本字符串。
[TestMethod()]
public void ValidateFileTest()
{
string contents = "0.0.0.0";
bool expected = true;
bool actual;
actual = Versions.ValidateFile(contents);
Assert.AreEqual(expected, actual);
}
我们失败了。正则表达式的时间。我们希望有一组通过的包含内容以及一组失败的包含内容。让我们先编写那个测试。
[TestMethod()]
public void ValidateFileTest()
{
string[] contentsPass = {
"0.0.0.0",
"1.2.3.4",
"10.2.3.4",
"10.20.3.4",
"10.20.30.4",
"10.20.30.40",
"10000.20000.300000000.400000000000000",
};
bool expected = true;
bool actual;
foreach (string contents in contentsPass)
{
actual = Versions.ValidateFile(contents);
Assert.AreEqual(expected, actual, contents);
}
string[] contentsFail = {
"a.0.0.0",
"0.b.0.0",
"0.0.c.0",
"0.0.0.d",
"1.4",
"10.2.3.4.87",
"blah blah",
"",
null,
"!@#^%!*#@($QJVW4.!@$(^T!@#",
};
expected = false;
foreach (string contents in contentsFail)
{
actual = Versions.ValidateFile(contents);
Assert.AreEqual(expected, actual, contents);
}
}
一如既往,请记住检查 null。正则表达式很简单,因为我们有一个非常严格的版本格式(#.#.#.#)。
public static bool ValidateFile(string contents)
{
bool val = false;
if (!string.IsNullOrEmpty(contents))
{
string pattern = "^([0-9]*\\.){3}[0-9]*$";
System.Text.RegularExpressions.Regex re =
new System.Text.RegularExpressions.Regex(pattern);
val = re.IsMatch(contents);
}
return val;
}
通过!现在回到版本方法;我们将添加验证器。
public static string LocalVersion(string path)
{
string lv = "";
if (string.IsNullOrEmpty(path)
|| System.IO.Path.GetInvalidPathChars().Intersect(
path.ToCharArray()).Count() != 0
|| !new System.IO.FileInfo(path).Exists)
{
lv = null;
}
else if (new System.IO.FileInfo(path).Exists)
{
string s = System.IO.File.ReadAllText(path);
if (ValidateFile(s))
lv = s;
else
lv = null;
}
return lv;
}
运行所有测试。全部绿色,代码干净!
远程文件呢?
现在我们需要查看更新服务器上的当前版本是什么。操作的核心是相同的,但我们通过 HTTP 而不是文件系统获取文件。
打开 IIS Manager,创建一个名为 AutoUpdate 的新虚拟目录,指向我们上面用于单元测试的文件夹(C:\VersionsTest)。我们没有 .version 文件的 MIME 类型,所以我们将其称为 app.txt。现在我们将通过 URL https:///AutoUpdate/app.txt 访问该文件。
同样,我们从方法的外壳开始。
public static string RemoteVersion(string url)
{
string rv = "";
return rv;
}
测试也随之而来。我们将重用创建先前测试文件的代码,这很适合我们,因为我们的虚拟 Web 文件夹是本地文件系统文件夹。
[TestMethod()]
public void RemoteVersionTest()
{
string url = "https:///AutoUpdate/app.txt";
string folderPath = "C:\\VersionsTest";
string fileName = "app.txt";
string expected = "11.22.33.44";
string path = CreateLocalVersionFile(folderPath,
fileName, expected);
string actual;
actual = Versions.RemoteVersion(url);
Assert.AreEqual(expected, actual);
}
当然,我们的测试失败了。我们还没有编写该方法!但我们正在创建文件并且可以构建,所以我们仍在取得进展。现在让我们检索我们文件的内容并再次进行验证。
public static string RemoteVersion(string url)
{
string rv = "";
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)
System.Net.WebRequest.Create(url);
System.Net.HttpWebResponse response =
(System.Net.HttpWebResponse)req.GetResponse();
System.IO.Stream receiveStream = response.GetResponseStream();
System.IO.StreamReader readStream =
new System.IO.StreamReader(receiveStream, Encoding.UTF8);
string s = readStream.ReadToEnd();
response.Close();
if (ValidateFile(s))
{
rv = s;
}
return rv;
}
现在我们有了一个正面的测试用例通过了,当然,我们需要一个负面测试。
[TestMethod()]
public void RemoteVersion_BadURL_Test()
{
string url = "https:///AutoUpdate/ZZapp.txt";
string folderPath = "C:\\VersionsTest";
string fileName = "app.txt";
string contents = "11.22.33.44";
string expected = null;
string path =
CreateLocalVersionFile(folderPath, fileName, contents);
string actual;
actual = Versions.RemoteVersion(url);
Assert.AreEqual(expected, actual);
}
这失败了。我们还没有处理它。让我们确保在文件不存在时返回 null
。对于我们的计划用途,我们很容易:如果我们看不到文件,我们就假设我们已断开连接,因此没有更新。
public static string RemoteVersion(string url)
{
string rv = "";
try
{
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)
System.Net.WebRequest.Create(url);
System.Net.HttpWebResponse response =
(System.Net.HttpWebResponse)req.GetResponse();
System.IO.Stream receiveStream = response.GetResponseStream();
System.IO.StreamReader readStream =
new System.IO.StreamReader(receiveStream, Encoding.UTF8);
string s = readStream.ReadToEnd();
response.Close();
if (ValidateFile(s))
{
rv = s;
}
}
catch (Exception)
{
// Anything could have happened here but
// we don't want to stop the user
// from using the application.
rv = null;
}
return rv;
}
当然,这可以更健壮,并且有更好的错误处理,但就我们而言,我们要么有更新,要么没有,所以我们将进行一个非常简单的错误捕获。如果用户看不到版本文件,启动器就不应尝试为他们提供更新。
现在我们既可以检索本地版本,也可以检索存储在 Web 服务器上的当前版本,我们可以编写集成测试来展示我们将如何使用这些信息下载更新。另外两个测试!
[TestMethod()]
public void CompareVersions_Match_Test()
{
string url = "https:///AutoUpdate/app.txt";
// Create our server-side version file.
string folderPath = "C:\\VersionsTest";
string fileName = "app.txt";
string expected = "1.0.3.121";
string path = CreateLocalVersionFile(folderPath, fileName, expected);
// Create our local version file.
fileName = "app.version";
path = CreateTestFile(folderPath, fileName, expected);
string localVersion = Versions.LocalVersion(path);
string remoteVersion = Versions.RemoteVersion(url);
Assert.AreEqual(localVersion, remoteVersion,
"Versions should match. No update!");
}
[TestMethod()]
public void CompareVersions_NoMatch_Test()
{
string url = "https:///AutoUpdate/app.txt";
// Create our server-side version file.
string folderPath = "C:\\VersionsTest";
string fileName = "app.txt";
string expected = "1.0.3.121";
string path = CreateLocalVersionFile(folderPath, fileName, expected);
// Create our local version file.
fileName = "app.version";
expected = "1.0.4.1";
path = CreateLocalVersionFile(folderPath, fileName, expected);
string localVersion = Versions.LocalVersion(path);
string remoteVersion = Versions.RemoteVersion(url);
Assert.AreNotEqual(localVersion, remoteVersion,
"Versions should not match. We need an update!");
}
出色的工作。我们处理了很多不同的文件夹,但我们没有可测试且可重复的方式来创建和测试它们。现在我们将编写一个例程来检查、清除和创建我们的目标下载文件夹。
[TestMethod()]
public void CreateTargetLocationTest()
{
string downloadToPath = "C:\\AutoDownloadTest\\Downloads";
string version = "6.2.3.0";
string expected = downloadToPath + "\\" + version;
// Delete the folder if it exists, and assert that it isn't there.
if (new System.IO.DirectoryInfo(expected).Exists)
new System.IO.DirectoryInfo(expected).Delete();
Assert.IsFalse(new System.IO.DirectoryInfo(expected).Exists,
"Before we start, it shouldn't exist.");
string actual;
actual = Versions.CreateTargetLocation(downloadToPath, version);
Assert.AreEqual(expected, actual);
Assert.IsTrue(new System.IO.DirectoryInfo(expected).Exists,
"After we are done, it should exist.");
}
使测试通过的例程非常简单
public static string CreateTargetLocation(string downloadToPath, string version)
{
if (!downloadToPath.EndsWith("\\")) // Give a trailing \ if there isn't one
downloadToPath += "\\";
string filePath = downloadToPath + version;
System.IO.DirectoryInfo newFolder = new System.IO.DirectoryInfo(filePath);
if (!newFolder.Exists)
newFolder.Create();
return filePath;
}
运行测试。通过!
我们现在知道如何获取版本号并进行比较,以及在哪里放置我们要下载的文件。我们的下一步是实际处理这些信息。
下载新更新
一旦我们意识到我们不同步了,我们就需要从服务器下载我们的更新。实际下载是一个包含可执行应用程序的 zip 文件,本质上是您编译的程序集的 bin 文件夹。让我们创建一个“更新”包。就我们而言,我们将使用一些小巧易懂的东西,因为创建一个实际要更新的应用程序超出了本文档的范围。
zip 文件巧妙地命名与其版本相同。让我们创建一个名为 update.txt 的文本文件并放入一些文本。
This is the first version.
压缩它!创建一个名为 1.0.0.0.zip 的 zip 文件,其中包含文本文件。
现在编辑 update.txt 并更改为
This is the second version.
创建一个名为 2.0.0.0.zip 的新 zip 文件,其中包含新更新的文本文件。将这两个 zip 文件都放入我们之前创建的虚拟网站文件夹 C:\VersionsTest 中。
我们的下一步是指示 1.0.0.0 是当前版本,以便我们的更新客户端下载它。向您的 VersionsTest 文件夹添加一个名为 updateVersion.txt 的文本文件。编辑该文件以显示 1.0.0.0。
AutoUpdater 首先需要看到我们有什么版本以及我们需要什么版本。我们已经有了这两个调用,所以我们将从一个简单的表单开始,它将显示本地版本和远程版本。在我们的 WPF 项目中,我们有一个 MainWindow 表单。将以下定义添加到窗口的 Grid
中
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
并为版本信息添加文本块
<TextBlock Grid.Row="0" Grid.Column="0">Local version:</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">Latest version:</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Name="LocalVersion" />
<TextBlock Grid.Row="1" Grid.Column="1" Name="RemoteVersion" />
添加对 ApplicationUpdate 库项目的引用,以便我们可以使用我们编写的逻辑,并添加对 DotNetZip 的引用,以便我们可以解压缩我们下载的文件。
现在向表单添加一个方法
private void CompareVersions()
{
string downloadToPath = "C:\\VersionsTest\\Downloads";
string localVersion =
ApplicationUpdate.Versions.LocalVersion(downloadToPath + "version.txt");
string remoteURL = "https:///AutoUpdate/";
string remoteVersion =
ApplicationUpdate.Versions.RemoteVersion(remoteURL + "updateVersion.txt");
string remoteFile = remoteURL + remoteVersion + ".zip";
LocalVersion.Text = localVersion;
RemoteVersion.Text = remoteVersion;
}
并在 MainWindow()
构造函数中 InitializeComponent();
之后调用它。
运行应用程序,我们将看到本地版本为空,远程版本显示为 1.0.0.0。理想情况下,我们上面的变量将从可配置源填充,而不是硬编码。这里工作的一个自然扩展是将这些添加到 app.config 或设置文件中。但现在,让我们做我们的方法所说的,并实际比较版本。
if (localVersion != remoteVersion)
{
BeginDownload(remoteFile, downloadToPath, remoteVersion, "update.txt");
}
我们必须编写 BeginDownload
例程,但我们知道我们将需要下载文件的 URL、将其保存到哪里以及在下载完成后要运行哪个文件。
private void BeginDownload(string remoteURL, string downloadToPath,
string version, string executeTarget)
{
string filePath = ApplicationUpdate.Versions.CreateTargetLocation(
downloadToPath, version);
Uri remoteURI = new Uri(remoteURL);
System.Net.WebClient downloader = new System.Net.WebClient();
downloader.DownloadFileCompleted +=
new System.ComponentModel.AsyncCompletedEventHandler(
downloader_DownloadFileCompleted);
downloader.DownloadFileAsync(remoteURI, filePath + ".zip",
new string[] { version, downloadToPath, executeTarget });
}
首先,我们需要创建目标文件夹。然后我们设置一个 WebClient
来执行 HTTP 下载。我们将异步下载,以便可以报告进度,并在等待时不会占用系统。这些都是第一方对象和调用,所以我们不需要为 .NET 附带的方法编写单元测试。我们需要一个事件例程,所以我们将创建 DownloadFileCompleted
。我们将在下载完成后将所需数据存储在 DownloadFileAsync
调用的 UserState
参数中。
void downloader_DownloadFileCompleted(object sender,
System.ComponentModel.AsyncCompletedEventArgs e)
{
string[] us = (string[])e.UserState;
string currentVersion = us[0];
string downloadToPath = us[1];
string executeTarget = us[2];
if (!downloadToPath.EndsWith("\\"))
// Give a trailing \ if there isn't one
downloadToPath += "\\";
// Download folder + zip file
string zipName = downloadToPath + currentVersion + ".zip";
// Download folder\version\ + executable
string exePath = downloadToPath + currentVersion + "\\" + executeTarget;
if (new System.IO.FileInfo(zipName).Exists)
{
using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile(zipName))
{
zip.ExtractAll(downloadToPath + currentVersion,
Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
if (new System.IO.FileInfo(exePath).Exists)
{
ApplicationUpdate.Versions.CreateLocalVersionFile(
downloadToPath, "version.txt", currentVersion);
System.Diagnostics.Process proc =
System.Diagnostics.Process.Start(exePath);
}
else
{
MessageBox.Show("Problem with download. File does not exist.");
}
}
else
{
MessageBox.Show("Problem with download. File does not exist.");
}
}
一下子有很多代码,所以这里是细分
我们将 zip 文件下载到我们的下载文件夹,然后将其解压到 download\version\,确保我们要查找的文件存在,更新本地版本文件,最后运行我们指定的文件。由于我们使用的是 Windows 进程,因此文件本身不需要是可执行文件——任何具有关联应用程序的文件都可以。如果文件不存在,显然我们遇到了问题。我们将在后续的安装中处理这些问题。
运行应用程序,并看到我们的第一个版本文本文件在记事本中打开(假设您仍将 .txt 文件与记事本关联!)。
关闭记事本和启动器窗口。现在将您之前创建的 updateVersion.txt 文件从 1.0.0.0 更改为 2.0.0.0。再次运行应用程序,您将看到第二个版本文本文件。恭喜!您正在自动更新您的客户端软件!
乐趣加倍太多了
重要的是一次只有一个应用程序实例在运行。毕竟,如果我们试图下载一个已经在下载的版本,我们会遇到冲突。附加的解决方案使用了 Arik Poznanski 出色的博客文章(http://blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/wpf-single-instance-application.aspx)来确保只有一个实例在运行。
后续步骤
用于监控大更新下载的进度条对用户来说肯定很棒。
基于更改的更新安装程序也很好。对于我使用过的应用程序,我压缩了整个 bin 文件夹并下载它。具有增量更新集将使这些包小得多。
清理旧的下载版本将为您的用户节省磁盘空间。在开始下载最新版本时,请保留用户之前拥有的版本,以防下载失败,但清除旧版本。
我将在以后的安装中处理其中的每一个。
结论
我们需要一种简单的方法让我们的远程用户更新到最新版本的软件,同时仍然允许他们离线运行。通过使用测试驱动开发的方法,我们编写了一个 WPF 应用程序,该应用程序将本地副本的版本与 Web 服务器上的版本进行比较,然后在可用时下载、解压缩并执行新版本。
我们可以通过将应用程序压缩并将其 zip 文件放入 Web 文件夹,然后更新 Web 文件夹的版本文本文件以指向我们刚刚创建的版本来发布新更新。然后,当我们的客户端连接到 Internet 运行时,他们将在下次运行时识别新版本。
我们对我们所有的逻辑进行了单元测试,并使用第三方库来处理 zip 文件。