[MAUI]深入了解.NET MAUI Blazor与Vue的混合开发
文章目录
- Vue在混合开发中的特点
- 创建MAUI项目
- 创建Vue应用
- 使用element-ui组件库
- JavaScript和原生代码的交互
- 传递根组件参数
- 从设备调用Javascript代码
- 从Vue页面调用原生代码
- 读取设备信息
- 项目地址
.NET MAUI结合Vue的混合开发可以使用更加熟悉的Vue的语法代替Blazor语法,你现有项目不必重写。之前写过一篇 [MAUI] 在.NET MAUI中结合Vue实现混合开发 ,其中介绍了如何创建一个vue应用并将其打包至MAUI项目,这种方式依赖vue-cli创建和打包静态站点,好处是可以使用Node.js 的构建但MAUI仅仅作为容器。开发应用需要一个独立的host项目
这次用集成的方式。将vue作为MAUI的一部分,这样就可以在MAUI项目中直接使用vue了。

Vue在混合开发中的特点
首先要说的是,Vue框架是渐进性的,所谓渐进性,就是Vue不会强求你使用所有的框架特性,你可以根据需要逐步使用。
同样地,element-ui也可以通过引入样式和组件库,配合Vue使用
因此我们不需要Vue Router、Vuex、Vue CLI、单文件组件这些高级特性,仅仅引入Vue.js即可使用Vue模板语法。我们将利用Blazor引擎的如下功能:
- 组件化开发
- 静态资源管理
- js代码的注入
- js调用C#代码
- C#调用js代码
由.NET MAUI提供的功能:
- 路由管理
- 状态管理
由Vue提供模板语法,事件处理,计算属性/侦听器等,以及Element-UI提供交互组件。
创建MAUI项目
创建一个MAUI项目,这里使用的是Visual Studio 2022 17.7.3,创建一个Blazor MAUI App项目命名MAUI-Vue-Hybriddev-Integrated,选择Android和iOS作为目标平台,选择.NET 7.0作为目标框架。

从Vue官网下载最新的Vue.js

将其放置在wwwroot目录下,然后在index.html中引入

<script src="lib/vuejs/vue.js"></script>
创建Vue应用
在Views目录下创建 HomePage.xaml作为Vue应用的容器,在页面中创建<BlazorWebView>视图元素,并设置HostPage为wwwroot/index.html,这样就可以在MAUI中使用Vue了。
<BlazorWebView x:Name="blazorWebView"HostPage="wwwroot/index.html"><BlazorWebView.RootComponents><RootComponent Selector="#app"x:Name="rootComponent"ComponentType="{x:Type views:HomePageWeb}" /></BlazorWebView.RootComponents>
</BlazorWebView>
每个BlazorWebView控件包含根组件(RootComponent)定义,ComponentType是在应用程序启动时加载页面时的类型,该类型需要继承自Microsoft.AspNetCore.Components.IComponent,由于我们的导航是由MAUI处理的,因此我们不需要使用Blazor路由,直接使用Razor组件
在Views目录下创建HomePageWeb.razor,这是Vue应用页面相当于Vue的单文件组件,这里可以使用Vue的模板语法,而不是Blazor的Razor语法。

我们在HomePageWeb.razor中写下Vue官方文档中Hello Vue示例代码
<div id="vue-app">{{ message }}
</div><script type="text/javascript">var app = new Vue({el: '#vue-app',data: {message: 'Hello Vue!',}})</script>
注意:Vue的根元素名称不要跟Blazor的根元素名称相同,否则会报错。

此时更改JavaScript里的内容,你会发现Blazor页面不会热加载。
请勿将 <script> 标记置于 Razor 组件文件 (.razor) 中,因为 <script> 标记无法由Blazor 动态更新。
于是需要将script部分代码放置在外部,此时有两种方案,一个是放在wwwroot/js目录下,然后在wwwroot/index.html中引入,还有一种是使用并置的js文件,这种方式是所谓的"CodeBehind",因为更利于组织代码,这里我们使用并置的js文件。
创建一个HomePageWeb.razor.js文件,将script部分代码放置在其中,然后在HomePageWeb.razor中引入

