BlazorでSPAするぞ!(5) - レイアウト

※ 2019/6/15 .NET Core 3.0 Preview 6 に対応した記述に修正しました。

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

ryuichi111std.hatenablog.com

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

1. レイアウト機能とは

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

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

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

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

dotnet new blazor -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 Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Layouts
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.JSInterop
@using layout_blazor
@using layout_blazor.Shared

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

(2) Pages/_Imports.razor

次にPagesフォルダ配下にも_Imports.razorファイルがあります。

# Pages/_Imports.razor #

@layout MainLayout

1行のみ「@layout」ディレクティブが記述されています。
@layoutは重要でレイアウトファイル(クラス)の指定になります。
つまり、Pagesおよびその配下のフォルダで定義された .razor ファイルには MainLayout で定義されたレイアウトが適用されることを意味します。

(3) MainLayout

MainLayoutは、どこで定義されているでしょうか?
Pagesフォルダ配下には存在しません。Sharedフォルダ配下で定義されています。
Pages/_Imports.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.Layouts」名前空間に定義されたクラスです。
レイアウトを定義するクラスは「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 {
    bool collapseNavMenu = true;

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

    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