筆者は当初 ASP.NET Core API + Blazor Server を使用していたため、Blazor Server を例に説明します。後日 Blazor WebAssembly を研究し終えたら、その知見も追記します。
まず、Component は再利用可能なので、Index.razor に二つの Counter を置き、プロジェクトを起動します(完全なデバッグが不要な場合は Ctrl+F5 を押すと、デバッグなしモードで起動し、起動が速くなります。ファイルを保存するたびに Blazor が検出し、Web ページをリロードすると新しいプログラムが読み込まれます)。ブラウザ上の二つの Counter にはそれぞれ独自の「Click me」ボタンがあり、それぞれクリックすると数字が個別に増加します。これは別々の Component であることを示しています。では、これらの数字はどこで定義されているのでしょうか?
Counter.razor を開くと、一番上に @page ディレクティブがありますが、これについては後述します。続いて HTML といくつかの C# コード、最後に @code ブロックがあります。これが Blazor の不思議なところです。@code は一般的な Web ページで JavaScript が行うこと(変数の定義、メソッドの実装、バックエンドや API へのリクエスト送信など)に相当しますが、Blazor では C# で記述します。ここではプライベート変数 currentCount とメソッド IncrementCount() が定義されています。このメソッドを呼び出すのは「Click me」ボタンで、ボタンをクリックするたびに currentCount が +1 され、その結果が <p> 要素内に表示されます。


currentCount の定義方法とページ表示は、モデルバインディング(model binding)です。つまり、データとページがバインドされている関係です。.NET Framework の View における @model や @Viewbag、Angular の [(ngModel)] も同じ考え方で、データがページに流れ、ページの操作がデータの動作に影響を与えることを実現しています。
別の変数 myClass を定義し、そこに Bootstrap のクラスを代入して、その変数を button の class に配置します。HTML 内で C# のコードを使う場合は必ず @ で始める必要があることを覚えておいてください。そうしないと Blazor がコンパイルできません。ページをリロードすると、ボタンのスタイルが変わっているのがわかります。Blazor が myClass の値 text-primary bg-warning を button の class に挿入してくれています。

次に FetchData.razor を見てみましょう。@using BlazorServer.Data がありますが、これは後で _Import.razor に移動できます。では、@inject WeatherForecastService ForecastService とは何でしょうか?まず @code ブロックを見ると、WeatherForecast[] 型の変数 forecasts が定義され、非同期メソッド OnInitializedAsync 内で ForecastService.GetForecastAsync(DateTime.Now) を呼び出し、その結果を forecasts に代入しています。注意深い人は気づいたでしょうが、一番上の ForecastService と @code ブロック内の ForecastService はまったく同じものです。

GetForecastAsync() メソッドをクリックして F12 を押すと、このメソッドがランダムに生成された 5 つの天気データの配列を返すことがわかります。HTML 内では forecasts が null でないかチェックし、null でなければ <table> を生成し、その中で foreach を使って forecasts の日付、摂氏、華氏、天気状態を一つずつ表示しています。

先ほど述べたように、Blazor は単一の Web ページを持ち、その他の内容はすべて Component で構成されています。イベントが発生するたびに、Server または WebAssembly は該当する Component をブラウザにレンダリングします。では、Blazor はどの Component を表示すべきかをどのように知るのでしょうか?
その答えは @page ディレクティブにあります。このディレクティブは従来のルーティングに相当します。Index.razor の @page は "/" で、これがホームページであることを示しています。Counter.razor や FetchData.razor にも対応する @page ディレクティブがあります。1 つのページに複数の @page ディレクティブを設定することもできますが、先頭は必ずスラッシュで始め、ダブルクォーテーションで囲む必要があります。筆者はかつて異なる Component の @page を enum で一元管理しようと考えましたが、現状 Blazor ではそのような方法はサポートされていません。また、2 つの Component が同じ @page を使用すると、コンパイル時にエラーが発生するため、重複ルートが発生した場合の処理を心配する必要はありません。

では、左側のメニューの Home、Counter、Fetch data の各ページはどこで定義されているのでしょうか?MainLayout.razor を開くと、<NavMenu> 要素があります。さらに NavMenu.razor を開くと、3 つの <NavLink> Component があります。これらの Component は Server によってブラウザが認識できる <a> 要素に変換されるため、DevTools で確認しても <a> 要素しか表示されません。



MainLayout.razor に戻ると、@Body ディレクティブがあります。これは他の Component が配置される場所で、一種のプレースホルダーです。さらに App.razor の中には <Found> と <NotFound> という 2 つの Component があります。名前からわかるように、前者は入力された URL に一致する Component が見つかった場合に使用され、後者は一致する Component が見つからなかった場合に使用されます。どちらも MainLayout を使用していることがわかります。また、異なるページに異なる Layout を適用したい場合は、独自の Layout を定義することもできます。


ここまで来たところで、Blazor Server の動作を復習しましょう。Angular と同様に、階層的に下っていく構造で、管理が容易です。Blazor WebAssembly と Blazor Server の index.html と _Layout.cshtml はほぼ同等であり、appsettings.json ファイルが存在しない点が異なります。通常、プログラムとデータベースの接続に必要な接続文字列はこのファイルに格納されます。このことからも、Blazor WebAssembly は実際には受動的にデータを受け取るだけで、データベースに能動的に接続できないことがわかります。筆者はここで EF Core を参照しようと試みましたが、やはり Blazor WebAssembly からデータベースにアクセスすることはできませんでした。.NET Framework の世界では XML 形式の web.config が使われていましたが、.NET Core では JSON 形式の appsettings.json に変更されています。
参考: ASP NET Core blazor project structure
注:本稿のコードは .NET 6 + Visual Studio 2022 でリファクタリングしています。原文リンクとリファクタリング後のコードを比較して学習することをお勧めします。お読みいただきありがとうございます。原作者をサポートしてください。