如何通过网络部署 Chromium 二进制文件
在某些情况下,安装程序或部署包需要尽可能小,例如,发布应用程序的服务可能会受到限制。
另一方面,DotNetBrowser 使用自己的 Chromium 引擎,二进制文件必须部署在目标环境中。 最直接的方法是将二进制文件作为安装程序的一部分提供,但是,这将会导致安装程序本身的大小增加。
可能的解决方案之一是在 DotNetBrowser 尝试定位它们时通过网络提供 Chromium 二进制文件。 DotNetBrowser 使用默认的 .NET 程序集加载逻辑来定位和加载二进制 DLL,因此可以使用 AppDomain.AssemblyResolve 事件来自定义整个过程并以非标准方式提供 DLL。
以下是对这一想法的一般描述:
- 注册 AppDomain.AssemblyResolve 事件的自定义处理程序。
- 在此处理程序中,筛选出与 DotNetBrowser 二进制文件相关的尝试。
- 当接收到相应的 AssemblyResolve 事件时,使用完全限定的程序集名称准备网络请求。
- 执行请求并获取字节数组形式的 DLL。
- 从字节加载 DLL 并从处理程序返回它。
执行所述操作以加载二进制文件 DLL 的示例应用程序的代码在 GitHub 存储库中作为 Visual Studio 项目提供: C#, VB.NET
该示例本身是一个 WPF 应用程序,但该方法并非特定于 WPF ,也可以在 WinForms 和控制台应用程序中使用。
下文将详细介绍该示例的实现过程,并解释其中最重要的部分。
实现
BinariesResolverBase 类
BinariesResolverBase
抽象类提供二进制解析器的一般实现。 该类的构造函数初始化 RequestUri
属性,该属性稍后可用于准备请求,创建 HttpClient
实例以发出这些请求并订阅应用程序域的 AssemblyResolve
事件。
protected BinariesResolverBase(string requestUri, AppDomain domain = null)
{
if (domain == null)
{
domain = AppDomain.CurrentDomain;
}
RequestUri = requestUri;
client = new HttpClient();
domain.AssemblyResolve += Resolve;
}
Resolve(object sender, ResolveEventArgs args)
方法处理 AssemblyResolve
事件,并筛选出名称以”DotNetBrowser.Chromium”开头的程序集的请求。 对于这些程序集,它会调用私有的 Resolve
方法重载以进行进一步处理。
public Assembly Resolve(object sender, ResolveEventArgs args)
=> args.Name.StartsWith("DotNetBrowser.Chromium") ? Resolve(args.Name).Result : null;
Resolve(string binariesAssemblyName)
方法重载创建一个 AssemblyName
实例并将其传递给抽象的 PrepareRequest(AssemblyName assemblyName)
方法以准备 URL 请求字符串。 之后,URL 请求字符串用于通过 HttpClient
执行实际请求。 响应流应包含 Chromium 二进制程序集的字节。 然后,这些字节被传递给抽象的 ProcessResponse(Stream responseBody, AssemblyName assemblyName)
方法,该方法返回程序集本身。
private async Task<Assembly> Resolve(string binariesAssemblyName)
{
//注意:程序集通常在 UI 应用程序的后台线程中解析。
try
{
//使用完全限定程序集名称构造请求。
AssemblyName assemblyName = new AssemblyName(binariesAssemblyName);
string request = PrepareRequest(assemblyName);
//执行请求并下载响应。
OnStatusUpdated("Downloading Chromium binaries...");
Debug.WriteLine($"Downloading {request}");
HttpResponseMessage response = await client.GetAsync(request);
response.EnsureSuccessStatusCode();
OnStatusUpdated("Chromium binaries package downloaded");
Stream responseBody = await response.Content.ReadAsStreamAsync();
//处理响应字节并加载程序集。
return ProcessResponse(responseBody, assemblyName);
}
catch (Exception e)
{
Debug.WriteLine("Exception caught: {0} ", e);
}
return null;
}
OnStatusUpdated
方法会引发带有特定信息的 StatusUpdated
事件,然后可利用该信息了解应用程序其他部分的进度。
BinariesResolverBase
抽象实现随后由BinariesResolver
类扩展,该类提供 PrepareRequest
和 ProcessResponse
方法的实现。
BinariesResolver 类
BinariesResolver
通过下载 DotNetBrowser 分发存档并从中即时提取所需的程序集来解析 Chromium 二进制程序集。 此类派生自 BinariesResolverBase
并提供其抽象成员的实现。
下面是 PrepareRequest
的实现:
protected override string PrepareRequest(AssemblyName assemblyName)
{
//如果构建组件为 0,则只使用主要和次要版本组件。
int fieldCount = assemblyName.Version.Build == 0 ? 2 : 3;
return string.Format(RequestUri, assemblyName.Version.ToString(fieldCount));
}
此实现重用 RequestUri
属性,根据 DotNetBrowser 版本准备对分发存档的直接引用。 本例中的 RequestUri
设置为 UriTemplate
,如下所示:
private const string UriTemplate =
"https://storage.googleapis.com/cloud.teamdev.com/downloads/"
+"dotnetbrowser/{0}/dotnetbrowser-net45-{0}.zip";
以流的形式接收分发归档后,会调用 ProcessResponse
方法。 该方法的实现方式是在运行过程中提取 DLL,并将其直接加载到当前应用程序域中。 然后将加载的程序集用作返回值。
protected override Assembly ProcessResponse(Stream responseBody, AssemblyName assemblyName)
{
// 下载的字节表示一个 ZIP 存档。 在此存档中找到我们需要的 DLL。
ZipArchive archive = new ZipArchive(responseBody);
ZipArchiveEntry binariesDllEntry
= archive.Entries
.FirstOrDefault(entry => entry.FullName.EndsWith(".dll")
&& entry.FullName.Contains(assemblyName.Name));
if (binariesDllEntry == null)
{
return null;
}
// 解压找到的条目,并加载 DLL。
OnStatusUpdated("Unzipping Chromium binaries");
Stream unzippedEntryStream;
using (unzippedEntryStream = binariesDllEntry.Open())
{
using (MemoryStream memoryStream = new MemoryStream())
{
unzippedEntryStream.CopyTo(memoryStream);
OnStatusUpdated("Loading Chromium binaries assembly");
Assembly assembly = Assembly.Load(memoryStream.ToArray());
OnStatusUpdated("Chromium binaries assembly loaded.", true);
return assembly;
}
}
}
将 DLL 加载到应用程序域后,DotNetBrowser 将二进制文件从此 DLL 解压到通过 EngineOptions.Builder.ChromiumDirectory
配置的目录,然后照常启动 Chromium 进程。
此处的 DotNetBrowser 分发存档仅用作示例。 如果您计划在应用程序中实现类似的方法,您应该考虑实现自己的服务,提供所需的 DLL,以避免过度的内存使用并减少初始化时间。
优点
建议方法的主要优点是最大限度地减小了分布式应用程序包的大小-不需要将 DotNetBrowser.Chromium DLL 作为安装程序或分发包的一部分提供。
缺点
- 初始化时间增加。 当 Chromium 二进制文件通过网络提供时,初始化过程包括下载二进制文件 DLL,即使连接良好,这通常也需要一些时间。 如果连接不佳,可能需要重试几次,并且初始化时间会显着增加。
- 需要连接互联网。 如果没有连接互联网,则无法下载二进制文件和初始化 DotNetBrowser 引擎。
- 内存使用量增加。 如果 DotNetBrowser.Chromium DLL 从字节数组中加载,DotNetBrowser 会在内存中解压缩二进制文件,初始化过程中内存使用量会增加。 如果内存使用率太高,甚至会导致 32 位环境中内存不足的问题。
总结
所描述的方法似乎对特定类型的解决方案很有用,但是,在做出最终决定之前,有必要全面考虑其优缺点。