🌊

.NET 8 での Blazor を整理整頓して理解しよう

2024/01/05に公開

はじめに

Blazor が .NET Core 3.1 の頃に出てきて、Single Page Application を C# + HTML/CSS + ちょっとのJavaScript で作れる技術という立ち位置で .NET 5 頃までいました。
そこから Blazor Hybrid や .NET 8 で Blazor United という俗称で呼ばれる新しい Blazor の形が出てきました。

正直、のほほーんと外から Blazor を眺めているだけだと何が何だかわからない状態だと思うので、.NET 8 時点での Blazor の形を整理してみようと思います。今回は基本的に整理するだけで、実際にコードとしてどのように実装するのかといった所までは踏み込みません。個々の詳細についてはドキュメントなどを参照してください。
関連するドキュメントや私の記事などは、関係する説明の途中にリンクを貼っています。

この記事で説明している Blazor は、以下の図のものになります。

Blazor の種類

では Blazor の種類について説明します。Blazor を整理する観点として、まず Web アプリを作りたいのか、ネイティブアプリを作りたいのかで分岐します。1 つずつ見ていきましょう。

ネイティブアプリ用の Blazor

ネイティブアプリを作る場合は Blazor Hybrid という形になります。
Blazor Hybrid の実態は WebView コントロールで Blazor のアプリをホストして画面のレンダリングやイベントのハンドリングを行うものになります。
特徴としてはボタンのクリックイベントなどは、WebView コントロールの中で処理されるのではなく、ホストされているネイティブアプリのプロセス内で処理されるので、そのプラットフォームのネイティブ機能などにアクセスすることができます。

Blazor Hybrid 用の WebView コントロールは BlazorWebView という名前で MAUI には組み込まれています。WPF や Windows Forms(以降 WinForms) にも BlazorWebView があります。BlazorWebView を通常は画面いっぱいに置いて使いますが、場合によっては画面の一部に BlazorWebView を置いて使うことも出来ます。

以下の図は、Blazor Hybrid での動作イメージになります。

MAUI を使う場合には、ターゲットとするプラットフォーム全てで動くように気を付けないといけないので、そのぶん難易度が高くなります。WPF や WinForms を使う場合は Windows のみをターゲットにすればよいので Windows のみをターゲットにして Blazor Hybrid を使う場合には WPF や WinForms の方が良いでしょう。個人的には .NET 8 の WinForms のデザイナーは、まだ正式版がリリースされていないので、特別な事情が無い限りは WPF を使う方が良いと思います。

また、あまり使うことは無いと思いますが JavaScript との相互運用機能を使うことで JavaScript を通じてブラウザーの機能を使うことも出来ます。

ということでネイティブアプリ用の Blazor のまとめは以下のようになります。

  • Blazor Hybrid
    • MAUI
      • Android, iOS, macOS, Windows で動かせるが、クロスプラットフォームなので全てのプラットフォームをサポートするようにコードを書かないといけない。
    • WPF
      • Windows のみをターゲットにする場合にはお勧め。
    • WinForms
      • .NET 8 向けの WinForms のデザイナーが正式版になっていないので、個人的には WPF の方が良いと思う。

https://learn.microsoft.com/ja-jp/aspnet/core/blazor/hybrid/?view=aspnetcore-8.0

Web アプリ用の Blazor

Web アプリ用の Blazor は完全な静的コンテンツとして配布するものと、サーバーサイドで動作するプログラムが必要なものに分かれます。まずは静的コンテンツとして配布するものの方が説明が簡単なので、そちらから解説したいと思います。

完全な静的コンテンツとして配布する Blazor

完全な静的コンテンツとして配布が出来る Blazor のアプリは Blazor WebAssembly(WASM) になります。このアプリを配布するには単純な静的コンテンツを配布できる Web サーバーがあればよく一度ダウンロードされてしまえば、その後はサーバーとの通信は必要ありません。ただし、WebAssembly 上で動作するための .NET ランタイムやアプリのアセンブリなどを初回アクセス時にダウンロードする必要があるため、どうしても初回起動は遅くなってしまうという問題点はあります。ダウンロードの進捗を示す UI を表示するための仕組みがあるので、それを使うことでユーザーの体感速度を改善することは出来ます。

この形式の特徴は、ブラウザ内で動作するため基本的にはブラウザ上で出来ることしか出来ません。例えば RDBMS などへの直接のアクセスは出来ません。RDBMS などへのアクセスをするには、別途 Web API などを経由して行う必要があります。また、JavaScript との相互運用機能などを使うことでブラウザーの localStorage や sessionStorage へのアクセスを行うことも出来ます。

ということで Blazor WebAssembly を簡単にまとめると以下のようになります。

  • 完全な静的コンテンツとして配布する Blazor
    • Blazor WebAssembly
      • ブラウザー上で動作する。
      • ブラウザー上で出来ることしか出来ない。
      • RDBMS などへの直接のアクセスは出来ない。
        • Web API などを経由して RDBMS などへのアクセスを行うことは出来る。
      • JavaScript との相互運用機能を使って JavaScript との連携も可能

サーバーサイドが必要な Blazor

サーバーサイドが必要な Blazor は、.NET 8 からは Blazor Web App テンプレートを通じて作成することができます。この形の Blazor が単に Blazor と記載したときに指しているものです。.NET 8 の開発が開始した直後に Blazor United として発表されたものになります。

このプロジェクト テンプレートで作成を行うと 1 つのアプリ内で以下のレンダリング モードをページごとやコンポーネントごとに使い分けることが出来るようになります。

  1. 静的サーバー サイド レンダリング
  2. 対話操作サーバー (InteractiveServer)
  3. 対話操作 WebAssembly (InteractiveWebAssembly)
  4. 対話操作自動 (InteractiveAuto)

1 の静的サーバー サイド レンダリングはユーザーとのリッチな対話操作が不要なページのためのレンダリング モードです。一般的な Web アプリケーションと同じように HTTP で普通にページを表示するタイプの Web アプリケーションに最も近い動きをします。

2 と 3 はユーザーとの対話操作が必要なページのためのレンダリング モードです。2 と 3 の違いは対話操作を受けて処理をするプログラムが何処で実行されるかになります。対話操作サーバーでは、ボタンをクリックしたりテキストを入力した際に何か処理をする場合はサーバーサイドで処理を行います。対話操作 WebAssembly では、ボタンをクリックしたりテキストを入力した際に何か処理をする場合はブラウザー上で処理を行います。
2 の対話操作サーバーの方式の場合、その特性上ブラウザーとサーバー間に WebSocket (厳密には SignalR の接続) が必要になります。対話操作 WebAssembly の場合は WebAssembly 上で動作する .NET ランタイムの上で動くプログラムで処理されます。そのため、初回のページアクセス時に WebAssembly のダウンロードが行われるため実際にユーザーがページを操作できるようになるまでタイムラグがあります。

図にまとめると以下のようになります。

静的サーバー サイド レンダリング固有の機能

静的サーバー サイド レンダリングは、以下の固有の機能を持っています。

  • ストリーミング レンダリング
  • 拡張ページ ナビゲーション
  • 拡張フォーム処理

ストリーミング レンダリング

ストリーミング レンダリングは、ページのレンダリングのために必要なデータを取得するために外部の DB や Web API を叩く必要があるケースで、データを取得前の状態で一度レスポンスを返してページを表示して、その後にデータ取得などをおこない、データ取得が完了したタイミングで再度完全なページを差分レンダリングするといった機能になります。

こうすることで、静的サーバー サイド レンダリングでもユーザーに対して即座にレスポンスを返して、その後にデータを表示するといった一般的な SPA で行うような動きを実現することが出来ます。

拡張ページ ナビゲーション・拡張フォーム処理

拡張ページ ナビゲーションは a タグのリンクをクリックしたときの画面遷移を Blazor がインターセプトしてブラウザーへのリクエストを fetch 関数を使って行い、画面の差分更新を行うようにする機能です。これにより、一般的な Web アプリケーションのように a タグをクリックした際に画面全体をリフレッシュするのではなく、必要な部分の差分更新が行えるので、ユーザーにとってはより快適な動きになります。

拡張フォーム処理は、form のサブミット処理を Blazor がインターセプトして画面の差分更新を行うようにする機能です。拡張フォーム処理はデフォルトでは無効になっていて、必要に応じて開発者側でフォーム単位でオンにする機能になります。

これらの動作を図にまとめると以下のようになります。

これらの機能により、静的サーバー サイド レンダリングを使用した場合でも SPA のように見た目の差分更新が行われるためユーザーにとって普通の Web アプリケーションよりも快適に動作するように実装することが出来ます。

対話操作サーバー・対話操作 WebAssembly

