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

背景

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

修改项目文件

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

xml 复制代码
<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 事件。

xaml 复制代码
<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 事件的代码来实现相应的加载动作。

csharp 复制代码
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 Core
            Exit += 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 控件后就可以查看了。

xaml 复制代码
<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 的监听地址。

csharp 复制代码
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 对象来获取监听的 Url
            var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses;
            SourceUrl = addresses is not null ? (addresses.FirstOrDefault() ?? "http://localhost:5000") : "http://localhost:5000";
            // 无 VM,用自身当 VM
            DataContext = this;
        }
    }
}

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

相关推荐
玖笙&2 天前
✨WPF编程基础【2.1】布局原则
c++·wpf·visual studio
玖笙&2 天前
✨WPF编程基础【2.2】:布局面板实战
c++·wpf·visual studio
SEO-狼术2 天前
.NET WPF 数据编辑器集合提供列表框控件
.net·wpf
FuckPatience6 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白6 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu6 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu7 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野7 天前
WPF——效果和可视化对象
wpf
He BianGu7 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者8 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf