天天看點

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

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

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

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

[MAUI] 在.NET MAUI中結合Vue實作混合開發
[MAUI] 在.NET MAUI中結合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] 在.NET MAUI中結合Vue實作混合開發
[MAUI] 在.NET MAUI中結合Vue實作混合開發

你也可以建立 Maui Blazor App 項目,命名為MatoProject,但是這個模闆主要圍繞Blazor開發,有的功能我們并不需要,得删很多檔案。

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

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

 安裝完成後在項目目錄中建立一個wwwroot檔案夾

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

這個檔案夾将是混合開發Web部分的根目錄,這個名稱不能随便定義,我們看看為什麼:

打開Microsoft.AspNetCore.Components.WebView.Maui.targets這個檔案:

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

我們可以看到建構項目時,這個庫會将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();
		}
	}
}           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

打開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>           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

建立_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
           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

Vue部分

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

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

[MAUI] 在.NET MAUI中結合Vue實作混合開發
[MAUI] 在.NET MAUI中結合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
}           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

打開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>           
[MAUI] 在.NET MAUI中結合Vue實作混合開發
<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>
           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

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

運作打包指令:

PS D:\Project\maui-vue-hybirddev\hybird-host> yarn build           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

将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>


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

注意,僅全部重寫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;
    }


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

注意以下這兩個語句需要對應打包生成的實際檔案名,并且加上跨域标簽

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" });
           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

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

<Button Text="Post Bar To WebView" HorizontalOptions="Center" VerticalOptions="End" HeightRequest="40" Clicked="PostBar_Clicked"></Button>
           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

 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);
		}
	}
}           
[MAUI] 在.NET MAUI中結合Vue實作混合開發

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

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

運作效果:

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

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

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

 現在,可能有人會問為什麼要使用這樣的技術架構?明明可能有更好用的混合開發技術Ionic,React Native,Uni-app。首先不可否認這些技術都有他們的特點與優勢,但當你擁有一個成熟的Xamarin架構,你可以輕松遷移到MAUI,利用EFCore實作資料持久化或者內建Abp架構來配置依賴注入,全局事件,本地化等移動開發常用的功能(另一篇文章将會教大家如何将Abp移值進MAUI)。Xamarin是一個裝置抽象層,提供的WebView也有較好的H5相容性。

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

 代碼倉庫:

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

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

繼續閱讀