跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C#大前端

基于 Angular UI 构建 C# 跨平台桌面应用

一种基于 C# 和 Avalonia 构建跨平台桌面应用的架构方案。该方案采用 Angular 作为用户界面,通过 DotNetBrowser 嵌入 WebView,实现离线运行且无需本地服务器。通信层利用 OpenAPI 规范定义契约,自动生成 TypeScript 和 C# 客户端,确保前后端类型安全与同步。整体架构结合了原生性能与 Web 开发的灵活性。

战神发布于 2026/3/23更新于 2026/9/1127K 浏览
基于 Angular UI 构建 C# 跨平台桌面应用

文章配图

现代用户体验已成为桌面应用的基础要求,采用 Web 技术能帮助团队更快交付软件。

本文将展示基于 C# 的桌面应用架构,其用户界面采用 Angular 实现。该应用支持离线运行且无需本地服务器。

为什么在桌面应用中使用 Web UI?

Web UI 技术栈拥有庞大且经过充分验证的组件生态系统。采用 Angular 构建桌面 UI,可借助熟悉的工具处理布局、主题和交互逻辑,避免在原生 UI 框架中重复实现相同控件。

这种方式同时简化了代码复用流程。同一个 Angular 应用既能在浏览器中运行,也能嵌入桌面应用宿主程序,大幅减少代码冗余,实现 UI 变更的集中管理。原生窗口与系统集成的工作由独立模块负责,Web 层保持可移植性,桌面应用则能与各个操作系统实现无缝集成。

C# 桌面应用中的 Web UI

本文将构建一个跨平台的 Avalonia 桌面应用,其 UI 是一个基于 Angular 的简单偏好设置界面。Angular Web 应用被打包进桌面程序中,而 .NET 端负责将设置保存到持久化存储中。

运行在 Avalonia 窗口中的 Angular UI

UI 使用了 PrimeNG 作为现成的 Angular 组件库,但在该架构下,任何类似的组件库都可以使用。

将网页界面嵌入桌面壳层时,需克服以下障碍:

  1. 需采用符合现代网页标准的可靠网页视图组件。
  2. 需实现无服务器环境下的网页内容加载,确保应用自包含性。
  3. 需建立 JavaScript 与 .NET 之间的双向通信机制。

后续章节将依次说明:窗口与网页视图的配置方案、无服务器加载 Angular UI 的实现方法,以及通过 OpenAPI 规范定义的接口连接 JavaScript 与 .NET 的具体方案。

应用窗口与网页视图

对于桌面壳层,我们使用 Avalonia 创建原生窗口和布局,并通过 DotNetBrowser(基于 Chromium 的跨平台网页视图组件)将其嵌入其中:

MainWindow.axaml

<Window Title="Angular Demo">
    <Grid>
        <app:BrowserView x:Name="BrowserView" />
    </Grid>
</Window>

在后端代码中,窗口会监听生命周期事件,并在打开时初始化浏览器组件:

MainWindow.axaml.cs

public partial class MainWindow : Window {
    private const string Url = ResourceRequestHandler.Domain;
    public IBrowser? Browser { get; set; }
    public IServiceProvider? ServiceProvider { get; set; }

    public MainWindow() {
        InitializeComponent();
        Opened += OnOpened;
        Closed += OnClosed;
    }

    private async void OnOpened(object? sender, EventArgs e) {
        Browser = ServiceProvider.GetRequiredService<IEngineService>().CreateBrowser();
        BrowserView.InitializeFrom(Browser);
        await Browser.Navigation.LoadUrl(Url);
    }

    private void OnClosed(object? sender, EventArgs e) {
        Browser?.Dispose();
    }
}

Engine 本身由 IEngineService 管理,因此窗口只需在打开时请求新的浏览器实例,并在关闭时释放它。这样可以将 Engine 的生命周期和配置集中管理。

在桌面应用中加载 Web UI