protected override async Task OnAfterRenderAsync(bool firstRender)
{if (firstRender){await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./Views/HomePageWeb.razor.js");}
}
发布应用后,框架会自动将脚本移动到 Web 根目录。 在此示例中,脚本被移动到./wwwroot/Views/HomePageWeb.razor.js
使用element-ui组件库
同样,我们在element-ui官方CDN下载样式文件和组件库,首先在index.html中引入样式和组件库
<link href="css/app.css" rel="stylesheet" />
...
<script src="lib/element-ui/index.js"></script>
然后在HomePageWeb.razor中使用组件
<div id="vue-app">{{ message }}<el-input v-model="input" placeholder="请输入内容"></el-input><el-button @click="showDialog = true">提交</el-button><el-dialog :visible.sync="showDialog" title="消息"><p>{{input}}</p><p>提交成功</p></el-dialog>
</div>
CodeBehind中引入组件
var app = new Vue({el: '#vue-app',data: {message: 'Hello Vue!',showDialog: false,input: 'text message from vue'}
})
运行效果如下:


JavaScript和原生代码的交互
Blazor组件中的代码可以通过注入IJSRuntime来调用JavaScript代码,JavaScript代码可以通过调用DotNet.invokeMethodAsync来调用C#代码。
传递根组件参数
如果被调用的代码位于其他类中,需要给这个Blazor组件传递实例,还记得刚才提及的根组件(RootComponent)吗?我们用它来传递这个实例,称之为根组件参数,详情请查看官方文档 在 ASP.NET Core Blazor Hybrid 中传递根组件参数
创建SecondPage.xaml,根据刚才的步骤创建一个BlazorWebView并注入vuejs代码
html部分创建一个el-dialog组件,当消息被接收时,显示对话框
@using Microsoft.Maui.Controls
@inject IJSRuntime JSRuntime<div id="vue-app">{{ message }}<el-dialog :visible.sync="showDialog" title="Native device msg recived!"><p>{{msg}}</p></el-dialog>
</div>
在@code代码段中创建SecondPage对象。
@code {[Parameter]public SecondPage SecondPage { get; set; }...
}
回到SecondPage.xaml.cs,在构造函数中将自己传递给根组件参数
public SecondPage()
{InitializeComponent();rootComponent.Parameters =new Dictionary<string, object>{{ "SecondPage", this }};
}
从设备调用Javascript代码
在SecondPage.xaml中,创建一个Post按钮,点击按钮后将文本框PostContentEntry的内容传递给Vue代码
<StackLayout Grid.Row="1"><Entry x:Name="PostContentEntry" Text="Hello,this is greetings from native device"></Entry><Button Text="Post To Vue"HorizontalOptions="Center"VerticalOptions="End"HeightRequest="40"Clicked="Post_Clicked"></Button></StackLayout>

在SecondPage.razor.js中, 创建greet方法,用于接收从原生代码传递过来的参数,并显示在对话框中。
window.app = new Vue({el: '#vue-app',data: {message: 'Vue Native interop',showDialog: false,msg: ''},methods: {greet: function (content) {this.msg = content;this.showDialog = true;}},
在SecondPage.xaml.cs中,创建一个OnPost事件,当Post按钮被点击时触发该事件
public event EventHandler<OnPostEventArgs> OnPost;private void Post_Clicked(object sender, EventArgs args)
{OnPost?.Invoke(this, new OnPostEventArgs(this.PostContentEntry.Text));
}
在SecondPage.razor中,订阅OnPost事件,当事件被触发时,调用greet方法,将参数传递给JavaScript代码
public async void Recived(object o, OnPostEventArgs args)
{await JSRuntime.InvokeAsync<string>("window.app.greet", args.Content);
}protected override async Task OnAfterRenderAsync(bool firstRender)
{try{if (firstRender){SecondPage.OnPost += this.Recived;await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./Views/SecondPageWeb.razor.js");}}catch (Exception ex){Console.WriteLine(ex);}}
在页面销毁时,要取消订阅事件,避免内存泄漏。
@implements IDisposable...public void Dispose()
{SecondPage.OnPost -= this.Recived;
}
运行效果如下

从Vue页面调用原生代码
原生代码指的是.NET MAUI平台的C#代码,比如要在设备上显示一个弹窗,需要调用Page.DisplayAlert方法,它隶属于Microsoft.Maui.Controls命名空间,属于MAUI组件库的一部分。
因此需要将MAUI类型的对象通过引用传递给JavaScript调用,调用方式是通过将对象实例包装在 DotNetObjectReference 中传递给JavaScript。使用该对象的invokeMethodAsync从 JS 调用 .NET 实例方法。详情请查看官方文档 JavaScript 函数调用 .NET 方法
在@code代码段中,界面加载时创建DotNetObjectReference对象
@code {private DotNetObjectReference<SecondPageWeb>? objRef;protected override void OnInitialized(){objRef = DotNetObjectReference.Create(this);}
页面加载完成时,将DotNetObjectReference对象传递给JavaScript代码
protected override async Task OnAfterRenderAsync(bool firstRender)
{try{if (firstRender){SecondPage.OnPost += this.Recived;await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./Views/SecondPageWeb.razor.js");await JSRuntime.InvokeAsync<string>("window.initObjRef", this.objRef);}}catch (Exception ex){Console.WriteLine(ex);}}
window.app = new Vue({...data: {...objRef: null},})
window.initObjRef = function (objRef) {window.app.objRef = objRef;
}
在SecondPage.razor中,创建el-input组件和el-button组件,当按钮被点击时,调用Post方法,将文本框的内容传递给原生代码
<div id="vue-app">{{ message }}<el-input v-model="input" placeholder="请输入内容"></el-input><el-button @click="post">Post To Native</el-button><el-dialog :visible.sync="showDialog" title="Native device msg recived!"><p>{{msg}}</p></el-dialog>
</div>
按钮和对话框的显示逻辑与之前相同,不再赘述。

在SecondPage.razor中,创建Post方法,方法被调用时,将触发MAUI组件库的原生代码
[JSInvokable]
public async Task Post(string content)
{await SecondPage.DisplayAlert("Vue msg recived!", content, "Got it!");}
vue绑定的函数中,调用DotNet.invokeMethodAsync将文本框的内容传递给原生代码
window.app = new Vue({el: '#vue-app',data: {message: 'Vue Native interop',showDialog: false,msg: '',input: 'Hi, I am a text message from Vue',deviceDisplay: null,objRef: null},methods: {greet: function (content) {this.msg = content;this.showDialog = true;},post: function () {this.objRef.invokeMethodAsync('Post', this.input);}}
})
运行效果如下

读取设备信息
可以使用Vue的watch属性监听数据变化,当MAUI对象加载完成时,调用原生代码,读取设备信息
<div id="vue-app">...<p>Device Display</p><p>{{deviceDisplay}}</p>
</div>
CodeBehind代码如下:
watch: {objRef: async function (newObjRef, oldObjRef) {if (newObjRef) {var deviceDisplay = await this.objRef.invokeMethodAsync('ReadDeviceDisplay');console.warn(deviceDisplay);this.deviceDisplay = deviceDisplay;}}
},
原生代码如下:
[JSInvokable]
public async Task<string> ReadDeviceDisplay()
{return await Task.FromResult(SecondPage.ReadDeviceDisplay());}
在ReadDeviceDisplay方法中,我们读取设备分辨率、屏幕密度、屏幕方向、屏幕旋转、刷新率等信息
public string ReadDeviceDisplay()
{System.Text.StringBuilder sb = new System.Text.StringBuilder();sb.AppendLine($"Pixel width: {DeviceDisplay.Current.MainDisplayInfo.Width} / Pixel Height: {DeviceDisplay.Current.MainDisplayInfo.Height}");sb.AppendLine($"Density: {DeviceDisplay.Current.MainDisplayInfo.Density}");sb.AppendLine($"Orientation: {DeviceDisplay.Current.MainDisplayInfo.Orientation}");sb.AppendLine($"Rotation: {DeviceDisplay.Current.MainDisplayInfo.Rotation}");sb.AppendLine($"Refresh Rate: {DeviceDisplay.Current.MainDisplayInfo.RefreshRate}");var text = sb.ToString();return text;
}
当页面加载时,会在HTML页面上显示设备信息

项目地址
Github:maui-vue-hybirddev
关注我,学习更多.NET MAUI开发知识!
相关文章:
[MAUI]深入了解.NET MAUI Blazor与Vue的混合开发
文章目录 Vue在混合开发中的特点创建MAUI项目创建Vue应用使用element-ui组件库JavaScript和原生代码的交互传递根组件参数从设备调用Javascript代码从Vue页面调用原生代码 读取设备信息项目地址 .NET MAUI结合Vue的混合开发可以使用更加熟悉的Vue的语法代替Blazor语法ÿ…...
1209. 带分数
题目: 1209. 带分数 - AcWing题库 思路: 1.targetab/c,由题意a,b,c会包含1~9 且每个数出现且只能出现一次。我们可以抽象化为9个坑位分成3份分别给a,b,c。 2.先采用递归搜索树写出9个坑位的全排列,再分成3个区,分…...
【树莓派触摸屏等学习笔记】
前言 树莓派触摸屏 提示:以下是本篇文章正文内容,下面案例可供参考 一、触摸屏硬件驱动 出现黑屏的时候,恢复一下txt config.txt 全屏显示 showFull Exec :自启动 surf 算法 特征点识别 算法的复杂度挺高的 特性树莓派强大…...
ERR_PNPM_JSON_PARSE Unexpected end of JSON input while parsing empty string in
终端报错: ERR_PNPM_JSON_PARSE Unexpected end of JSON input while parsing empty string in 报错原因:依赖没有删除干净 解决办法: ①删除node_modules ②在package.json的dependencies删除不需要依赖 ③重新pnpm i...
linux基础IO
文章目录 前言一、基础IO1、文件预备知识1.1 文件类的系统调用接口1.2 复习c语言接口 2、文件类的系统调用接口2.1 open系统调用2.2 close系统调用2.3 write系统调用2.4 read系统调用 3、文件描述符3.1 文件描述符fd介绍3.2 文件描述符fd分配规则与重定向3.3 重定向原理3.4输入…...
华为OD机试 - TLV格式 - 逻辑分析(Java 2023 B卷 100分)
目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷&#…...
LLMs之RAG:利用langchain实现RAG应用五大思路步骤—基于langchain使用LLMs(ChatGPT)构建一个问题回答文档的应用程序实战代码
LLMs之RAG:利用langchain实现RAG应用五大思路步骤—基于langchain使用LLMs(ChatGPT)构建一个问题回答文档的应用程序实战代码 目录 相关文章...
链式队列----数据结构
队列的基本概念 队列是一种操作受限的线性表(先进先出),只允许在队尾插入,队头删除。 例如去银行办理业务,肯定是先来的先出去,后来的人排在后方,只有第一个人业务办理完了,才会有…...
VM虚拟机VMware Fusion(13.5.0)
VMware Fusion提供了在Apple Mac上运行Windows、Linux等操作系统的最佳方式,无需重新启动。Fusion 13支持运行macOS 12及更高版本的Intel和Apple Silicon Mac,并包含面向开发人员、IT管理员和日常用户的功能。 Fusion 13 新增功能 支持新的客户机操作系…...
自动化测试08
Junit 为什么学了Selenium还需学习Junit Selenium自动化测试框架;Junit单元测试框架。 拿着一个技术写自动化测试用例(Selenium3) 拿着一个技术管理已经编写好的测试用例(Junit5) Junit相关的技术 Junit是针对Java的一…...
d3dx9_43.dll丢失有什么办法可以解决,解决d3dx9_43.dll丢失
通常d3dx9_43.dll丢失都是在运行游戏时汤出的d3dx9_43.dll找不到的错误窗口,因为d3dx9_43.dll文件更多是在使用游戏时会被调用的dll文件,d3dx9_43.dll是属于DirectX9的一个组件,DirectX9是游戏系统中的一个重要程序,所以当d3dx9_4…...
【C++】: auto关键字(C++11)+基于范围的for循环(C++11)+指针空值nullptr(C++11)
auto关键字(C11) 随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在: 类型难于拼写含义不明确导致容易出错 #include <string> #include <map> int main() {std::map<std::string, std::…...
华为OD机试 - 玩牌高手 - 动态规划(Java 2023 B卷 100分)
目录 一、题目描述二、输入描述三、输出描述四、解题思路具体规则如下:具体步骤如下: 五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中,刷题点这里 一、题目描述 给定一个长度为n的整型数组࿰…...
【java】【重构二】分模块开发版本锁定以及耦合(打包)实战
目录 一、创建dependencyManagement标签 二、 将需要版本控制的依赖版本进行标签设置 三、将需要版本控制的依赖从各子模块迁移到此处 四、将父模块的依赖版本控制 五、删除子模块的全部版本 1、bocai-web-management模块 2、bocai-utils模块 六、打包 1、确定代码都…...
Excel提高工作效率常用功能
定位快捷键使用 CtrlG或者F5 根据不同类别插入空行 例:以下表,以部门为单位,每个部门后插入空白行 部门姓名出勤基本工资岗位津贴公体加班绩效基数工龄应发合计财务部姓名73115002101710财务部姓名11116006003401502363财务部姓名5271000…...
物联网_00_物理网介绍
1.物联网为什么会出现? 一句话-----追求更高品质的生活, 随着科技大爆炸, 人类当然会越来越追求衣来伸手饭来张口的懒惰高品质生活, 最早的物联网设备可以追溯到19世纪末的"在线可乐售卖机"和"特洛伊咖啡壶"(懒惰的技术人员为了能够实时看到物品的情况而设…...
华为智选SF5,AITO问界的车怎么样
#华为智选 #赛力斯SF5 #aito问界m5 #aito问界m7 #华为汽车 华为的车,后杠焊两点,拉车的时候,拖车钩断了,后杠拉出来了,这质量可以吗?是否应该全部召回?M5,M7是不是也这样?…...
大数据测试用例分析
基于大数据分析,对业务系统产生的日志进行智能分析,能够识别日志中的接口、参数、业务流,并依据分析的结果生成测试用例。 问题与背景 业务复杂 业务系统的复杂性,对测试人员的业务能力提出严格要求,加重测试成本。 …...
Java中的泛型:高效编程的利器
泛型—— 一种可以接收数据类型的数据类型。泛型是Java中的一种参数化类型机制,通过类型参数,可以在类、接口和方法中实现通用的代码。 泛型的引入 泛型(Generics)是 Java 编程语言中引入的一个重要特性,它可以让程序…...
Mysql的關鍵字或者保留字
1.group 不能用group作爲字段名 ### SQL: insert INTO biz_customer_group ( id,customer,group ) VALUES (?,?,?) ON DUPLICATE key update customer VALUES(customer), group VALUES(group) ### Cause: java.sql.SQLSyntaxErrorException: You have an error in your S…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...
