在 WPF 中集成 ASP.NET Core 和 WebView2 用于集成 SPA 应用

news/2024/9/28 13:24:50

背景

我们有些工具在 Web 版中已经有了很好的实践,而在 WPF 中重新开发也是一种费时费力的操作,那么直接集成则是最省事省力的方法了。

修改项目文件

我们首先修改项目文件,让 WPF 项目可以包含 ASP.NET Core 的库,以及引用 WebView2 控件。

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>WinExe</OutputType><TargetFramework>net8.0-windows</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><UseWPF>true</UseWPF></PropertyGroup><ItemGroup><!-- 这里插入 WebView2 的包,用于显示网页 --><PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2478.35" /><!-- 这里插入 ASP.NET Core 的框架引用,用于代理资源文件 --><FrameworkReference Include="Microsoft.AspNetCore.App" /></ItemGroup><ItemGroup><!-- 这里模仿 ASP.NET Core,将 SPA 资源文件存于 wwwroot 文件夹下 --><None Update="wwwroot\**"><CopyToOutputDirectory>Always</CopyToOutputDirectory></None></ItemGroup></Project>

修改 App.xamlApp.xaml.cs 以使用 ASP.NET Core 的 WebApplication.CreateBuilder()

这里为了全局使用依赖注入,我们将 WebApplication.CreateBuilder() 放在 App.xaml.cs 中全局使用。为了使用依赖注入应注释掉默认启动窗口,并接管 Startup 事件。

<Application x:Class="WpfAircraftViewer.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfAircraftViewer"Startup="ApplicationStartup"><!-- 这里将 StartupUri 属性删除,然后注册 Startup 事件 --><Application.Resources></Application.Resources>
</Application>

然后通过修改 Startup 事件的代码来实现相应的加载动作。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using System.Windows;namespace WpfAircraftViewer
{/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class App : Application, IAsyncDisposable{public WebApplication? WebApplication { get; private set; }public async ValueTask DisposeAsync(){if (WebApplication is not null){await WebApplication.DisposeAsync();}GC.SuppressFinalize(this);}private async void ApplicationStartup(object sender, StartupEventArgs e){// 这里是创建 ASP.NET 版通用主机的代码var builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs());// 注册主窗口和其他服务builder.Services.AddSingleton<MainWindow>();builder.Services.AddSingleton(this);var app = builder.Build();// 这里是文件类型映射,如果你的静态文件在浏览器中加载报 404,那么需要在这里注册,这里我加载一个 3D 场景文件的类型var contentTypeProvider = new FileExtensionContentTypeProvider();contentTypeProvider.Mappings[".glb"] = "model/gltf-binary";app.UseStaticFiles(new StaticFileOptions{ContentTypeProvider = contentTypeProvider,});// 你如果使用了 Vue Router 或者其他前端路由了,需要在这里添加这句话让路由返回前端,而不是 ASP.NET Core 处理app.MapFallbackToFile("/index.html");WebApplication = app;// 处理退出事件,退出 App 时关闭 ASP.NET CoreExit += async (s, e) => await WebApplication.StopAsync();// 显示主窗口MainWindow = app.Services.GetRequiredService<MainWindow>();MainWindow.Show();await app.RunAsync().ConfigureAwait(false);}}
}

此时,我们已经可以正常开启一个默认界面的 MainWindow 了。

使用 WebView2 控件

这时我们就可以先将 SPA 文件从 npm 项目的 dist 复制到 wwwroot 了,在编辑 MainWindow 加入 WebView2 控件后就可以查看了。

<Window x:Class="WpfAircraftViewer.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfAircraftViewer"xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"mc:Ignorable="d" MinHeight="450" MinWidth="800" SnapsToDevicePixels="True"><!-- 在上面加入 xmlns:wv2 属性用于引用 WebView2 控件 --><Grid><!-- 这里插入 WebView2 控件,我们默认可以让 Source 是 http://localhost:5000,这是 ASP.NET Core 的默认监听地址 --><wv2:WebView2 Name="webView"Source="{Binding SourceUrl, FallbackValue='http://localhost:5000'}" AllowDrop="True" SnapsToDevicePixels="True"/></Grid>
</Window>

我们可以继续编辑窗口的信息,让他可以关联 ASP.NET Core 的监听地址。

using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Windows;namespace WpfAircraftViewer
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public string SourceUrl { get; set; }public MainWindow(IServer server){InitializeComponent();// 这里通过注入的 IServer 对象来获取监听的 Urlvar addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses;SourceUrl = addresses is not null ? (addresses.FirstOrDefault() ?? "http://localhost:5000") : "http://localhost:5000";// 无 VM,用自身当 VMDataContext = this;}}
}

这时我们就可以看到窗口打开了我们的 SPA 页面了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/30278.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

【湿地探秘】守护蓝色星球的绿色之肾

在地球的广阔画卷中,湿地犹如镶嵌其中的翡翠,不仅孕育着丰富的生物多样性,更是自然界不可或缺的调节器。今天,让我们一同深入了解湿地的基本概念、我国湿地的概貌、湿地的多样类型,以及保护湿地对于人类和地球的深远意义。湿地的基本概念湿地,被誉为“地球之肾”,是地球…

云效 Pipeline as Code 来了!这些场景,用好它效率翻倍!

提到 YAML,不少同学首先想到的是使用门槛。云效 Flow 内置了丰富的 YAML 模板以及 YAML 手册,支持 YAML 语法自动补齐、实时校验并推荐修复方案,以及多种快捷键操作等,旨在帮助开发者降低 YAML 使用门槛,提升 YAML 编写效率。从可视化编排到支持 YAML 编排 云效流水线 Flo…

递归+回溯解决有效括号问题

题目描述: 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合 例如:当n=1 时,有效的括号组合[()]当n=2时,有效的括号组合[(()),()()]当n=3时,有效的括号组合有[((())),(()()),(())().()(()),()()()]解题思路:先递归生成n个左括号…

正义使者其二

大赢特赢,最正义的一集!\(\Huge{还有体活\,赢!}\)

..\HAL_LIB\Inc\stm32l4xx_hal_rcc_ex.h(2424): error: #20: identifier HAL_StatusTypeDef is undefined

stm32工程编译时遇到这个错误,显示HAL_StatusTypeDef没有被定义,但是go to definition又能找到定义 后来在网上寻找解决办法,结果发现竟然是 #include "stm32l4xx_hal_spi.h"#include "stm32l4xx_hal.h" 这两个的顺序问题,#include "stm32l4xx_h…

LOTO示波器动作编程功能(命令批处理)

动作编程功能是为了方便客户根据自己的应用场景,做到一个按键就连续做多个示波器操作,从而降低了对操作人员的技术要求,做到傻瓜式操作。之前LOTO有个类似的功能,是把示波器的基础设置根据不同的测试场景存成不同的设置文件,需要时可以选择合适的场景设置导入进来这个设置…

CF207C3 Game with Two Trees

CF207C3 Game with Two Trees 妙到家的树上字符串问题。 约定树 \(1\):\(t_1\)。树 \(2\):\(t_2\)。\(S_{1/2}(i,l)\) 为树 \(1/2\) 上节点 \(i\) 沿父亲走经过 \(l\)​ 条边所构成的字符串。\(E_{1/2}(u,v)\) 为树 \(1/2\) 上,连接节点 \(u,v\)​ 的边的字符。\(fa_{1/2}u\…

Python环境变量设置与读取

★ 环境变量基本概念环境变量定义环境变量是操作系统中存储有关操作系统配置信息和应用程序运行环境的动态值的一种机制。环境变量的主要作用是为正在运行的进程提供配置信息,帮助程序找到所需的资源或者确定程序运行的方式。在操作系统中,每个进程都有自己的环境变量集合。这…