[MAUI] 在.NET MAUI中結合Vue實現混合開發


 在MAUI微軟的官方方案是使用Blazor開發,但是當前市場大多數的Web項目使用Vue,React等技術構建,如果我們沒法繞過已經積累的技術,用Blazor重寫整個項目並不現實。

Vue是當前流行的web框架, 簡單來說是一套模板引擎,利用“模板”和“綁定”兩大特性實現web頁面mvvm模式開發。利用.NET MAUI框架可以將Vue應用嵌入到Web容器中。可以實現跨平台的混合開發。

例如我在某醫療行業項目中,已經用這個混合開發的方式生成應用,Vue代碼不需要做什么改動,就能跨平台運行:

如果你有一套Vue開發的網站,可以根據這篇文章,嘗試移值進你的iPhone,Android以及平板電腦等移動設備。

混合開發的核心工作是構建Web與.net 的互操作,我們將利用Blazor引擎的如下功能:

  • 資源的統一管理
  • js代碼的注入
  • js調用C#代碼
  • C#調用js代碼

如果你還不了解混合開發的概念,請回看上一章節[MAUI] 混合開發概念_jevonsflash的專欄-CSDN博客 https://blog.csdn.net/jevonsflash/article/details/121835547

整個工作分為MAUI部分,Vue部分和混合改造。

MAUI部分

創建Maui App項目:

你也可以創建 Maui Blazor App 項目,命名為MatoProject,但是這個模板主要圍繞Blazor開發,有的功能我們並不需要,得刪很多文件。

創建完成后編輯MatoProject.csproj,在Sdk最末尾加上.Razor,VS會自動安裝Microsoft.AspNetCore.Components.WebView.Maui依賴包(注意不要手動Nuget添加這個包,否則程序無法運行)

 安裝完成后在項目目錄中創建一個wwwroot文件夾

這個文件夾將是混合開發Web部分的根目錄,這個名稱不能隨便定義,我們看看為什么:

打開Microsoft.AspNetCore.Components.WebView.Maui.targets這個文件:

我們可以看到構建項目時,這個庫會將wwwroot文件夾里的內容作為Maui資源(MauiAsset)類型設置標簽,編譯器則會根據MauiAsset標簽將這些內容打包進各個平台的資源文件夾,具體的Maui資源類型可以參考這個文章.NET MAUI – Manage App Resources – Developer Thoughts (egvijayanand.in) ,

打開MauiProgram.cs 在builder 中注冊BlazorMauiWebView組件,在服務中使用擴展方法AddBlazorWebView()來添加相關Blazor的服務

using Microsoft.Maui; using Microsoft.Maui.Hosting; using Microsoft.Maui.Controls.Compatibility; using Microsoft.Maui.Controls.Hosting; using Microsoft.AspNetCore.Components.WebView.Maui; using Microsoft.Extensions.DependencyInjection; namespace MatoProject { public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .RegisterBlazorMauiWebView() .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); builder.Services.AddBlazorWebView(); return builder.Build(); } } }

打開MainPage.xaml,編輯原生應用的主頁面:

建立BlazorWebView控件鋪滿屏幕,並設置HostPage為Web部分的主頁index.html

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MatoProject.MainPage" xmlns:b="clr-namespace:Microsoft.AspNetCore.Components.WebView.Maui;assembly=Microsoft.AspNetCore.Components.WebView.Maui" BackgroundColor="{DynamicResource SecondaryColor}"> <Grid> <b:BlazorWebView HostPage="wwwroot/index.html"> <b:BlazorWebView.RootComponents> <b:RootComponent Selector="#blazorapp" x:Name="MainWebView" ComponentType="{x:Type local:Index}/> </b:BlazorWebView.RootComponents> </b:BlazorWebView> </Grid> </ContentPage>

 

建立_import.razor

@using System.Net.Http
@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 MatoProject

Vue部分

至此我們建立好了原生開發的Web容器,接下來需要處理Vue項目了:

cd到項目目錄,使用vue-cli創建一個空白Vue項目:

 這里可以按照Vue的編程喜好建立,比如我選擇了2.0項目,支持Typescript,es6的class命名方式等,最終都要通過webpack打包成靜態資源,所以無所謂。

建立src/api/fooService.ts,創建如下的函數:

window['DotNet']對象將是MAUI Blazor中注入的交互操作對象

export async function GetAll(data) { var result = null await window['DotNet'].invokeMethodAsync('MatoProject', 'GetFoo') .then(data => { console.log("DotNet method return the value:" + data); result = data }); return result } export async function Add(data) { var result = null await window['DotNet'].invokeMethodAsync('MatoProject', 'Add', data) .then(data => { console.log("DotNet method return the value:" + data); result = data }); return result }

打開Home.vue 編輯:

這是Web的主頁面,我們需要三個按鈕以及相關函數,測試js與C#的交互操作。

<template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <div> <h3>foo:</h3> <button @click="getFoo">click to get foo</button> <br /> <span>{{ foo }}</span> </div> <div> <h3>bar:</h3> <span>{{ bar }}</span> </div> <div> <button @click="add">click here to add</button> <span>click count:{{ cnt }}</span> </div> </div> </template>

