7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[.NET 8]MVVMで作るBlazor Web App

Posted at

忙しい人向けの結論

  1. ReactiveProperty.Blazor を使います。
  2. Blazor Web Appテンプレートの Counter ページ向けに ViewModels/CounterViewModel.cs を作ります。
  3. ReactivePropertySlim<int>ReactiveCommandSlim を使って、プロパティと、値をインクリメントするコマンドを作ります。
  4. CounterViewModel をDIして、 Counter.razor にインジェクションします。
  5. Counter ページの @onclickCounterViewModel のコマンドを紐づけます。
  6. おわり

以下で解説していきます。なお、この記事ではMVVMとは何か?という話はしません。BlazorでMVVMをするにはどうするのか?にフォーカスして書いていきます。

ReactiveProperty.Blazor って何?

Blazor向けに作られたReactivePropertyのNuGetパッケージです。Blazorはボタンなどのクリックイベント @onclick を実行すると、UI(DOM)が自動で再描画されるため、嬉しさ半減なのですが、例えば実行する処理が、タイマー実行系の処理であったり、非同期で少し重めの処理であったりする場合は、明示的に StateHasChanged() を呼ぶ必要があるため、このパッケージを使用するメリットが出てきます。

どちらかと言うと、処理に対するメリットよりも、 「MVVMで書くぞ!」 という強い意気込み(制約)がカオスになりやすい .razor ファイル内の @code{ }.razor.cs ファイル自体をあるべき姿に保ってくれるというメリットの方が大きいかもしれません。

詳しいドキュメントはこちらになりますので、興味があればご参照ください。

Counterページ向けにViewModelを作る

おなじみの Blazor Web App プロジェクトテンプレートを使用します。
まずは、クライアント側に ViewModels/CounterViewModel.cs を作成します。
image.png

続いて、以下のコードを参考に CounterViewModel.cs に実装していきます。

CounterViewModel.cs
using Reactive.Bindings;
using Reactive.Bindings.Disposables;
using Reactive.Bindings.Extensions;

namespace BlazorApp1.Client.ViewModels
{
    public class CounterViewModel : IDisposable
    {
        private readonly CompositeDisposable _disposable = [];

        // ボタンを押されたときの動作を定義する
        public ReactiveCommandSlim IncrementCommand { get; }
        // 実際のカウンターの値を保持するプロパティ
        public ReactivePropertySlim<int> Count { get; }

        public CounterViewModel()
        {
            // 初期化
            Count = new ReactivePropertySlim<int>().AddTo(_disposable);
            IncrementCommand = new ReactiveCommandSlim();

            // コマンドが実行された時にCountの値をインクリメントする
            IncrementCommand.Subscribe(_ => Count.Value++).AddTo(_disposable);
        }

        // Viewの@onclickに紐付ける用
        public void ExecuteIncrementCommand()
        {
            IncrementCommand.Execute();
        }

        public void Dispose()
        {
            _disposable.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}

ReactiveCommandSlim IncrementCommand

MVVMのコマンドを定義するプロパティです。コンストラクタ内の以下のコードで具体的な処理を定義しています。

// コマンドが実行された時にCountの値をインクリメントする
IncrementCommand.Subscribe(_ => Count.Value++).AddTo(_disposable);

.Subscribe() の引数にLINQで処理を渡してあげることで、 IncrementCommand.Execute() が呼ばれた時にその処理が実行されるようになります。今回の例ですと、 Count.Value++ が実行され、 Count の値が1増えます。

ReactivePropertySlim<int> Count

値が変更されてことを自動的にView側へ通知してくれるプロパティです。 IncrementCommand によって Count の値が変更された時にViewへ通知が飛び、UIが更新され最新の値が画面に表示されます。

CounterViewModel をDIする

クライアント側、サーバー側ともに編集が必要です。
まずはクライアント側の Program.cs を以下のように修正します。

+ using BlazorApp1.Client.ViewModels;
  using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

  var builder = WebAssemblyHostBuilder.CreateDefault(args);
+ builder.Services.AddSingleton<CounterViewModel>();

  await builder.Build().RunAsync();

次にサーバ側の Program.cs も同様に修正します。

  using BlazorApp1.Client.Pages;
+ using BlazorApp1.Client.ViewModels;
  using BlazorApp1.Components;

  namespace BlazorApp1
  {
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddRazorComponents()
                .AddInteractiveServerComponents()
                .AddInteractiveWebAssemblyComponents();

+           builder.Services.AddScoped<CounterViewModel>();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseWebAssemblyDebugging();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles();
            app.UseAntiforgery();

            app.MapRazorComponents<App>()
                .AddInteractiveServerRenderMode()
                .AddInteractiveWebAssemblyRenderMode()
                .AddAdditionalAssemblies(typeof(Counter).Assembly);

            app.Run();
        }
    }
  }

これで、 Countr.razorCounterViewModel をインジェクションできるようになりました。

Counter.razor でViewModelを使う

それでは、 Counter.razor を以下のように修正します。

  @page "/counter"
+ @using BlazorApp1.Client.ViewModels
  @rendermode InteractiveAuto
+ @inject CounterViewModel ViewModel

  <PageTitle>Counter</PageTitle>

  <h1>Counter</h1>

- <p role="status">Current count: @currentCount</p>
+ <p role="status">Current count: @ViewModel.Count.Value</p>

- <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
+ <button class="btn btn-primary" @onclick="ViewModel.ExecuteIncrementCommand">Click me</button>

- @code {
-     private int currentCount = 0;

-     private void IncrementCount()
-     {
-         currentCount++;
-     }
- }

まず、ソース上部で ViewModelをインジェクションしています。 @inject TypeName VariableName とすることで、フレームワーク側で自動的にViewModelを割り当ててくれます。

@inject CounterViewModel ViewModel

今回のスコープですと、ページ遷移してもViewModelの状態は保たれているため、Countの値もブラウザを閉じるまで保持し続けます。


続いてUI側を見ていきます。

<p role="status">Current count: @ViewModel.Count.Value</p>

<button class="btn btn-primary" @onclick="ViewModel.ExecuteIncrementCommand">Click me</button>

ReactivePropertyで定義したプロパティの値を参照する場合は、 .Value を使用して参照する必要があるため注意が必要です。今回ですと、 @ViewModel.Count.Value の部分です。
そして、ViewModel.ExecuteIncrementCommand@onclick に紐付けることで、ボタンがクリックされる度に IncrementCommand が実行され、 Count の値がインクリメントしていきます。

おわりに

今回の例示では、ReactivePropertyの良さが活かしきれませんでしたが、例えばPull型で定期的にサーバに問い合わせて情報を取得、そして反映のようなクライアントをBlazorで実装する場合には恩恵を受けることでしょう。

また、既に INotifyPropertyChanged インターフェースを使用してオレオレViewModelを作っている場合は、簡単に乗り換えることができるため、ぜひ使ってみてはいかがでしょうか?

最後になりますが、Blazorにはかっちりはまるソフトウェアアーキテクチャが無い(もしかしたら知らないだけかも・・・)です。なので、こうしてMVVMの型にはめて開発を進めるのも、クリーンなソリューションを保つための1つの手かもしれません。

もし、Blazorでの開発でいい方法があるよ!と手を挙げられる方がおりましたら、ぜひ Blazor Advent Calendar に寄稿をお待ちしております!!(それか、コソっと @nr_ck に教えてください。 )


この記事で伝えたいことは以上です。
これを参考にBlazorでMVVMやるぜ!って言う人が増えてくれたら筆者は幸いです。

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?