これを選択すると WebSocket(SignalR) の接続を行いサーバーサイドで処理を行うか、ブラウザ上での動作で完結させるかのどちらかの方式でユーザーとの対話操作を行うページやコンポーネントを実装可能です。Blazor Web App では、以下の図のようにアプリケーション全体で対話操作を有効にするか、ページ単位やコンポーネント単位で対話操作を有効にするかを選択することが出来ます。

ページ単位・コンポーネント単位で対話操作を有効化する場合のデフォルトの動作は静的サーバー サイド レンダリングになります。この時、対話操作サーバーを選択しているページから離れたタイミングで WebSocket(SignalR) の接続は切断されます。WebSocket(SignalR) の接続をはりっぱなしにしているとスケーラビリティの観点で望ましくないことがあるので、この動作は個人的にはとてもありがたいです。

対話操作自動

対話操作サーバーと対話操作 WebAssembly を自動で切り替える機能になります。この 2 つの対話操作のモードは以下のトレードオフがあります。

  • 対話操作サーバー
    • メリット: 初回アクセスが早い
    • デメリット: 接続している間、接続先のサーバーのリソースを一定量、使用し続けるためスケールしない
  • 対話操作 WebAssembly
    • メリット: 完全にブラウザ上で動作するためサーバーには負荷をかけない
    • デメリット: 初回アクセス時に WebAssembly のダウンロードが必要なため初回アクセスが遅い

対話操作自動を使うことで、初回アクセス時は対話操作サーバーで動作を行い裏で WebAssembly のダウンロードが行われます。2 回目以降にページにアクセスを行うと WebAssembly のダウンロードは既に行われている状態なので即座に WebAssembly 上で Blazor のアプリが起動して動作可能になるため対話操作 WebAssembly として動作するようになります。こうすることで両方のデメリットを補う形で動作するようになります。

対話操作自動を選択したときのデメリットは、同じページでもサーバーと WebAssembly の別の動作モデルで動作するように実装しないといけないという点です。具体的にはページ内では DB や Web API を呼ぶような処理は行わずに DI で注入されたサービスを使って処理を行うように実装する必要があります。サーバーで動作しているときは、サーバー用の実装を渡して、WebAssembly で動作しているときは WebAssembly 用の実装を渡すようにします。
そのため最悪のケースでは実装コストが 2 倍になる可能性があります。

ページ単位・コンポーネント単位で対話操作を有効にした時の動作

ページ単位・コンポーネント単位で対話操作を有効化した際には以下の注意が必要です。

  • ページのレイアウトで定義された部分は静的サーバー サイド レンダリングで行われる。
  • ページ内部だけ指定したレンダリングモードで動作する。
  • サーバーで動いているページ内で WebAssembly モードのコンポーネントは置けない。(逆もしかり)
  • Blazor 静的 SSR と対話操作サーバーと対話操作 WebAssembly は、それぞれ別の動作モードなので連携はあまり密にできない。
    • DB や localStorage などを経由したデータ受け渡しは可能
    • 別モード間でのコンポーネントのパラメーター受け渡しは JSON にシリアライズ可能なもののみ可能
    • ErrorBoundary は同じモード内で発生したエラーしかハンドリング出来ない

レイアウトで定義された部分は静的 SSR で行われページ内部だけ指定したレンダリングモードで動作するというのは、デフォルトのプロジェクトテンプレートで説明すると以下の図のようになります。

そのため、ページの外側のレイアウトで定義されている部分とページのコンテンツ部分で動作モードが異なります。基本的に、対話操作を有効にしたページでは上記の制限事項があるということを意識しておく必要があります。

https://zenn.dev/microsoft/articles/aspnetcore-blazor-dotnet8-errorhandling

対話操作モードのプリレンダリング

対話操作サーバーと対話操作 WebAssembly については追加で、もう 1 つ注意する点があります。それはデフォルトでは、サーバーサイドで必ずプリレンダリングが行われるということです。これによって SEO に強くなったり WebAssembly の場合には初回表示が格段に速くなる(とはいえ実際に対話操作が動くのは WebAssembly のダウンロードが終わって起動してからです)といったメリットがあります。

この動作は WebAssembly の場合は特に気を付けないといけません。
もし Web ブラウザー上でしか動かないようなコードを書いているとプリレンダリングはサーバーサイドで実行されるためにページのレンダリング時にエラーになってしまいます。
例えばページのデータを表示するために Web API を叩いているといった場合は、サーバーサイドでプリレンダリングを行う際にも正しく、目的の Web API を叩けるように各種設定が行われた HttpClient が使えるようにする必要があります。または、該当部分の処理をインターフェースとして定義してサーバー用の実装と WebAssembly 用の実装の両方を用意して DI コンテナに登録しておく必要があります。

