
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 应用
对于本例,我们将使用以下项目结构:
text
Example.Docker/
├── <App files> # 应用程序源文件
├── Dockerfile # Docker 构建文件
└── startup.sh # 用于启动应用的脚本
让我们从创建一个简单的 Avalonia 桌面应用开始。为了简化操作,我们将使用官方的 DotNetBrowser Avalonia 模板,而不是从头开始设置一切。
首先,安装模板:
bash
dotnet new install DotNetBrowser.Templates
然后创建一个新项目:
bash
dotnet new dotnetbrowser.avalonia.app -o Example.Docker -li <your_license_key>
这将在 Example.Docker 文件夹中生成一个可立即运行的 Avalonia 应用程序。开箱即用,它会打开一个窗口并在 DotNetBrowser 中加载一个网页。
由于应用将在 Docker 容器中运行,我们需要一种方式来确认它在没有可见窗口时也能正常运行。可以在页面加载完成后将网页标题打印到控制台。
在 MainWindow.axaml.cs 中添加代码:
csharp
// 页面加载完成后打印标题。
browser.Navigation.FrameLoadFinished += (_, e) =>
{
if (e.Frame.IsMain)
{
Console.WriteLine($"Title: {browser.Title}");
}
};
// 加载网页。
browser.Navigation.LoadUrl("https://www.google.com");
完成此操作后,应用程序就可以打包了。下一步是生成 Release 构建:
bash
dotnet publish -c Release -o out
生成的 out/ 目录将在运行时被容器使用。
创建 Dockerfile
现在,我们需要定义应用程序运行的环境。在 Docker 中,这是通过 Dockerfile 完成的。
我们的目标是构建一个包含以下内容的镜像:
- .NET 运行时
- Chromium 的 Linux 原生依赖项
- 一个虚拟显示服务器
- 已发布的应用程序文件
让我们一步步构建它。
基础环境配置
以基础镜像开始您的 Dockerfile:
dockerfile
FROM mcr.microsoft.com/dotnet/runtime:8.0
此镜像基于 Debian,并且已经包含了 .NET 运行时,因此无需手动安装。
请记住,Chromium 依赖于 glibc。因此,您不能使用 Alpine 或任何其他依赖于 musl 或其他标准 C 库的 Linux 发行版。
接下来,为安装软件包设置一个非交互式环境:
dockerfile
ENV DEBIAN_FRONTEND=noninteractive
这可以确保 apt-get 在安装过程中不会暂停并等待输入。
安装 Chromium 依赖项
Chromium 依赖于几个原生 Linux 库。它们负责处理窗口、图形、声音和安全性等基本系统功能。在最小的 Docker 镜像中,这些库默认不会安装。
dockerfile
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 在没有物理显示器可用时运行。
添加应用程序
现在环境已准备就绪,我们可以添加应用程序了。我们需要定义它的启动方式并将其文件复制到容器中。
- 创建启动脚本。
我们不直接启动应用程序,而是使用一个小的包装脚本:
bash
#!/bin/sh
set -e
if [ -z "${DISPLAY:-}" ]; then
Xvfb :0 -screen 0 1920x1080x24 &
export DISPLAY=:0
fi
exec dotnet Example.Docker.dll
此脚本通过 DISPLAY 变量检查是否有 X 服务器可用:
- 如果未设置
DISPLAY,它会启动一个虚拟显示器,并将应用程序指向它。 - 如果设置了
DISPLAY,则假定有真实的 X 服务器可用并正常运行。
即使显示器是虚拟的,Chromium 也会像对待真实屏幕一样对待它。所以我们使用 -screen 选项设置分辨率和色深。
- 将脚本复制到镜像中:
dockerfile
WORKDIR /Example.Docker
COPY startup.sh .
RUN chmod +x startup.sh
我们设置一个工作目录,复制脚本,并使其可执行。
- 复制先前发布的应用程序文件:
dockerfile
COPY out/ .
- 定义入口点:
dockerfile
ENTRYPOINT ["/Example.Docker/startup.sh"]
这告诉 Docker 在容器启动时执行什么。
至此,Docker 镜像已准备就绪。下一步是构建它并观察其运行。
构建并运行容器
确保 Docker Engine 正在运行,然后构建镜像:
bash
docker build -t dnb-app -f Dockerfile .
运行容器通常有两种方式:
- 无头模式 --- 适用于自动化、测试和服务器端运行,不需要用户界面。
- 桌面模式 --- 适用于希望查看应用程序窗口并进行可视化调试的场景。
以下是每种模式的工作原理。
无头模式
无头模式是 CI 或服务器环境中最常见的设置。在这种模式下,应用程序使用虚拟 X 服务器运行,而不是真实的显示器。
运行容器:
bash
docker run --rm --shm-size=1g dnb-app
由于没有设置 DISPLAY,启动脚本会启动 Xvfb 并在无头模式下运行应用程序。
--shm-size=1g 选项增加了分配给容器的共享内存量。Chromium 使用共享内存在其进程之间进行通信。在大多数 Linux 系统上,默认情况下共享内存足够大。但在 Docker 中,它被限制为 64 MB ------ 对于 Chromium 来说太小了。
在示例应用程序中,我们加载 Google 并打印页面标题。当容器启动时,您应该看到:
text
Title: Google
桌面模式
桌面模式适用于您希望看到容器内的应用程序窗口的情况------例如,调试渲染问题或测试用户输入。在这种情况下,容器需要访问您主机的 X 服务器。
这种方法适用于使用 X11 的 Linux 系统。在 Linux 上,图形应用程序通过 Unix 套接字与 X 服务器通信,DISPLAY 变量告诉它们使用哪个显示器。我们将此信息传递给容器,以便应用程序能够在您的主机桌面上显示其窗口。
首先,允许本地连接到您的 X 服务器:
bash
xhost +local:root
然后启动容器:
bash
docker run --rm \
--shm-size=1g \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
dnb-app
这里发生了什么:
-e DISPLAY=$DISPLAY告诉应用程序要使用哪个显示器。-v /tmp/.X11-unix:/tmp/.X11-unix挂载了 X11 通信套接字。
应用程序窗口应该会出现:

