[MAUI]深入了解.NET MAUI Blazor与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页面上显示设备信息

项目地址
关注我,学习更多.NET MAUI开发知识!
[MAUI]深入了解.NET MAUI Blazor与Vue的混合开发的更多相关文章
- [MAUI] 在.NET MAUI中结合Vue实现混合开发
在MAUI微软的官方方案是使用Blazor开发,但是当前市场大多数的Web项目使用Vue,React等技术构建,如果我们没法绕过已经积累的技术,用Blazor重写整个项目并不现实. Vue是当前流 ...
- vue app混合开发蓝牙串口连接(报错java.io.IOException: read failed, socket might closed or timeout, read ret: -1;at android.bluetooth.BluetoothSocket.connect at js/BluetoothTool.js:329)
我使用的uni-app <template> <view class="bluetooth"> <!-- 发送数据 --> <view c ...
- Blazor和Vue对比学习(基础1.4):事件和子传父
Blazor和Vue的组件事件,都使用事件订阅者模式.相对于上一章的组件属性,需要多绕一个弯,无论Blazor还是Vue,都是入门的第一个难点.要突破这个难点,一是要熟悉事件订阅模式<其实不难& ...
- Blazor和Vue对比学习(进阶.路由导航一):基本使用
Blazor和Vue都是单文件组件SPA,路由的实现逻辑非常相似,页面路径的改变都是组件的切换,但因为各自语言的特性,在实现方式上有较大差异. 一.安装 1.Vue:Router是Vue的一个插件.如 ...
- windows环境下搭建vue+webpack的开发环境
前段时间一直在断断续续的看vue的官方文档,后来就慢慢的学习搭建vue的开发环境,已经有将近两周了,每到最后一步的时候就会报错,搞的我好郁闷,搁置了好几天,今天又接着搞vue的开发环境,终于成功了.我 ...
- vue-calendar 基于 vue 2.0 开发的轻量,高性能日历组件
vue-calendar-component 基于 vue 2.0 开发的轻量,高性能日历组件 占用内存小,性能好,样式好看,可扩展性强 原生 js 开发,没引入第三方库 Why Github 上很多 ...
- 第二节——vue多页面开发
我们平常用vue开发的时候总觉得vue好像就是专门为了单页面应用而诞生的,其实不是.因为vue在工程化开发的时候很依赖webpack,而webpack是将所有的资源整合到一块,弄成一个单页面. 但是v ...
- 移动端Tap与滑屏实战技巧总结以及Vue混合开发自定义指令
最近在忙混合开发,因交互相对复杂,所以也踩了很多坑.在此做一下总结. 1.tap事件的实际应用 在使用tap事件时,老生常谈的肯定是点透问题,大多情况下,在有滑屏交互的页面时,我们会在根节点阻止默认行 ...
- vue使用tradingview开发K线图相关问题
vue使用tradingview开发K线图相关问题 1.TradingView中文开发文档https://b.aitrade.ga/books/tradingview/CHANGE-LOG.html2 ...
- vue去掉严格开发,即去掉vue-cli安装时的eslint
vue去掉严格开发,即去掉vue-cli安装时的eslint : 1.vue-cli书写规范(主要是js规范) a.逗号.冒号后面要加空格 b.不能使用双引号,一律使用单引号 webpack的语法检查 ...
随机推荐
- Taurus .Net Core 微服务开源框架:Admin 插件【4-1】 - 配置管理-Kestrel【含https启用】
前言: 继上篇:Taurus .Net Core 微服务开源框架:Admin 插件[3] - 指标统计管理 本篇继续介绍下一个内容: 1.系统配置节点:App - Config 界面 界面图如下: 双 ...
- 2022蓝桥杯B组(java)版
2022蓝桥杯b组 A题 import java.math.BigInteger; public class A { public static void main(String[] args) { ...
- Flutter系列文章-Flutter环境搭建和Dart基础
Flutter是Google推出的一个开源的.高性能的移动应用开发框架,可以用一套代码库开发Android和iOS应用.Dart则是Flutter所使用的编程语言.让我们来看看如何搭建Flutter开 ...
- 因为一条DDL,差点搞挂整个系统,这次真的长了教训
有一次在线上提了一个sql变更,就是下面这条, -- 修改字段的数据类型由varchar(500)变更为text ALTER TABLE t MODIFY COLUMN name text; 提完之后 ...
- ASP.NET Core - 日志记录系统(一)
一.日志记录 日志记录是什么?简单而言,就是通过一些方式记录应用程序运行中的某一时刻的状态,保留应用程序当时的信息.这对于我们进行应用程序的分析.审计以及维护有很大的作用. 作为程序员,我们恐怕谁也不 ...
- Unity UGUI的PointerEventData的介绍及使用
Unity UGUI的PointerEventData的介绍及使用 1. 什么是PointerEventData? PointerEventData是Unity中UGUI系统中的一个重要组件,用于处理 ...
- zanePerfor中一套简单通用的Node前后端Token登录机制和github授权登录方式
HI!,你好,我是zane,zanePerfor是一款我开发的一个前端性能监控平台,现在支持web浏览器端和微信小程序端. 我定义为一款完整,高性能,高可用的前端性能监控系统,这是未来会达到的目的,现 ...
- Acwing 周赛88 题解
比赛链接 ·A题 题目描述 给定一个整数\(x\),请你找到严格大于\(x\)且各位数字均不相同的最小整数\(y\). \(1000 \le x \le 9000\) 做法分析 发现数据范围很小,那么 ...
- UI自动化执行过程中,隐藏浏览器页面
在执行UI自动化的过程中,浏览器总是会弹出,如果自动化环境是在个人办公笔记本,在工作过程中会影响正常办公.故需要将UI自动化执行时的浏览器隐藏. 代码实现如下: from selenium impor ...
- AI 与智能化 API 治理的探索实践
7月 Eolink 受邀参加 QECon 2023 全球软件质量&效能大会(北京站).Eolink CEO 刘昊臻,发表了主题为「AI 与智能化 API 治理的探索实践」的演讲,分享 Eoli ...