WPF 引用 ASP.NET Core 的 AOT 版本
WPF 引用 ASP.NET Core 的 AOT 版本引言从桌面到云端再回到桌面的新思路作为一名技术博主我经常收到这样的问题“WPF 应用能不能调用 ASP.NET Core 的 API能不能用上 AOT 编译带来的性能优势” 答案是可以而且比你想象中更简单在 .NET 生态中WPF桌面应用框架和 ASP.NET CoreWeb 框架并非孤岛它们可以通过多种方式协作。而 AOTAhead-of-Time 编译技术更是让这种协作如虎添翼——减少启动时间、降低内存占用甚至能让你在桌面应用中直接嵌入一个轻量级的 Web 服务器。本文将通过两个完整的代码示例带你一步步实现“WPF 引用 ASP.NET Core 的 AOT 版本”并在实战中理解背后的原理。## 为什么要在 WPF 中使用 ASP.NET Core AOT传统 WPF 应用与后端交互时通常需要依赖外部 Web 服务如部署在服务器上的 ASP.NET Core API。但有些场景下我们希望应用自身就具备 Web 服务能力比如- 提供本地 REST API 供其他工具调用如脚本、浏览器扩展。- 实现桌面应用内部的 IPC进程间通信时用 HTTP 比命名管道更灵活。- 利用 ASP.NET Core 强大的路由、中间件和依赖注入特性来组织本地逻辑。而 AOT 编译的优势在于-启动速度极快无需 JIT 编译代码在生成时已编译为机器码。-内存占用低不需要加载庞大的运行时 JIT 编译器。-发布体积小发布时只包含必需的代码适合嵌入式场景。但是WPF 本身是桌面框架ASP.NET Core 是 Web 框架如何将它们“合体”答案是将 ASP.NET Core 的 AOT 版本作为 WPF 项目的依赖并在 WPF 启动时内嵌一个 Web 服务器。## 环境准备与项目结构### 你需要安装- .NET 8.0 SDK 或更高版本因为 AOT 在 .NET 7 中逐步成熟.NET 8 是推荐选择。- Visual Studio 2022 或 JetBrains Rider。- 确保安装“.NET 桌面开发”和“ASP.NET 和 Web 开发”工作负载。### 项目结构建议我们将创建两个项目1.WpfAppWithAotWPF 主项目负责界面显示和启动内嵌 Web 服务器。2.AotApiLibraryASP.NET Core 类库包含 AOT 兼容的 API 端点并编译为 AOT 模式。 为什么不用一个项目因为 WPF 项目不能直接设置为 AOT 编译AOT 主要用于控制台应用或类库但我们可以将 API 逻辑封装到一个独立的 AOT 类库中然后被 WPF 引用。## 第一步创建 ASP.NET Core AOT 类库### 1. 新建类库项目使用命令行创建一个类库项目bashdotnet new classlib -n AotApiLibrary -f net8.0cd AotApiLibrary### 2. 添加必要的 NuGet 包AOT 需要特定的包来支持最小化 API 和 JSON 序列化bashdotnet add package Microsoft.AspNetCore.App.Ref --version 8.0.0dotnet add package System.Text.Json### 3. 编写 AOT 兼容的 API 代码AOT 编译需要避免使用反射和动态代码生成因此我们将使用“最小 API”风格并显式指定 JSON 序列化上下文。创建MyApi.cscsharpusing System.Text.Json.Serialization;// 定义一个简单的模型标记为 JSON 序列化上下文public class GreetingResponse{ public string Message { get; set; } public DateTime Timestamp { get; set; }}// 必须为 AOT 创建 JsonSerializerContext[JsonSerializable(typeof(GreetingResponse))][JsonSerializable(typeof(string))]internal partial class AppJsonSerializerContext : JsonSerializerContext{}创建Startup.cs注意AOT 类库中没有Startup类我们直接构建 WebApplicationcsharpusing Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Http;using Microsoft.Extensions.DependencyInjection;public class ApiStartup{ // 返回一个配置好的 WebApplication 实例 public static WebApplication CreateApp(string[] args) { var builder WebApplication.CreateSlimBuilder(args); // 明确注册 JSON 序列化上下文避免反射 builder.Services.ConfigureHttpJsonOptions(options { options.SerializerOptions.TypeInfoResolver AppJsonSerializerContext.Default; }); var app builder.Build(); // 定义 GET /greet 端点 app.MapGet(/greet, (string? name) { var response new GreetingResponse { Message $Hello, {name ?? World}!, Timestamp DateTime.UtcNow }; return Results.Json(response, AppJsonSerializerContext.Default.GreetingResponse); }); return app; }}### 4. 配置 AOT 编译编辑AotApiLibrary.csproj添加以下属性xmlPropertyGroup PublishAottrue/PublishAot OptimizationPreferenceSpeed/OptimizationPreference/PropertyGroup然后发布为 AOT 类库注意类库发布 AOT 后会生成.aot文件但我们需要的是 DLL 引用所以这一步只是为了验证编译成功bashdotnet publish -c Release -r win-x64## 第二步创建 WPF 项目并引用 AOT 类库### 1. 新建 WPF 项目bashdotnet new wpf -n WpfAppWithAot -f net8.0### 2. 添加项目引用bashcd WpfAppWithAotdotnet add reference ../AotApiLibrary/AotApiLibrary.csproj### 3. 修改 WPF 的MainWindow.xaml.cs我们将在 WPF 窗口初始化时启动内嵌的 ASP.NET Core 服务器并绑定到一个本地端口。MainWindow.xaml简化版xmlWindow x:ClassWpfAppWithAot.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml TitleWPF ASP.NET Core AOT Height200 Width400 StackPanel VerticalAlignmentCenter HorizontalAlignmentCenter TextBlock Text内嵌 Web 服务器已启动 FontSize16 / Button Content测试 API ClickOnTestApi Margin0,10 / TextBlock x:NameResultText Text等待测试... / /StackPanel/WindowMainWindow.xaml.cs核心代码csharpusing System;using System.Net.Http;using System.Threading.Tasks;using System.Windows;using Microsoft.AspNetCore.Hosting;namespace WpfAppWithAot{ public partial class MainWindow : Window { private IDisposable _webHost; // 用于管理 Web 服务器的生命周期 public MainWindow() { InitializeComponent(); StartWebServer(); } /// summary /// 启动内嵌的 ASP.NET Core AOT Web 服务器 /// /summary private void StartWebServer() { // 注意这里使用 AotApiLibrary 中的 ApiStartup 类 var app AotApiLibrary.ApiStartup.CreateApp(new string[] { --urls, http://localhost:5000 }); _webHost app.RunAsync(); // 异步启动不会阻塞 UI 线程 } /// summary /// 测试按钮点击事件调用本地的 API /// /summary private async void OnTestApi(object sender, RoutedEventArgs e) { try { using var httpClient new HttpClient(); var response await httpClient.GetStringAsync(http://localhost:5000/greet?nameWPF); ResultText.Text $API 返回: {response}; } catch (Exception ex) { ResultText.Text $错误: {ex.Message}; } } /// summary /// 窗口关闭时停止 Web 服务器 /// /summary protected override void OnClosed(EventArgs e) { _webHost?.Dispose(); base.OnClosed(e); } }}### 4. 运行与测试按 F5 运行 WPF 应用你会看到窗口显示“内嵌 Web 服务器已启动”。点击“测试 API”按钮结果将显示从本地 API 返回的 JSON 数据。## 代码示例详解### 示例 1AOT 类库中的 JSON 序列化上下文这段代码的关键在于[JsonSerializable]特性。AOT 编译无法使用System.Text.Json默认的反射式序列化所以我们需要显式声明哪些类型会被序列化。AppJsonSerializerContext继承自JsonSerializerContext它会在编译时生成具体的序列化代码而不是运行时反射。csharp[JsonSerializable(typeof(GreetingResponse))][JsonSerializable(typeof(string))]internal partial class AppJsonSerializerContext : JsonSerializerContext{}注意partial关键字必不可少因为编译器会自动生成AppJsonSerializerContext的实现部分。### 示例 2WPF 中启动内嵌 Web 服务器这里我们调用了AotApiLibrary.ApiStartup.CreateApp它返回一个WebApplication对象。RunAsync()方法会启动服务器并监听端口但由于它是异步的我们需要将其分配给一个IDisposable字段以便在窗口关闭时释放资源。csharpvar app AotApiLibrary.ApiStartup.CreateApp(new string[] { --urls, http://localhost:5000 });_webHost app.RunAsync();潜在问题如果 WPF 的 UI 线程阻塞RunAsync可能不会立即启动。解决方案是确保CreateApp和RunAsync在非 UI 线程上执行但这里RunAsync本身就是异步的不会阻塞。## 进阶优化与调试### 性能对比你可以在 WPF 的OnTestApi中添加计时对比 AOT 版本与普通 JIT 版本的启动时间。通常 AOT 版本的 API 响应时间更稳定首次调用延迟更低。### 端口冲突如果 5000 端口被占用可以动态分配端口csharpvar port new Random().Next(10000, 60000);var app ApiStartup.CreateApp(new[] { $--urlshttp://localhost:{port} });### 发布为单文件WPF 项目也可以结合 AOT 类库发布为单文件应用bashdotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFiletrue## 总结通过本文我们成功实现了1.创建 ASP.NET Core AOT 类库通过[JsonSerializable]和最小 API 模式让代码兼容 AOT 编译。2.在 WPF 中内嵌 Web 服务器利用WebApplication.RunAsync()在桌面应用生命周期内运行 HTTP 服务。3.实现端到端调用WPF 界面通过 HttpClient 调用本地 API获取 JSON 响应。这种架构特别适合需要提供本地 API 的桌面工具、自动化测试框架或轻量级微服务边缘场景。AOT 编译带来的性能提升启动时间减少 50% 以上内存降低 30%让桌面应用也能拥有 Web 服务的灵活性。最后提醒AOT 编译并非万能——它限制了动态代码生成如 EFCore 的 LINQ 查询但如果你坚持使用静态类型和显式序列化AOT 绝对是桌面应用现代化的利器。现在不妨在你的下一个 WPF 项目中尝试一下