使用 Asp.Net 核心进行自我更新/卷影复制
Self-update / shadow-copy with Asp.Net Core
我正在编写一个 Asp.Net 核心应用程序,它应该能够自我更新(在 运行 时替换它自己的二进制文件)。
This MSDN article 描述了使用经典 .Net 框架进行卷影复制,这正是我所需要的。但是 .Net Core 中缺少整个 AppDomain。
所以我的问题是:
- .Net Core 中是否有替代方法来启用影子复制程序集?
- .Net Core 中是否有其他机制允许构建自更新应用程序?
.NET Core 中没有内置卷影复制功能
由于 .NET Core 中没有用于执行此操作的内置机制,我最终实现了自己的自定义解决方案。它的工作原理大致是这样的:
- 运行 应用程序下载新二进制文件并将其提取到新文件夹。
- 运行 应用程序启动了一个小的更新程序进程。以下参数通过命令行传递给更新程序进程:
- 运行 应用程序的进程 ID
- 运行 应用程序的二进制路径
- 下载的二进制文件的路径
- 运行 应用程序自行退出。
- 更新进程一直等到 运行 应用程序退出(使用进程 ID),或者如果 运行 应用程序没有在给定的超时时间内自行退出,则强制终止该应用程序。
- 更新程序删除现有二进制文件并复制新下载的二进制文件。
- 更新进程启动主应用程序的新版本。
确保您在主应用程序中执行尽可能多的操作(下载、解压缩、验证...)并使更新程序过程尽可能简单(将失败的风险降至最低)。
这种方法已被证明是相当稳定的。
.Net API Browser 表示在 .Net Core 中设置它所需的 属性 但 AppDomainSetup 不是。
需要说明的是,.Net Standard 2.0 中添加了 AppDomain,但目前不支持创建域
为了避免有人不得不做我刚刚做的事情并制作这个 - 这只会复制具有不同修改日期时间的文件。我检查并重建你的应用程序只改变了几个文件。这会产生一个非常快速的自加载程序,然后在新位置启动 exe,并退出执行从旧位置 运行ning 加载的 exe。这可能取决于一些东西,例如您的 DLL 运行必须将代码命名为与启动它的 EXE 相同的名称。
适用于 .Net 5:
using System;
using System.Diagnostics;
using System.IO;
namespace NetworkHelper
{
public static class LocalCopier
{
public static void EnsureRunningLocally(string callingAssemblyDotLocation)
{
var assemblyFileFriendlyName = Path.GetFileName(callingAssemblyDotLocation.Replace(".", "-"));
var assemblyDirToCheck = Path.GetDirectoryName(callingAssemblyDotLocation);
var localLocation = Configuration.Tools.AppsLocation + assemblyFileFriendlyName + "\";
var assemblyFinalExePath = localLocation + assemblyFileFriendlyName.Replace("-dll", ".exe");
// Check what assembly passed in path starts with
var runningFromNetwork = callingAssemblyDotLocation.ToLower().StartsWith(@"\w2k3nas1\");
if (callingAssemblyDotLocation.ToLower().StartsWith(@"i:\")) runningFromNetwork = true;
if (!runningFromNetwork) return;
// Check if copied to local already
Directory.CreateDirectory(localLocation);
// Foreach file in source dir, recursively
CopyOnlyDifferentFiles(assemblyDirToCheck, localLocation);
Process.Start(assemblyFinalExePath);
Environment.Exit(0);
}
private static void CopyOnlyDifferentFiles(string sourceFolderPath, string destinationFolderPath)
{
string[] originalFiles = Directory.GetFiles(sourceFolderPath, "*", SearchOption.AllDirectories);
Array.ForEach(originalFiles, (originalFileLocation) =>
{
FileInfo originalFile = new FileInfo(originalFileLocation);
FileInfo destFile = new FileInfo(originalFileLocation.Replace(sourceFolderPath, destinationFolderPath));
if (destFile.Exists)
{
if (originalFile.LastWriteTime != destFile.LastWriteTime)
{
originalFile.CopyTo(destFile.FullName, true);
}
}
else
{
Directory.CreateDirectory(destFile.DirectoryName);
originalFile.CopyTo(destFile.FullName, false);
}
});
}
}
}
请注意,“\w2k3nas1”和“i:”是网络位置的示例,如果它是 运行ning,它应该将自己复制到本地目录,我使用应用程序 data/roaming/localApps 然后从新目录重新启动。
这一切都可以放入参考库中,并从任何客户端应用程序调用:
NetworkHelpers.LocalCopier.EnsureRunningLocally(Assembly.GetExecutingAssembly().位置);
(此处,Assembly.GetExecutingAssembly()。位置是从调用应用程序传入的,因为如果您要 运行 从参考项目中获取,您将获得该库的 dll .)
我正在编写一个 Asp.Net 核心应用程序,它应该能够自我更新(在 运行 时替换它自己的二进制文件)。
This MSDN article 描述了使用经典 .Net 框架进行卷影复制,这正是我所需要的。但是 .Net Core 中缺少整个 AppDomain。
所以我的问题是:
- .Net Core 中是否有替代方法来启用影子复制程序集?
- .Net Core 中是否有其他机制允许构建自更新应用程序?
.NET Core 中没有内置卷影复制功能
由于 .NET Core 中没有用于执行此操作的内置机制,我最终实现了自己的自定义解决方案。它的工作原理大致是这样的:
- 运行 应用程序下载新二进制文件并将其提取到新文件夹。
- 运行 应用程序启动了一个小的更新程序进程。以下参数通过命令行传递给更新程序进程:
- 运行 应用程序的进程 ID
- 运行 应用程序的二进制路径
- 下载的二进制文件的路径
- 运行 应用程序自行退出。
- 更新进程一直等到 运行 应用程序退出(使用进程 ID),或者如果 运行 应用程序没有在给定的超时时间内自行退出,则强制终止该应用程序。
- 更新程序删除现有二进制文件并复制新下载的二进制文件。
- 更新进程启动主应用程序的新版本。
确保您在主应用程序中执行尽可能多的操作(下载、解压缩、验证...)并使更新程序过程尽可能简单(将失败的风险降至最低)。
这种方法已被证明是相当稳定的。
.Net API Browser 表示在 .Net Core 中设置它所需的 属性 但 AppDomainSetup 不是。
需要说明的是,.Net Standard 2.0 中添加了 AppDomain,但目前不支持创建域
为了避免有人不得不做我刚刚做的事情并制作这个 - 这只会复制具有不同修改日期时间的文件。我检查并重建你的应用程序只改变了几个文件。这会产生一个非常快速的自加载程序,然后在新位置启动 exe,并退出执行从旧位置 运行ning 加载的 exe。这可能取决于一些东西,例如您的 DLL 运行必须将代码命名为与启动它的 EXE 相同的名称。
适用于 .Net 5:
using System;
using System.Diagnostics;
using System.IO;
namespace NetworkHelper
{
public static class LocalCopier
{
public static void EnsureRunningLocally(string callingAssemblyDotLocation)
{
var assemblyFileFriendlyName = Path.GetFileName(callingAssemblyDotLocation.Replace(".", "-"));
var assemblyDirToCheck = Path.GetDirectoryName(callingAssemblyDotLocation);
var localLocation = Configuration.Tools.AppsLocation + assemblyFileFriendlyName + "\";
var assemblyFinalExePath = localLocation + assemblyFileFriendlyName.Replace("-dll", ".exe");
// Check what assembly passed in path starts with
var runningFromNetwork = callingAssemblyDotLocation.ToLower().StartsWith(@"\w2k3nas1\");
if (callingAssemblyDotLocation.ToLower().StartsWith(@"i:\")) runningFromNetwork = true;
if (!runningFromNetwork) return;
// Check if copied to local already
Directory.CreateDirectory(localLocation);
// Foreach file in source dir, recursively
CopyOnlyDifferentFiles(assemblyDirToCheck, localLocation);
Process.Start(assemblyFinalExePath);
Environment.Exit(0);
}
private static void CopyOnlyDifferentFiles(string sourceFolderPath, string destinationFolderPath)
{
string[] originalFiles = Directory.GetFiles(sourceFolderPath, "*", SearchOption.AllDirectories);
Array.ForEach(originalFiles, (originalFileLocation) =>
{
FileInfo originalFile = new FileInfo(originalFileLocation);
FileInfo destFile = new FileInfo(originalFileLocation.Replace(sourceFolderPath, destinationFolderPath));
if (destFile.Exists)
{
if (originalFile.LastWriteTime != destFile.LastWriteTime)
{
originalFile.CopyTo(destFile.FullName, true);
}
}
else
{
Directory.CreateDirectory(destFile.DirectoryName);
originalFile.CopyTo(destFile.FullName, false);
}
});
}
}
}
请注意,“\w2k3nas1”和“i:”是网络位置的示例,如果它是 运行ning,它应该将自己复制到本地目录,我使用应用程序 data/roaming/localApps 然后从新目录重新启动。
这一切都可以放入参考库中,并从任何客户端应用程序调用: NetworkHelpers.LocalCopier.EnsureRunningLocally(Assembly.GetExecutingAssembly().位置);
(此处,Assembly.GetExecutingAssembly()。位置是从调用应用程序传入的,因为如果您要 运行 从参考项目中获取,您将获得该库的 dll .)