由容器渲染的应用程序窗口。
完成后,您可以撤销 X 服务器的权限:
bash
xhost -local:root
完整的示例应用程序可在 GitHub 仓库 中找到。
远程调试
当出现问题时,您需要查看 Chromium 内部发生了什么------检查 DOM、调试 JavaScript 或监控网络请求。
在 DotNetBrowser 中,通过引擎选项启用远程调试:设置端口并添加 --remote-allow-origins 开关。
将以下配置添加到 EngineOptions.Builder 中:
csharp
IEngine engine = EngineFactory.Create(new EngineOptions.Builder
{
ChromiumSwitches = { "--remote-allow-origins=http://localhost:9222" },
RemoteDebuggingPort = 9222
}.Build());
修改应用程序后,不要忘记重新构建 Docker 镜像。
当 Chromium 在容器内运行时,DevTools 无法直接从您的主机访问。Chromium 在本地端口上暴露 DevTools ------但这里的"本地"指的是容器内部。要从主机访问 DevTools,请使用 SSH 本地端口转发。
在主机上,启动容器并发布 SSH 端口:
shell
docker run -d -p 2222:22 --shm-size=1g dnb-app
在正在运行的容器内打开一个 shell:
shell
docker exec -it <container_id> /bin/bash
将 <container_id> 替换为正在运行的容器的 ID。您可以通过运行 docker ps 找到它。
在容器内部安装并启动 SSH 服务器:
shell
apt install -y openssh-server
service ssh start
在容器内部,创建一个用于 SSH 访问的用户:
shell
useradd --create-home --shell /bin/bash dnb-app
passwd dnb-app
在主机上,转发远程调试端口:
shell
ssh -L 9222:localhost:9222 -p 2222 dnb-app@localhost
此命令创建了一个从主机的 localhost:9222 到容器内部的 localhost:9222 的隧道。在使用 DevTools 期间,请保持此 SSH 会话打开。
在主机上,打开 Google Chrome 并加载 chrome://inspect 以访问 DevTools。
总结
DotNetBrowser 应用程序不仅仅是一个 .NET 进程------它嵌入了一个完整的 Chromium Engine。该 Engine 期望特定的系统库和一个显示服务器。Docker 允许您显式定义该环境,并在本地、CI 或生产环境中运行相同的镜像,获得一致的结果。