Blazor 混合开发_MAUI+Vue_WPF+Vue
Blazor 混合开发_MAUI+Vue_WPF+Vue
- 背景
- 混合开发的核心
- 为什么必须使用 wwwroot 文件夹放置 Web 项目文件
- 创建 MAUI 项目
- 创建 wwwroot 文件夹
- 服务注册
- 创建 _import.razor
- 添加 Main.razor 组件
- 修改 MainPage.xaml 文件
- 创建 WPF 项目
- 创建 wwwroot 文件夹
- 服务注册
- 创建 _import.razor
- 添加 Shell.razor 组件
- 修改 MainWindow.xaml 文件
- 创建 Vue 项目
- 修改创建好的 Vue 项目
- 执行 npm run build 命令
- Copy dist
- 修改 index.html 内容
- 效果预览
- Demo 下载
背景
在 MAUI 微软的官方方案是使用 Blazor 开发,但是当前市场大多数的 Web 项目使用 Vue、React 等技术构建,用Blazor重写整个项目并不现实。
Vue 是当前流行的 Web 框架, 简单来说是一套模板引擎,利用 “模板” 和 “绑定” 两大特性实现Web页面 MVVM 模式开发。利用 .NET MAUI 框架可以将 Vue 应用嵌入到 Web 容器中,可以实现跨平台的混合开发。
混合开发的核心
- 混合开发的核心工作是构建 Web 与 .NET 的互操作,我们将利用 Blazor 引擎的如下功能:
- 资源的统一管理
- js 代码的注入
- js 调用 C# 代码
- C# 调用 js 代码
为什么必须使用 wwwroot 文件夹放置 Web 项目文件
这个文件夹将是混合开发Web部分的根目录,这个名称不能随便定义

Microsoft.AspNetCore.Components.WebView.Maui库会将 wwwroot 文件夹里的内容作为 Maui 资源(MauiAsset)类型设置标签,编译器则会根据MauiAsset标签将这些内容打包进各个平台的资源文件夹。
创建 MAUI 项目
项目名字 MAUI.Vue.hybirddev

创建完成后编辑Hybrid.Maui.csproj,在Sdk最末尾加上.Razor,VS 会自动安装Microsoft.AspNetCore.Components.WebView.Maui 依赖包
不要手动 Nuget 添加这个包,否则程序无法运行