将 Angular 应用移入嵌入式浏览器会改变其加载方式。 在生产环境中,编译后的压缩文件会被打包到桌面应用中,而非通过常规浏览器标签页的 http://localhost 地址提供服务。

不过,在开发阶段,您仍希望获得开发服务器的快速反馈和热重载功能。

在开发模式下,可保持常规 Angular 工作流:启动 ng serve 并加载开发 URL,这样热重载功能才能得以保留。

生产环境中的 Web UI 加载

在生产环境中,我们不希望依赖开发服务器,甚至不希望使用任何 HTTP 服务器。相反,嵌入式浏览器会加载类似 dnb://internal.host/ 这样的自定义协议 URL。

DotNetBrowser 可拦截该协议请求,并通过嵌入资源响应数据而非网络请求。配置此协议处理器后,Angular 应用完全由包内部提供:

EngineService.cs

var builder = new EngineOptions.Builder { RenderingMode = RenderingMode.HardwareAccelerated };
builder.Schemes.Add(Scheme.Create("dnb"), handler);

ResourceRequestHandler.cs

private string ConvertToResourcePath(string url) {
    string path = url.Replace(Domain, string.Empty, StringComparison.Ordinal);
    if (string.IsNullOrWhiteSpace(path) || path == "/") {
        path = "browser/index.html";
    }
    if (!path.StartsWith("browser/", StringComparison.OrdinalIgnoreCase)) {
        path = $"browser/{path}";
    }
    return prefix + path.TrimStart('/').Replace("/", ".");
}

完成资源加载配置后,剩下的关键步骤是让 Angular 与 .NET 之间进行信息交换。

Web 应用与 .NET 之间的通信

与任何 Web UI 一样,Angular 应用也需要与后端进行通信。这便是应用程序的 .NET 部分,负责处理业务逻辑,而 Web UI 则负责用户交互。关键问题在于如何将这两部分连接起来,同时确保在应用程序演进过程中保持可维护性。

JavaScript 与 .NET 的直接桥接

最直接的方式是将 .NET 方法直接暴露给 JavaScript。DotNetBrowser 支持这种方式。首先,定义一个 C# 类:

public class PrefsService {
    private readonly PreferencesStore store;

    public PrefsService(PreferencesStore store) {
        this.store = store;
    }

    public void SetAccountEmail(string email) {
        var account = store.GetAccount();
        account.Email = email;
        store.SetAccount(account);
    }
}

其次,在 TypeScript 中映射其结构:

declare class PrefsService {
    setAccountEmail(email: string): void;
}
declare const prefsService: PrefsService;
// ... prefsService.setAccountEmail('[email protected]');

最后,将 C# 对象注入 JavaScript 环境:

browser.InjectJsHandler = new Handler<InjectJsParameters>(p => {
    dynamic window = p.Frame.ExecuteJavaScript("window").Result;
    if (window != null) {
        window.prefsService = new PrefsService(store);
    }
});

对于小型项目来说,这种方式简单直观,也容易理解。但当 API 增长到不止几个方法时,就会在 C# 和 TypeScript 中重复定义同样的契约,并依赖人工保持同步。

为避免这种问题,该应用将桥接层视为一个小型 HTTP API,并使用工具自动生成客户端,具体做法将在 OpenAPI 章节中介绍。

OpenAPI

在这种架构中,Web 与 .NET 之间的契约通过 OpenAPI 进行一次性描述。基于同一份规范,可以生成强类型的 C# 和 TypeScript 模型与客户端,从而确保两端在 API 演进过程中始终保持同步。

在这里插入图片描述

Angular UI 与 .NET 处理程序的通信

处理 API 请求时,我们采用与加载网页相同的 dnb:/ 方案拦截器。此设计使应用保持自包含特性,避免暴露用户可在普通浏览器中访问的 localhost 端口或远程端点。

客户端方面,生成的 TypeScript 服务通过常规 fetch() API 进行通信。这确保 TypeScript 代码具备可移植性,并与常规浏览器环境兼容。

OpenAPI 契约概览

以下是我们用于演示项目的 OpenAPI 定义精简版:

paths:
  /account:
    get:
      responses:
        '200':
          content:
            application/json:
              schema: $ref: '#/components/schemas/Account'
    put:
      requestBody:
        required: true
        content:
          application/json:
            schema: $ref: '#/components/schemas/Account'
components:
  schemas:
    Account:
      type: object
      properties:
        email:
          type: string
        fullName:
          type: string
        twoFactorAuthentication:
          type: string
        biometricAuthentication:
          type: boolean

以下是 TypeScript 代码如何使用自动生成的 API 客户端:

const account = await DefaultService.getAccount();
const next: Account = {...account, email: '[email protected]'};
await DefaultService.putAccount(next);

在服务器端,相同的 OpenAPI 定义支持拦截器处理 dnb://internal.host/api/* 请求,并将它们路由到业务逻辑类:

ResourceRequestHandler.cs

private InterceptRequestResponse HandleApiRequest(
    InterceptRequestParameters parameters,
    string path) {
    return (method, trimmedPath) switch {
        ("GET", "account") => JsonResponse(parameters, store.GetAccount()),
        ("PUT", "account") => HandlePut<Account>(parameters, a => store.SetAccount(a)),
        ("GET", "profile-picture") => JsonResponse(parameters, store.GetProfilePicture()),
        ("PUT", "profile-picture") => HandlePut<ProfilePicture>(parameters, pic => store.SetProfilePicture(pic)),
        _ => CreateResponse(parameters, HttpStatusCode.NotFound)
    };
}

该架构确保协议类型安全、支持离线操作,并保持与常规浏览器环境的兼容性,因为它依赖标准 HTTP 语义。

总结

DotNetBrowser 与 Avalonia 提供了原生、跨平台的桌面外壳,而 Angular 与 PrimeNG 则带来了现代化的 Web UI。通过自定义协议加载 UI,可以保持应用完全自包含;而基于 OpenAPI 的契约,则允许 JavaScript 通过 fetch 调用 .NET,而无需额外启动服务器。

目录

  1. 为什么在桌面应用中使用 Web UI?
  2. C# 桌面应用中的 Web UI
  3. 应用窗口与网页视图
  4. 在桌面应用中加载 Web UI
  5. 生产环境中的 Web UI 加载
  6. Web 应用与 .NET 之间的通信
  7. JavaScript 与 .NET 的直接桥接
  8. OpenAPI
  9. OpenAPI 契约概览
  10. 总结

更多推荐文章

查看全部
  • Faster Whisper v1.7 本地语音转录工具使用指南(含 AMD 支持)
  • C++ 最小生成树详解
  • C++ 模板的幻觉:实例化、重定义与隐藏依赖
  • 商汤开源 SenseNova-MARS 多模态自主推理模型
  • Qwen3 + Qwen Agent 智能体开发实战:接入 MCP 工具详解
  • Docker 国内镜像源大全及配置方案
  • C++ CAS 原子操作详解与 ABA 问题解决方案
  • C++11 手写 Promise 实现及与 std::promise 对比
  • Java 面试高频场景题:Full GC、秒杀、幂等性与高可用设计
  • AI 产品经理面试准备指南:大厂真题与核心能力解析
  • QAnything 本地知识库问答系统:支持海量数据与跨语种 RAG 应用
  • 基于 C++11 手写前端 Promise 实现
  • 基于 FPGA 的 TDC 抖动测试系统设计与实现
  • WorkBuddy 接入 QQ 机器人配置指南
  • 智元机器人三大产线及技术产业化分析
  • LLaMA 衍生模型全景解析:官方演进与社区生态
  • C++ 内存管理技巧与最佳实践
  • 网络安全核心岗位盘点与职业发展方向
  • 基于 C++11 手写前端 Promise 实现
  • C++ string 标准库核心用法详解

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online