DotNetBrowser 是一个基于 Chromium 的 .NET WebView。它允许您在 .NET 桌面应用中嵌入现代网页内容,并通过代码对其进行控制。
与普通的 .NET 应用不同,运行 Chromium 的应用依赖一些系统级组件,这些组件并不包含在您的项目中。应用可能在您的电脑上运行良好,但在一个干净的服务器或 CI 环境中却无法运行。
Docker 通过将应用程序与其所需的完整运行环境一起打包来解决这个问题。本文将介绍如何使用 Docker 部署 DotNetBrowser 应用。
为什么选择 Docker
DotNetBrowser 构建于 Chromium 之上,这意味着它继承了 Chromium 的系统要求。在 Linux 上,这包括原生库和用于渲染内容的 X 服务器。在典型的桌面设置中,这些部分已经由操作系统提供。但在 CI 或服务器环境中,它们通常是缺失的。
Docker 允许您显式地定义和打包环境。这使您的应用程序更具可预测性,并减少了经典的“在我机器上能跑”的问题。当您需要在官方支持列表之外的 Linux 发行版上运行时,它也能派上用场。
准备工作
我们将构建一个小型的 .NET 应用程序,并使用 Docker 进行打包。要跟着操作,您需要:
- Docker Engine。
- .NET 8 SDK。
- DotNetBrowser 许可证。
创建 DotNetBrowser 应用
对于本例,我们将使用以下项目结构:
Example.Docker/ ├── <App files> # 应用程序源文件 ├── Dockerfile # Docker 构建文件 └── startup.sh # 用于启动应用的脚本让我们从创建一个简单的 Avalonia 桌面应用开始。为了简化操作,我们将使用官方的 DotNetBrowser Avalonia 模板,而不是从头开始设置一切。
首先,安装模板:
dotnet newinstallDotNetBrowser.Templates然后创建一个新项目:
dotnet new dotnetbrowser.avalonia.app-oExample.Docker-li<your_license_key>这将在Example.Docker文件夹中生成一个可立即运行的 Avalonia 应用程序。开箱即用,它会打开一个窗口并在 DotNetBrowser 中加载一个网页。
由于应用将在 Docker 容器中运行,我们需要一种方式来确认它在没有可见窗口时也能正常运行。可以在页面加载完成后将网页标题打印到控制台。
在MainWindow.axaml.cs中添加代码:
// 页面加载完成后打印标题。browser.Navigation.FrameLoadFinished+=(_,e)=>{if(e.Frame.IsMain){Console.WriteLine($"Title:{browser.Title}");}};// 加载网页。browser.Navigation.LoadUrl("https://www.google.com");完成此操作后,应用程序就可以打包了。下一步是生成 Release 构建:
dotnet publish-cRelease-oout生成的out/目录将在运行时被容器使用。
创建 Dockerfile
现在,我们需要定义应用程序运行的环境。在 Docker 中,这是通过Dockerfile完成的。
我们的目标是构建一个包含以下内容的镜像:
- .NET 运行时
- Chromium 的 Linux 原生依赖项
- 一个虚拟显示服务器
- 已发布的应用程序文件
让我们一步步构建它。
基础环境配置
以基础镜像开始您的Dockerfile:
FROM mcr.microsoft.com/dotnet/runtime:8.0此镜像基于 Debian,并且已经包含了 .NET 运行时,因此无需手动安装。
请记住,Chromium 依赖于glibc。因此,您不能使用 Alpine 或任何其他依赖于musl或其他标准 C 库的 Linux 发行版。
接下来,为安装软件包设置一个非交互式环境:
ENV DEBIAN_FRONTEND=noninteractive这可以确保apt-get在安装过程中不会暂停并等待输入。
安装 Chromium 依赖项
Chromium 依赖于几个原生 Linux 库。它们负责处理窗口、图形、声音和安全性等基本系统功能。在最小的 Docker 镜像中,这些库默认不会安装。
RUN apt-get update && \ apt-get -y install --no-install-recommends \ libgtk2.0-0 \ libgtk-3-0 \ libgbm1 \ libnotify4 \ libgconf-2-4 \ libnss3 \ libxss1 \ libasound2 \ libxtst6 \ xauth \ xvfb \ x11-xserver-utils && \ rm -rf /var/lib/apt/lists/*我们还安装了Xvfb,一个轻量级的虚拟 X 服务器。它允许 DotNetBrowser 在没有物理显示器可用时运行。
添加应用程序
现在环境已准备就绪,我们可以添加应用程序了。我们需要定义它的启动方式并将其文件复制到容器中。
- 创建启动脚本。
我们不直接启动应用程序,而是使用一个小的包装脚本:
#!/bin/shset-eif[-z"${DISPLAY:-}"];thenXvfb :0-screen01920x1080x24&exportDISPLAY=:0fiexecdotnet Example.Docker.dll此脚本通过DISPLAY变量检查是否有 X 服务器可用:
- 如果未设置
DISPLAY,它会启动一个虚拟显示器,并将应用程序指向它。 - 如果设置了
DISPLAY,则假定有真实的 X 服务器可用并正常运行。
即使显示器是虚拟的,Chromium 也会像对待真实屏幕一样对待它。所以我们使用-screen选项设置分辨率和色深。
- 将脚本复制到镜像中:
WORKDIR /Example.Docker COPY startup.sh . RUN chmod +x startup.sh我们设置一个工作目录,复制脚本,并使其可执行。
- 复制先前发布的应用程序文件:
COPY out/ .- 定义入口点:
ENTRYPOINT ["/Example.Docker/startup.sh"]这告诉 Docker 在容器启动时执行什么。
至此,Docker 镜像已准备就绪。下一步是构建它并观察其运行。
构建并运行容器
确保 Docker Engine 正在运行,然后构建镜像:
dockerbuild-tdnb-app-fDockerfile.运行容器通常有两种方式:
- 无头模式— 适用于自动化、测试和服务器端运行,不需要用户界面。
- 桌面模式— 适用于希望查看应用程序窗口并进行可视化调试的场景。
以下是每种模式的工作原理。
无头模式
无头模式是 CI 或服务器环境中最常见的设置。在这种模式下,应用程序使用虚拟 X 服务器运行,而不是真实的显示器。
运行容器:
dockerrun--rm--shm-size=1g dnb-app由于没有设置DISPLAY,启动脚本会启动 Xvfb 并在无头模式下运行应用程序。
--shm-size=1g选项增加了分配给容器的共享内存量。Chromium 使用共享内存在其进程之间进行通信。在大多数 Linux 系统上,默认情况下共享内存足够大。但在 Docker 中,它被限制为 64 MB —— 对于 Chromium 来说太小了。
在示例应用程序中,我们加载 Google 并打印页面标题。当容器启动时,您应该看到:
Title: Google桌面模式
桌面模式适用于您希望看到容器内的应用程序窗口的情况——例如,调试渲染问题或测试用户输入。在这种情况下,容器需要访问您主机的 X 服务器。
这种方法适用于使用 X11 的 Linux 系统。在 Linux 上,图形应用程序通过 Unix 套接字与 X 服务器通信,DISPLAY变量告诉它们使用哪个显示器。我们将此信息传递给容器,以便应用程序能够在您的主机桌面上显示其窗口。
首先,允许本地连接到您的 X 服务器:
xhost +local:root然后启动容器:
dockerrun--rm\--shm-size=1g\-eDISPLAY=$DISPLAY\-v/tmp/.X11-unix:/tmp/.X11-unix\dnb-app这里发生了什么:
-e DISPLAY=$DISPLAY告诉应用程序要使用哪个显示器。-v /tmp/.X11-unix:/tmp/.X11-unix挂载了 X11 通信套接字。
应用程序窗口应该会出现:
由容器渲染的应用程序窗口。
完成后,您可以撤销 X 服务器的权限:
xhost-local:root完整的示例应用程序可在 GitHub 仓库 中找到。
远程调试
当出现问题时,您需要查看 Chromium 内部发生了什么——检查 DOM、调试 JavaScript 或监控网络请求。
在 DotNetBrowser 中,通过引擎选项启用远程调试:设置端口并添加--remote-allow-origins开关。
将以下配置添加到EngineOptions.Builder中:
IEngineengine=EngineFactory.Create(newEngineOptions.Builder{ChromiumSwitches={"--remote-allow-origins=http://localhost:9222"},RemoteDebuggingPort=9222}.Build());修改应用程序后,不要忘记重新构建 Docker 镜像。
当 Chromium 在容器内运行时,DevTools 无法直接从您的主机访问。Chromium 在本地端口上暴露 DevTools ——但这里的“本地”指的是容器内部。要从主机访问 DevTools,请使用 SSH 本地端口转发。
在主机上,启动容器并发布 SSH 端口:
dockerrun-d-p2222:22 --shm-size=1g dnb-app在正在运行的容器内打开一个 shell:
dockerexec-it<container_id>/bin/bash将<container_id>替换为正在运行的容器的 ID。您可以通过运行docker ps找到它。
在容器内部安装并启动 SSH 服务器:
aptinstall-yopenssh-serverservicesshstart在容器内部,创建一个用于 SSH 访问的用户:
useradd--create-home--shell/bin/bash dnb-apppasswddnb-app在主机上,转发远程调试端口:
ssh-L9222:localhost:9222-p2222dnb-app@localhost此命令创建了一个从主机的localhost:9222到容器内部的localhost:9222的隧道。在使用 DevTools 期间,请保持此 SSH 会话打开。
在主机上,打开 Google Chrome 并加载chrome://inspect以访问 DevTools。
总结
DotNetBrowser 应用程序不仅仅是一个 .NET 进程——它嵌入了一个完整的 Chromium Engine。该 Engine 期望特定的系统库和一个显示服务器。Docker 允许您显式定义该环境,并在本地、CI 或生产环境中运行相同的镜像,获得一致的结果。