创建 wwwroot 文件夹
创建之后会自动变成网络资源文件夹
服务注册
- 使用扩展方法
builder.Services.AddMauiBlazorWebView()对 BlazorMauiWebView 组件服务进行注册
using Microsoft.Extensions.Logging;namespace MAUI.Vue.hybirddev
{public static class MauiProgram{public static MauiApp CreateMauiApp(){var builder = MauiApp.CreateBuilder();builder.UseMauiApp<App>().ConfigureFonts(fonts =>{fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");});builder.Services.AddMauiBlazorWebView(); // 注册#if DEBUGbuilder.Services.AddBlazorWebViewDeveloperTools();builder.Logging.AddDebug();
#endifreturn builder.Build();}}
}
创建 _import.razor
添加 → 类 → Razor 组件

导入 namespace
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Hybrid.Maui @*当前项目名称*@
添加 Main.razor 组件
- 被JS调用的方法必须是静态的
- Dispose 销毁页面资源,防止内存溢出
@inject IJSRuntime JSRuntime
@implements IDisposable@code {[JSInvokable]public static Task<string> Test(){return Task.FromResult("Maui Test Function");}public void Dispose(){}
}
修改 MainPage.xaml 文件
建立 BlazorWebView 控件铺满屏幕,并设置 HostPage 为Web部分的主页 index.html
<?xml version="1.0" encoding="utf-8" ?>
<ContentPagex:Class="Hybrid.Maui.MainPage"xmlns="http://schemas.microsoft.com/dotnet/2021/maui"xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"xmlns:local="clr-namespace:Hybrid.Maui"Shell.NavBarIsVisible="False"><BlazorWebView HostPage="wwwroot/index.html"><BlazorWebView.RootComponents><RootComponent ComponentType="{x:Type local:Main}" Selector="#blazorApp" /></BlazorWebView.RootComponents></BlazorWebView></ContentPage>
创建 WPF 项目
项目名字 Hybrid.Wpf

创建完成后编辑Hybrid.Wpf.csproj,在Sdk最末尾加上.Razor
同时在项目文件的现有 <PropertyGroup> 中,添加 <RootNamespace>Hybrid.Wpf</RootNamespace> 标记

安装 Nuget 包 Microsoft.AspNetCore.Components.WebView.Wpf

创建 wwwroot 文件夹
创建之后会自动变成网络资源文件夹

服务注册
- 通过依赖注入容器注入
AddWpfBlazorWebView()服务 - 在资源中添加已注册的服务
Resources.Add("services", Services) - 删除App.xaml 中的
StartupUri="MainWindow.xaml"
using System.Windows;
using Microsoft.Extensions.DependencyInjection;namespace Hybrid.Wpf
{/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class App : Application{public App(){Services = ConfigureServices();Resources.Add("services", Services);}public new static App Current => (App)Application.Current;public IServiceProvider Services { get; }protected override void OnStartup(StartupEventArgs e){Services.GetRequiredService<MainWindow>().Show();}private static IServiceProvider ConfigureServices(){var serviceCollection = new ServiceCollection();serviceCollection.AddSingleton<MainWindow>();serviceCollection.AddWpfBlazorWebView();#if DEBUGserviceCollection.AddBlazorWebViewDeveloperTools();
#endifreturn serviceCollection.BuildServiceProvider();}}
}
创建 _import.razor
添加 → 类 → Razor 组件

导入 namespace
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Hybrid.Wpf @*Object Namespace*@
添加 Shell.razor 组件
- 被JS调用的方法必须是静态的
- Dispose 销毁页面资源,防止内存溢出
@inject IJSRuntime JSRuntime
@implements IDisposable@code {[JSInvokable]public static Task<string> Test(){return Task.FromResult("Wpf Test Function");}public void Dispose(){}
}
修改 MainWindow.xaml 文件
建立 BlazorWebView 控件铺满屏幕,并设置 HostPage 为Web部分的主页 index.html
<Windowx:Class="Hybrid.Wpf.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:Hybrid.Wpf"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"Title="Hybrid.Wpf"d:Height="200"d:Width="450"WindowStartupLocation="CenterScreen"mc:Ignorable="d"><blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{DynamicResource services}"><blazor:BlazorWebView.RootComponents><blazor:RootComponent ComponentType="{x:Type local:Shell}" Selector="#blazorApp" /></blazor:BlazorWebView.RootComponents></blazor:BlazorWebView></Window>
创建 Vue 项目
通过命令 npm create vue@latest 前提是已安装 Node.js

执行命令尝试运行项目


修改创建好的 Vue 项目
DotNet.invokeMethodAsync("Hybrid.Maui", "Test") 第一个参数是容器项目的 Namespace,第二个参数是要调用的方法。
<script setup>
import { RouterLink, RouterView } from 'vue-router';
import HelloWorld from './components/HelloWorld.vue';/*** 访问 Hybrid.Wpf 项目中的 Test 方法*/
async function getTest() {await DotNet.invokeMethodAsync("Hybrid.Maui", "Test").then(res => {console.log(res);});
}
</script><template><header><img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" /><div class="wrapper"><HelloWorld msg="You did it!" /><nav><RouterLink to="/">Home</RouterLink><RouterLink to="/about">About</RouterLink></nav><button @click="getTest">To Hybrid.Maui Test</button></div></header><RouterView />
</template><style scoped>
header {line-height: 1.5;max-height: 100vh;
}.logo {display: block;margin: 0 auto 2rem;
}nav {width: 100%;font-size: 12px;text-align: center;margin-top: 2rem;
}nav a.router-link-exact-active {color: var(--color-text);
}nav a.router-link-exact-active:hover {background-color: transparent;
}nav a {display: inline-block;padding: 0 1rem;border-left: 1px solid var(--color-border);
}nav a:first-of-type {border: 0;
}@media (min-width: 1024px) {header {display: flex;place-items: center;padding-right: calc(var(--section-gap) / 2);}.logo {margin: 0 2rem 0 0;}header .wrapper {display: flex;place-items: flex-start;flex-wrap: wrap;}nav {text-align: left;margin-left: -1rem;font-size: 1rem;padding: 1rem 0;margin-top: 1rem;}
}
</style>
执行 npm run build 命令
执行 npm run build 命令发布 Vue 项目

Copy dist
将 dist 文件夹下的所有文件复制到容器项目下的 wwwroot 文件夹下

修改 index.html 内容
- JS、CSS 文件名一定要与编译后的文件名一致
- head 中的 JS 导入添加
crossorigin="anonymous"跨域支持 - 在 body 中导入
_framework/blazor.webview.js必须的,没有它玩不成
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title><script type="module" crossorigin="anonymous" src="/assets/index-lGWBURTF.js"></script><link rel="stylesheet" crossorigin href="/assets/index-bTbjHxa7.css">
</head>
<body><div id="app">Loading...</div><div id="blazorApp"></div><!-- Maui 项目需要添加 autostart="false" --><script src="_framework/blazor.webview.js" autostart="false"></script><!-- Wpf 项目不需要 --><script src="_framework/blazor.webview.js"></script>
</body>
</html>
效果预览
点击 To Hydrid.Wpf Test 按钮就可以在控制台打印出 C# 代码中的返回值

Demo 下载
https://github.com/Gun319/Hybrid
相关文章:
Blazor 混合开发_MAUI+Vue_WPF+Vue
Blazor 混合开发_MAUIVue_WPFVue 背景混合开发的核心为什么必须使用 wwwroot 文件夹放置 Web 项目文件 创建 MAUI 项目创建 wwwroot 文件夹服务注册创建 _import.razor添加 Main.razor 组件修改 MainPage.xaml 文件 创建 WPF 项目创建 wwwroot 文件夹服务注册创建 _import.razo…...
udp异步方式接收消息
C#实现 //定义结构体 public struct UdpState { public UdpClient u; public IPEndPoint e; } private UdpClient _client; //_client的初始化请参考其他资料 IPEndPoint remoteEP null; //TODO //public static bool mess…...
【RocketMQ笔记01】安装RocketMQ消息队列运行环境
这篇文章,主要介绍如何安装RocketMQ消息队列运行环境。 目录 一、RocketMQ消息队列 1.1、下载RocketMQ 1.2、解压安装包 1.3、配置RocketMQ环境变量 1.4、修改启动脚本 1.5、启动RocketMQ (1)启动NameServer (2࿰…...
使用 Privoxy 实现对多域名的定向转发
需求与思路 内网一台主机想要访问公网的两个不同站点, 想要实现访问两个站点时表现出不同的公网 IP 地址. 即在公网的站点服务器端看到的客户端 IP 是不同的. 思路是搭建两台具有不同公网 IP 的服务器, 分别安装配置 Privoxy 后进行串联, 并将其中一台作为主服务器暴露给内网…...
《PySpark大数据分析实战》-19.NumPy介绍ndarray介绍
📋 博主简介 💖 作者简介:大家好,我是wux_labs。😜 热衷于各种主流技术,热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员(PCTA)、TiDB数据库专家(PCTP…...
图解LRU缓存
图解LRU缓存 OJ链接 介绍 LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。 双向链表按照被使用的顺序存储了这些键值对,靠近尾部的键值对是最近使用的,而靠近头部的键值对是最久未…...
FFmpeg常见命令行
1、ffmpeg命令行 视频生成图片 ffmpeg -i test.mp4 -r 25 -f image2 data/image%3d.jpg这个命令行使用FFmpeg工具将视频文件(test.mp4)转换为一系列图像文件。 让我们逐个解释每个参数的含义: -i test.mp4: 指定输入文件为test.mp4。-i是F…...
智能优化算法应用:基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.斑马算法4.实验参数设定5.算法结果6.参考文献7.MA…...
《C++避坑神器·二十五》简单搞懂json文件的读写之遍历json文件读写
json.hpp库放在文章末尾 1、遍历json文件读写 (1)插入新键值对到json之情形1 原来json文件如下所示: {"Connection": {"IpAddress": "192.168.20.1","Rock": 0,"Solt": 1}, "Data…...
使用 fixture 机制重构 appium_helloworld
一、前置说明 在 pytest 基础讲解 章节,介绍了 pytest 的特性和基本用法,现在我们可以使用 pytest 的一些机制,来重构 appium_helloworld 。 appium_helloworld 链接: 编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来 代码目录结构: pytest.ini 设置: [pyt…...
基于python的excel检查和读写软件
软件版本:python3.6 窗口和界面gui代码: class mygui:def _init_(self):passdef run(self):root Tkinter.Tk()root.title(ExcelRun)max_w, max_h root.maxsize()root.geometry(f500x500{int((max_w - 500) / 2)}{int((max_h - 300) / 2)}) # 居中显示…...
Podman配置mongodb
文章目录 查询镜像拉取镜像查看镜像运行容器创建root用户 查询镜像 podman search mongo拉取镜像 podman pull docker.io/library/mongo查看镜像 podman images运行容器 podman run -d -p 27017:27017 --namemongodb-test docker.io/library/mongo创建root用户 podman exe…...
java实现矩阵谱峰搜索算法
矩阵谱峰搜索算法,也称为矩阵谱峰查找算法,是一种用于搜索二维矩阵中谱峰的方法。谱峰是指在矩阵中的一个元素,它比其上下左右四个相邻元素都大或相等。 该算法的基本思想是从矩阵的中间列开始,找到该列中的最大元素,…...
Jenkins的特殊操作定时自动执行任务以及测试报告调优
java -Dhudson.model.DirectoryBrowserSupport.CSP -jar Jenkins.war 测试报告 不美丽 执行上面的代码 重启jenkins 就好了...
【Grafana】Grafana匿名访问以及与LDAP连接
上一篇文章利用Docker快速部署了Grafana用来展示Zabbix得监控数据,但还需要给用户去创建账号允许他们登录后才能看展示得数据,那有什么办法让非管理员更方便得去访问Grafana呢?下面介绍两个比较方便实现的: 在开始设置前ÿ…...
elasticsearch-py 8.x的一些优势
早在 2022 年 2 月,当 Elasticsearch 8.0 发布时,Python 客户端也发布了 8.0 版本。它是对 7.x 客户端的部分重写,并带有许多不错的功能(如下所述),但也带有弃用警告和重大更改。今天,客户端的 7.17 版本仍然相对流行,每月下载量超过 100 万次,占 8.x 下载量的 ~50…...
RK3588平台开发系列讲解(AI 篇)RKNN 数据结构详解
文章目录 一、rknn_sdk_version二、rknn_input_output_num三、rknn_tensor_attr四、rknn_perf_detail五、rknn_perf_run六、rknn_mem_size七、rknn_tensor_mem八、rknn_input九、rknn_output沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解 RKNN 相关的数…...
2023版本QT学习记录 -6- UDP通信之UDP接收端
———————UDP接收端——————— 🎄动图演示 🎄发送端通信步骤思维导图 🎄添加组件 QT core gui network🎄添加头文件 #include "qudpsocket.h"🎄创建接收对象 QUdpSocket *recvsocket;&…...
C预处理 | pragma详解
欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和…...
轻松搭建知识付费小程序:让知识传播更便捷
明理信息科技saas知识付费平台 在当今数字化时代,知识付费已经成为一种趋势,越来越多的人愿意为有价值的知识付费。然而,公共知识付费平台虽然内容丰富,但难以满足个人或企业个性化的需求和品牌打造。同时,开发和维护…...
IP定位系统源码二开版 新增分销功能 PHP地理位置查询系统
概述 在大数据与精准营销时代,获取访问者的地理位置信息已成为许多业务场景(如广告推广、安全风控、用户画像分析)的核心需求。为了帮助开发者快速搭建此类服务,幽络源源码网特别分享这款IP定位系统源码二开版。这是一套基于PHP开…...
解决企业级日期处理难题:Vue3-DateTime-Picker的现代化架构设计与实战应用
解决企业级日期处理难题:Vue3-DateTime-Picker的现代化架构设计与实战应用 【免费下载链接】vue3-date-time-picker Datepicker component for Vue 3 项目地址: https://gitcode.com/gh_mirrors/vu/vue3-date-time-picker Vue3-DateTime-Picker是一款基于Vue…...
模型下载与转换实战:从HuggingFace到GGUF/SafeTensors,格式、量化与校验全解析
系列导读 你现在看到的是《本地大模型私有化部署与优化:从入门到生产级实战》的第 2/10 篇,当前这篇会重点解决:让你不再被模型格式和量化选项搞晕,确保下载和转换过程零失败。 上一篇回顾:第 1 篇《本地大模型部署前夜:硬件选型、环境搭建与框架对比(Ollama/vLLM/Lla…...
别让电源拖后腿!手把手教你用Sigrity PowerDC搞定PCB直流压降仿真(附HyperLynx SPD转换指南)
电源完整性实战:从零掌握Sigrity PowerDC直流压降仿真全流程 在高速PCB设计中,电源网络的稳定性往往决定了整个系统的可靠性。想象一下这样的场景:一款精心设计的硬件产品在实验室测试时频繁出现异常重启,经过两周的排查最终定位到…...
基于RAG的代码语义搜索:用自然语言对话你的Git仓库
1. 项目概述:当代码库遇上对话式AI如果你是一名开发者,每天都要和Git仓库打交道,那么你一定遇到过这样的场景:面对一个庞大或陌生的代码库,想快速了解某个功能的实现逻辑,或者想找到一段特定的代码…...
运算放大器在扫地机器人硬件设计中的六大关键应用解析
1. 项目概述:当扫地机器人遇上运算放大器扫地机器人,这个二十多年前还只是科幻电影里的概念,如今已经成了许多家庭的清洁主力。从最初的“随机碰撞式”清扫,到如今具备激光导航、自动集尘、智能拖地等复杂功能,它的“智…...
PCB设计规范-机插定位孔设计要求
核心要求1) 机插定位孔的孔径为4mm,只能是机械孔,孔内不能沉铜。2) 第一个机插定位孔位于PCB板长边的左下角,机插定位孔的中心与两板的距离都等于5mm。3) 第二机插定位孔仅位于PCB板长边的右下角,距离长边的板边5mm,离…...
别再只用AddModuleScore了!用irGSEA包一站式搞定单细胞基因集富集分析与8种可视化
单细胞基因集富集分析进阶指南:告别AddModuleScore,拥抱irGSEA的全能解决方案 在单细胞转录组数据分析中,基因集富集分析(Gene Set Enrichment Analysis, GSEA)是揭示细胞状态和功能特征的关键步骤。然而,许…...
前端技能树:从知识图谱到实战路径的系统学习指南
1. 项目概述:一个为掘金社区量身定制的技能树最近在GitHub上看到一个挺有意思的项目,叫Wscats/juejin-skills。光看名字,你可能会以为这是一个教你如何在掘金社区写爆款文章、玩转运营的“秘籍”。但点进去之后,你会发现它的内涵远…...
第26课:OpenClaw|日志审计与问题诊断
文章目录26.1 OpenClaw的日志体系与日志级别日志的“两个表面”日志级别的分层逻辑WebSocket日志的三级样式Cache-Trace日志:穿透Agent上下文的黑盒26.2 工作目录中的.jsonl日志文件分析三类关键日志文件读取日志的三种方式三类日志的关联追踪法26.3 结构化日志的收…...
