.NET MAUIでVueと組み合わせたハイブリッド開発

.NET MAUIでVueと組み合わせたハイブリッド開発

MAUIでは、Microsoftの公式ソリューションはBlazorを使用した開発ですが、現在の市場のほとんどのWebプロジェクトはVue、Reactなどの技術で構築されています。蓄積された技術を避けて通ることはできず、プロジェクト全体をBlazorで書き直すのは現実的ではありません。

最終更新 2022/01/18 22:11
林 小
読了目安 8 分
カテゴリ
MAUI
タグ
.NET C# Blazor MAUI Vue

MAUI の公式ソリューションは Blazor を使用した開発ですが、現在の市場におけるほとんどの Web プロジェクトは Vue、React などの技術で構築されています。既存の技術を無視して Blazor でプロジェクト全体を書き直すのは現実的ではありません。

Vue は現在人気の Web フレームワークであり、簡単に言えばテンプレートエンジンです。「テンプレート」と「バインディング」という2つの特性を利用して、Web ページの MVVM パターン開発を実現します。.NET MAUI フレームワークを使用すると、Vue アプリケーションを Web コンテナに埋め込むことができ、クロスプラットフォームのハイブリッド開発が可能になります。

例えば、私がある医療業界のプロジェクトで、このハイブリッド開発方式を用いてアプリケーションを生成しました。Vue コードをほとんど変更することなく、クロスプラットフォームで動作します:

Vue で開発された Web サイトをお持ちであれば、この記事を参考にして、iPhone、Android、タブレットなどのモバイルデバイスに移植してみてください。

ハイブリッド開発の中核は、Web と .NET の相互運用を構築することです。そのために、Blazor エンジンの以下の機能を利用します。

  • リソースの一元管理
  • JS コードの注入
  • JS から C# コードの呼び出し
  • C# から JS コードの呼び出し

ハイブリッド開発の概念をまだご存じない場合は、前章「[MAUI] 混合開発概念_jevonsflash の専欄-CSDN ブログ」をご参照ください。

全体の作業は、MAUI 部分、Vue 部分、ハイブリッド改造の3つに分かれます。

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 プロジェクトを処理します。

プロジェクトディレクトリに移動し、vue-cli を使用して空の Vue プロジェクトを作成します。

ここでは Vue の好みに合わせてプロジェクトを設定できます。例えば、私は 2.0 プロジェクトを選択し、Typescript と es6 のクラス命名方式などをサポートしました。最終的には 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# の相互運用をテストするために、3つのボタンと関連する関数が必要です。

<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; } }</IJSObjectReference
        ></IJSObjectReference
      ></string
    ></int
  ></string
>```

以下の2つのステートメントは、バンドルで生成された実際のファイル名に対応し、クロスオリジンタグを追加する必要があります。

```csharp
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 を MAUI に移植する方法を紹介する予定です)。Xamarin はデバイス抽象化レイヤーであり、提供される WebView は H5 互換性に優れています。

もちろん、主な理由は迅速な開発にあります。蓄積されたコードこそが貴重であり、コードの変更量を最小限に抑えることが重要です。もし React 技術スタックで Web コードを書いているのであれば、React Native が最適な選択かもしれません。最適な技術は存在せず、自分に最適な技術があるだけです。

コードリポジトリ:

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2023/01/12

Maui Blazor でカメラ機能を実装する方法

Maui Blazor ではインターフェースが WebView でレンダリングされるため、Android のカメラを使用する際に画面コンポーネントにバインドする必要があるため、カメラを取得できません。

続きを読む
同じカテゴリ / 同じタグ 2022/04/26

MAUIでMasa Blazorを使用する

`.NET MAUI` を使用すると、`Android`、`iOS`、`macOS`、`Windows`、Linux(コミュニティサポート)向けのアプリを単一の共有コードベースから開発でき、1つのコードで複数のプラットフォームで実行できます。

続きを読む