プリレンダリングはオフにすることも出来ます。

https://learn.microsoft.com/ja-jp/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0

また、プリレンダリングと WebAssembly 上での再レンダリング時に同じデータに 2 回アクセスすると無駄なのでデータを持ちまわる仕組みも提供されています。

https://learn.microsoft.com/ja-jp/aspnet/core/blazor/components/prerender?view=aspnetcore-8.0

関連するドキュメント

https://learn.microsoft.com/ja-jp/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0

https://zenn.dev/microsoft/articles/aspnetcore-blazor-dotnet8-overview

ここまでのまとめ

Blazor について色々書いてきました。全体として再掲になりますが、ざっくりと以下の図のような感じになります。

この図の末端部分のどれを選ぶかが、自分達が作りたいアプリケーションによって変わってきます。そして一般的に Blazor という単語で語られるものは一番したの Blazor Web App の部分を指していることが多いです。文脈などに応じてどれを指しているのかをきちんと把握するようにしましょう。

コードの共通化

こんなに Blazor が派生していると、全ての Blazor アプリでコードを共通化できるのではないか?と考えると思います。この答えは「やろうと思えば可能だけど大変」になります。一度でもクロスプラットフォーム アプリケーション開発をしたことのある人であれば共感していただけるかもしれません。書いたコードが全てのプラットフォームで動作するかどうか、動作しない場合はプラットフォームごとの差異を吸収するようにインターフェースを定義して、プラットフォームごとの実装を用意して、実行プラットフォームに応じて実装を差し替えるといったことを行う必要があります。
プラットフォームごとの実装を差し込む部分は最近では DI コンテナにお任せ出来るので独自の仕組みは必要ありませんが、個々のプラットフォームごとの実装が必要な部分を見極めて実装していくということは必要になります。また、結構大変な作業になります。

技術的には Visual Studio 2022 にある Razor クラス ライブラリというプロジェクトを使うことで、共通化したい Razor コンポーネント (Blazor のページやコンポーネントを定義するもの) や共通ロジックをクラスライブラリとして作成することが出来ます。
これを Blazor Hybrid のプロジェクトや Blazor Web App のプロジェクトや Blazor WebAssembly のプロジェクトから参照することでコードの共通化を行うことが出来ます。

ただし、先ほどのプラットフォームごとの実装が必要な部分などは別途実装が必要です。

主な差異としては以下のようなものがあります。

プラットフォーム 対話操作 JavaScript 相互運用 ネイティブ機能呼び出し DB サーバーへの直アクセス Web API 呼び出し 備考
Blazor Hybrid (MAUI) 〇(対応 OS ごとに異なる API になることもある) △(Windows と macOS は可) MAUI 自体がクロスプラットフォームのフレームワークなので各 OS ごとで実装が異なる部分がある。
Blazor Hybrid (WPF/WinForms)
Blazor WebAssembly × × WebAssembly はブラウザー上で動作するため、ブラウザーの機能を使うことは出来るが、ブラウザーの外の機能にはアクセスできない。
Blazor Web App の静的 SSR × × × JavaScript の使用も可能だが他とは大きく異なる。
Blazor Web App の対話操作WASM × × Web API 呼び出しは可能だが、CORS の設定や呼び出す API のベースパスが異なる可能性がある。
Blazor Web App の対話操作サーバー × JavaScript との対話操作が可能になるのは初回レンダリング完了直後からになるため注意。Blazor WASM は初回レンダリング完了前でも利用可能。

Blazor 静的 SSR の場合の JavaScript の使用方法については以下を参照してください。

https://learn.microsoft.com/ja-jp/aspnet/core/blazor/javascript-interoperability/static-server-rendering?view=aspnetcore-8.0

https://zenn.dev/microsoft/articles/aspnetcore-blazor-dotnet8-jsinterop

まとめ

現状の Blazor をなるべく整理して実際に使う場合の注意点などをまとめてみました。
.NET 8 で Blazor は大きく進化をしたのですが、そのぶんちょっと複雑になってしまいました。一度理解してしまうと、そういうものだと納得できるのですが、知らずに使っているとハマってしまう部分が結構あると思います。

この記事が .NET 8 で Blazor を使う人の一助になれば幸いです。

それでは、楽しい Blazor ライフを!!

Microsoft (有志)

Discussion