<script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src import { GetAll, Add } from "@/api/fooService"; @Component({ components: { HelloWorld, }, }) export default class Home extends Vue { foo: string = ""; bar: string = ""; cnt: number = 0; async created() { window["postBar"] = this.postBar; } async add() { this.cnt = await Add({ a: this.cnt, b: 1 }); } async getFoo() { var foo = await GetAll(null); this.foo = foo; } async postBar(data) { this.bar = data; console.log("DotNet invocked the function with param:" + data); return this.bar; } } </script> 

到此已經完成了一個簡單的Vue項目

運行打包命令:

PS D:\Project\maui-vue-hybirddev\hybird-host> yarn build

將dist目錄中的所有內容復制到wwwroot文件夾下。

混合改造

這是混合開發的重點,改造MAUI項目,以適配Vue

打開wwwroot/index.js重寫為:

<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="icon" href="favicon.ico"> <title>hybird-host</title> <link href="js/about.dc8b0f2b.js" rel="prefetch"> <link href="css/app.03043124.css" rel="preload" as="style"> <link href="js/app.b6b5425b.js" rel="preload" as="script" crossorigin="anonymous"> <link href="js/chunk-vendors.cf6d8f84.js" rel="preload" as="script" crossorigin="anonymous"> <link href="css/app.03043124.css" rel="stylesheet"> </head> <body> <div id="blazorapp">Loading...</div> <script src="_framework/blazor.webview.js" autostart="false"></script> </body> </html> 

注意,僅全部重寫body部分,不要更改head的link標簽內容,僅在js后面加上crossorigin="anonymous" 以解決跨域問題。

建立Index.razor文件:

@using Microsoft.Maui.Controls
@inject IJSRuntime JSRuntime
@implements IDisposable
<noscript><strong>We're sorry but CareAtHome doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div> @code { [JSInvokable] public static Task<string> GetFoo() { return Task.FromResult("this is foo call C# method from js"); } [JSInvokable] public static Task<int> Add(AddInput addInput) { return Task.FromResult(addInput.a + addInput.b); } public async void Post(object o, EventArgs a) { await JSRuntime.InvokeAsync<string>("postBar", "this is bar call js method from C#"); } protected override async Task OnAfterRenderAsync(bool firstRender) { ((App.Current as App).MainPage as MainPage).OnPostBar += this.Post; try { if (firstRender) { await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/chunk-vendors.cf6d8f84.js", new { crossorigin = "anonymous" }); await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/app.b6b5425b.js", new { crossorigin = "anonymous" }); } } catch (Exception ex) { Console.WriteLine(ex); } } public void Dispose() { (Application.Current.MainPage as MainPage).OnPostBar -= this.Post; } } 

注意以下這兩個語句需要對應打包生成的實際文件名,並且加上跨域標簽

await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/chunk-vendors.cf6d8f84.js", new { crossorigin = "anonymous" }); await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/app.b6b5425b.js", new { crossorigin = "anonymous" }); 

 MainPage.xaml建立一個按鈕並且設置觸發事件方法:

<Button Text="Post Bar To WebView" HorizontalOptions="Center" VerticalOptions="End" HeightRequest="40" Clicked="PostBar_Clicked"></Button> 

 CodeBehind:

using System; using Microsoft.Maui.Controls; using Microsoft.Maui.Essentials; namespace MatoProject { public partial class MainPage : ContentPage { public event EventHandler<EventArgs> OnPostBar; int count = 0; public MainPage() { InitializeComponent(); } private async void PostBar_Clicked(object sender, EventArgs args) { OnPostBar?.Invoke(this, args); } } }

至此,所有的代碼工作已經完成,在PC上可以選擇Windows或者Android模擬器來運行程序

運行效果:

若在windows平台上運行,原生控件使用 Edge WebView2 呈現器加載頁面, 按f12會調用原生的調試工具,在這里看到打印

 現在,可能有人會問為什么要使用這樣的技術架構?明明可能有更好用的混合開發技術Ionic,React Native,Uni-app。首先不可否認這些技術都有他們的特點與優勢,但當你擁有一個成熟的Xamarin框架,你可以輕松遷移到MAUI,利用EFCore實現數據持久化或者集成Abp框架來配置依賴注入,全局事件,本地化等移動開發常用的功能(將Abp移植進.NET MAUI項目(一):搭建項目 - 林曉lx - 博客園 (cnblogs.com))。Xamarin是一個設備抽象層,提供的WebView也有較好的H5兼容性。

當然主要原因還是在快速開發上,你的代碼積累才是寶貴的,更少的修改代碼量才是王道,如果你在用React技術棧編寫Web代碼,也許React Native才是你最佳選擇 。沒有最優的技術,只有最適合你的技術。

 

 

 代碼倉庫:

jevonsflash/maui-vue-hybirddev: maui-vue-hybirddev (github.com)

jevonsflash/maui-vue-hybirddev (gitee.com)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM