アイキャッチ画像

.Net MAUIでXAMLとBlazorをViewModelで連携する方法について

.Net MAUIでXAMLとBlazorをViewModelで連携する方法を紹介していきます。

連携が行えるようになると、XAML側のEntryのテキスト変更をBlazorへ反映できます。

紹介環境

当記事は以下の環境で作成しています。

開発環境

  • Visual Studio 2022
  • .Net 9

対応方法

ファイル構造は以下で作成しています。良ければ参考にしてください。

ファイル構造

手順1:ViewModelを準備します

今回利用するViewModelは、以下のようになります。

using System.ComponentModel;
using System.Runtime.CompilerServices;

// 名前空間はプロジェクトに合わせてください。
namespace SampleApp.ViewModels;

public class MainViewModel : INotifyPropertyChanged
{
    private string _text = "";

    public string Text
    {
        get => _text;
        set => OnPropertyChanged(ref _text, value);
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    public void OnPropertyChanged<T>(ref T refValue, T value, [CallerMemberName] string name = "")
    {
        refValue = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

手順2:XAML側の画面を準備します

先ほど準備したViewModelをXAML側の画面に利用します。ViewModelはRootComponentを利用してBlazor側に渡します。

今回は以下のようになります。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SampleApp"
             xmlns:components="clr-namespace:SampleApp.Components"
             x:Class="SampleApp.MainPage">

    <Grid RowDefinitions="auto,*">
        <Entry Grid.Row="0" Text="{Binding Text}" />
        <BlazorWebView Grid.Row="1" HostPage="wwwroot/index.html">
            <BlazorWebView.RootComponents>
                <RootComponent
                    x:Name="rootComponent"
                    Selector="#app"
                    ComponentType="{x:Type components:Routes}" />
            </BlazorWebView.RootComponents>
        </BlazorWebView>
    </Grid>
</ContentPage>
using SampleApp.Components;
using SampleApp.ViewModels;

// 名前空間はプロジェクトに合わせてください。
namespace SampleApp;

public partial class MainPage : ContentPage
{
    private MainViewModel _viewModel = new();

    public MainPage()
    {
        InitializeComponent();
        this.BindingContext = _viewModel;

        rootComponent.Parameters =
             new Dictionary<string, object?>
             {
                 { nameof(Routes.ViewModel), _viewModel }
             };
    }
}

手順3:Blazor側の画面を準備します

Blazor側の画面では、ルーターとページを用意していきます。

ルーターでViewModelを受け止めるには、Parameter属性を利用します。

今回は以下のようになります。

@using SampleApp.ViewModels

<Router AppAssembly="@typeof(MauiProgram).Assembly">
    <Found Context="routeData">
        <CascadingValue Value="ViewModel">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        </CascadingValue>
    </Found>
</Router>

@code {
    [Parameter]
    public MainViewModel? ViewModel { get; set; }
}

ページでViewModelを受け止めるには、CascadingParameter属性を利用します。XAML側のViewModelの変更を受け付けるためにStateHasChanged()を利用します。

今回は以下のようになります。

@page "/"
@using SampleApp.ViewModels
@using System.ComponentModel

<div>
    @ViewModel?.Text
</div>

@code {
    [CascadingParameter]
    protected MainViewModel? ViewModel { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();

        if (ViewModel is not null)
        {
            ViewModel.PropertyChanged += OnPropertyChanged;
        }
    }

    private void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
    {
        StateHasChanged();
    }
}

動作イメージ

以下のようにXAMLとBlazorが連携できるようになります。

動作イメージ

終わりに

XAMLとBlazorの連携が上手くいかず、いろいろ試して今回の方法にたどり着くことができました。StateHasChanged()を利用すればいいことに、なかなか気づけず時間がかかりました。この記事や役に立つと幸いです。