BlazorでSPAするぞ!(5) - レイアウト -正式版対応済

※最終更新日: 2020/5/24 正式リリース版に対応修正しました。

という事で、↓↓↓の続きです。

ryuichi111std.hatenablog.com

今回はレイアウト機能について。

1. レイアウト機能とは

これはよくあるやつですね。
ASP.NET Coreでの「_Layout.cshtml」と大体同じことです。
ヘッダやフッター、メニューなど、アプリケーション内の複数のページで共通する画面項目(UI)がある場合に使われます。
すべての画面定義に「ヘッダ、フッター、メニュー」をペタペタとコピペしても画面実装は可能ですが、煩雑ですしメンテナンス性も失われます。
そこで、複数画面で共通して利用するUIを「レイアウト」として定義します。そして、画面毎に異なるコンテンツ部分を個別に実装し、それらを共通レイアウトに差し込む事ができます。

2. 自動生成コードから学ぶ

dotnet new コマンドで自動生成したプロジェクトは、きっちりとレイアウト機能が使われた形になっています。
自動生成コードを見ながらレイアウト機能を理解していきたいと思います。

では、dotnet new blazorwasm コマンドでプロジェクトを生成します。

dotnet new blazorwasm -o layout_blazor

2.1. _Imports.razor / @layout

まず「_Imports.razor」という名前のファイルがBlazorにおいて特殊なファイルとして扱われます。
コンパイラは「
_Imports.razor」という名前のファイルを見つけると、このファイルの記述内容を同一フォルダおよび配下のフォルダに再帰的に適用します。

(1) /_Imports.razor

layout_blazorプロジェクトでは、まず、プロジェクトルートに「__Imports.razor」ファイルがあります。

# /_Imports.razor #

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using layout_blazor
@using layout_blazor.Shared

「@using」宣言がいくつか並んでいます。
ルートの_Imports.razorで定義されているという事で、つまり、このプロジェクトではルートフォルダおよび配下フォルダで定義されるすべての .razor において、これらのusingが有効になります。

(2) DefaultLayout

App.razorを見ると、Routerが定義されていますが、その中で「DefaultLayout」値が設定されています。
以下のように「DefaultLayout="@typeof(MainLayout)」と定義されています。
つまり、このアプリケーション内では「MainLayout」がデフォルトのレイアウトとして適用されます。

# App.razor #

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView
          RouteData="@routeData"
          DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

(3) MainLayout

MainLayoutは、どこで定義されているでしょうか?
これは、Sharedフォルダ配下で定義されています。
App.razorにおいて「MainLayout」と記述できる理由は、/_Imports.razorにおいて「@using layout_blazor.Shared」という宣言が行われていたためです。

以下を見てわかるように、この「Shared/MainLayout.razor」がレイアウト定義を行っているファイルになります。

# Shared/MainLayout.razor #

@inherits LayoutComponentBase

<div class="sidebar">
  <NavMenu />
</div>

<div class="main">
  <div class="top-row px-4">
    <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
  </div>

  <div class="content px-4">
    @Body
  </div>
</div>
【@inherits LayoutComponentBase】

@inheritsディレクティブは、コードビハインドクラスを指定するときにも使用しましたが、ここでは MainLayout の継承元として LayoutComponentBase を指定しています。
LayoutComponentBaseクラスは、Blazorライブラリで「Microsoft.AspNetCore.Components」名前空間に定義されたクラスです。
レイアウトを定義するクラスは「LayoutComponentBase」を継承する必要があります。

【NavMenu】

サイドバーとして「NavMenu」コンポーネントを配置しています。
NavMenu.razorは、同じSharedフォルダに定義されており、以下のような実装になります。
実行したサンプルの 左のサイドバーメニュー そのままの定義ですね。

# Shared/NavMenu.razor #

<div class="top-row pl-4 navbar navbar-dark">
  <a class="navbar-brand" href="">layout_blazor</a>
  <button class="navbar-toggler" @onclick="ToggleNavMenu">
    <span class="navbar-toggler-icon"></span>
  </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
  <ul class="nav flex-column">
    <li class="nav-item px-3">
      <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
        <span class="oi oi-home" aria-hidden="true"></span> Home
      </NavLink>
    </li>
    <li class="nav-item px-3">
      <NavLink class="nav-link" href="counter">
        <span class="oi oi-plus" aria-hidden="true"></span> Counter
      </NavLink>
    </li>
    <li class="nav-item px-3">
      <NavLink class="nav-link" href="fetchdata">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
      </NavLink>
    </li>
  </ul>
</div>

@code {
  private bool collapseNavMenu = true;

  private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

  private void ToggleNavMenu()
  {
    collapseNavMenu = !collapseNavMenu;
  }
}
【@Body】

「@Body」ディレクティブが、各ページ(各コンポーネント)固有のコンテンツが配置される部分になります。
例えば「/」(ルート)にマップされるファイルは Pages/Index.razor になりますが、Shared/MainLayout.razor の @Body 箇所に Pages/Index.razor がレンダリングされます。

f:id:daigo-knowlbo:20190502022002p:plain

3. まとめ

レイアウト機能は比較的シンプルでASP.NET Coreのレイアウトなどを知っていれば、スムーズに理解できそうです。
自動生成されるプロジェクトが、昔に比べて複雑化していますが、これを丁寧にトレースすることで、レイアウト機能の基本は理解できると思います。

次は ryuichi111std.hatenablog.com