ASP.NET Core Blazor レイアウト

注意

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

この記事では、Blazor アプリの再利用可能レイアウト コンポーネントの作成方法について説明します。

メニュー、著作権メッセージ、会社のロゴなどの一部のアプリ要素は、通常、アプリの全体的なプレゼンテーションの一部です。 これらの要素のマークアップのコピーをアプリのすべてのコンポーネントに配置するのは、効率的ではありません。 これらの要素のいずれかが更新されるたびに、その要素が使用されているすべてのコンポーネントを更新する必要があります。 この方法は維持するのにコストがかかり、更新が行われなかった場合にコンテンツの一貫性が失われるおそれがあります。 "レイアウト" を使用することで、これらの問題が解決されます。

Blazor レイアウトとは、それを参照するコンポーネントとマークアップを共有する Razor コンポーネントのことです。 レイアウトでは、データ バインディング依存関係の挿入、およびコンポーネントのその他の機能を使用できます。

レイアウト コンポーネント

レイアウト コンポーネントを作成する

レイアウト コンポーネントを作成するには:

  • Razor テンプレートまたは C# コードによって定義された Razor コンポーネントを作成します。 Razor テンプレートが基になっているレイアウト コンポーネントでは、通常の Razor コンポーネントと同じように .razor ファイル拡張子が使用されます。 レイアウト コンポーネントはアプリのコンポーネント間で共有されるため、通常はアプリの共有またはレイアウト フォルダーに配置されます。 ただし、レイアウトは、それを使用するコンポーネントにアクセスできる任意の場所に配置できます。 たとえば、それを使用するコンポーネントと同じフォルダーに、レイアウトを配置できます。
  • コンポーネントを LayoutComponentBase から継承します。 LayoutComponentBase によって、レイアウト内にレンダリングされるコンテンツの Body プロパティ (RenderFragment 型) が定義されています。
  • Razor 構文 @Body を使用して、コンテンツがレンダリングされるレイアウト マークアップ内の場所を指定します。

注意

RenderFragment の詳細については、ASP.NET Core Razor コンポーネントに関する記事を参照してください。

次の DoctorWhoLayout コンポーネントには、レイアウト コンポーネント Razor テンプレートが示されています。 レイアウトにより LayoutComponentBase が継承されて、ナビゲーション バー (<nav>...</nav>) とフッター (<footer>...</footer>) の間に @Body が設定されます。

DoctorWhoLayout.razor:

@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}

MainLayout コンポーネント

Blazor プロジェクト テンプレートから作成されたアプリでは、MainLayout コンポーネントがアプリの既定のレイアウトです。 Blazor のレイアウトでは Flexbox layout model (MDN documentation) (W3C 仕様) を採用しています。

MainLayout.razor:

@inherits LayoutComponentBase

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

    <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>
    </main>
</div>
@inherits LayoutComponentBase

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

    <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>
    </main>
</div>
@inherits LayoutComponentBase

<div class="page">
    <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>
</div>
@inherits LayoutComponentBase

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

<div class="main">
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

Blazor の CSS 分離機能により、分離された CSS スタイルが MainLayout コンポーネントに適用されます。 慣例により、スタイルは同じ名前 MainLayout.razor.css の付随するスタイルシートによって提供されます。 スタイルシートの ASP.NET Core フレームワークの実装を、ASP.NET Core 参照ソース (dotnet/aspnetcore GitHub リポジトリ) での検査に使用できます。

Note

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

Blazor の CSS 分離機能により、分離された CSS スタイルが MainLayout コンポーネントに適用されます。 慣例により、スタイルは同じ名前 MainLayout.razor.css の付随するスタイルシートによって提供されます。 スタイルシートの ASP.NET Core フレームワークの実装を、ASP.NET Core 参照ソース (dotnet/aspnetcore GitHub リポジトリ) での検査に使用できます。

注意

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

レイアウトを適用する

コンポーネントにレイアウトを適用する

@page ディレクティブが使用されているルーティング可能な Razor コンポーネントにレイアウトを適用するには、@layoutRazor ディレクティブを使用します。 コンパイラにより、@layoutLayoutAttribute に変換され、その属性がコンポーネント クラスに適用されます。

次の Episodes コンポーネントの内容が、@Body の位置にある DoctorWhoLayout に挿入されます。

Episodes.razor:

@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>
@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>
@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>
@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>

レンダリングされた次の HTML マークアップが、前の DoctorWhoLayout および Episodes コンポーネントによって生成されます。 関連する 2 つのコンポーネントによって提供されるコンテンツに注目するため、余分なマークアップは示されていません。

  • ヘッダー (<header>...</header>) の Doctor Who™ Episode Database という見出し (<h1>...</h1>)、ナビゲーション バー (<nav>...</nav>)、フッター (<footer>...</footer>) の商標情報要素 (<div>...</div>) は、DoctorWhoLayout コンポーネントによって生成されたものです。
  • Episodes という見出し (<h2>...</h2>) とエピソードの一覧 (<ul>...</ul>) は、Episodes コンポーネントによって生成されたものです。
<body>
    <div id="app">
        <header>
            <h1>Doctor Who&trade; Episode Database</h1>
        </header>

        <nav>
            <a href="main-list">Main Episode List</a>
            <a href="search">Search</a>
            <a href="new">Add Episode</a>
        </nav>

        <h2>Episodes</h2>

        <ul>
            <li>...</li>
            <li>...</li>
            <li>...</li>
        </ul>

        <footer>
            Doctor Who is a registered trademark of the BBC. 
            https://www.doctorwho.tv/
        </footer>
    </div>
</body>

コンポーネントでレイアウトを直接指定すると、"既定のレイアウト" がオーバーライドされます。

レイアウトをコンポーネントのフォルダーに適用する

アプリのすべてのフォルダーには、必要に応じて、_Imports.razor という名前のテンプレート ファイルを格納できます。 コンパイラにより、インポート ファイルに指定されたディレクティブが、同じフォルダー内とそのすべてのサブフォルダー内で再帰的にすべての Razor テンプレートに含まれます。 そのため、@layout DoctorWhoLayout が含まれる _Imports.razor ファイルにより、フォルダー内のすべてのコンポーネントで DoctorWhoLayout コンポーネントが確実に使用されます。 フォルダーとサブフォルダー内のすべての Razor コンポーネント (.razor) に、@layout DoctorWhoLayout を繰り返し追加する必要はありません。

_Imports.razor:

@layout DoctorWhoLayout
...

_Imports.razor ファイルは、Razor ビューおよびページに対する _ViewImports.cshtml ファイルに似ていますが、Razor コンポーネント ファイルに限定して適用されます。

_Imports.razor でレイアウトを指定すると、ルーターの既定のアプリ レイアウトとして指定されているレイアウトがオーバーライドされます。これについては、次のセクションで説明します。

警告

Razor@layout ディレクティブをルート _Imports.razor ファイルに追加しないでください。レイアウトが無限ループになります。 既定のアプリ レイアウトを制御するには、Router コンポーネントでレイアウトを指定します。 詳細については、次の「アプリに既定のレイアウトを適用する」セクションを参照してください。

Note

@layoutRazor ディレクティブによってレイアウトが適用されるのは、@page ディレクティブが使用されているルーティング可能な Razor コンポーネントのみです。

アプリに既定のレイアウトを適用する

App コンポーネントの Router コンポーネントで、既定のアプリ レイアウトを指定します。 Blazor プロジェクト テンプレートに基づくアプリからの次の例では、既定のレイアウトが MainLayout コンポーネントに設定されています。

App.razor:

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

注意

ASP.NET Core 5.0.1 のリリースと、その他の 5.x リリースでは、Router コンポーネントに @trueに設定された PreferExactMatches パラメーターが含まれています。 詳細については、「ASP.NET Core 3.1 から 5.0 への移行」を参照してください。

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

Router コンポーネントについて詳しくは、「ASP.NET Core の Blazor ルーティングとナビゲーション」をご覧ください。

Router コンポーネントで既定のレイアウトとしてレイアウトを指定することは、この記事のこれまでのセクションで説明したように、コンポーネントごとまたはフォルダーごとにレイアウトをオーバーライドできるため、便利な方法です。 レイアウトを使用する最も一般的で柔軟な方法であるため、Router コンポーネントを使用してアプリの既定のレイアウトを設定することをお勧めします。

任意のコンテンツにレイアウトを適用する (LayoutView コンポーネント)

任意の Razor テンプレート コンテンツにレイアウトを設定するには、LayoutView コンポーネントでレイアウトを指定します。 LayoutView は、任意の Razor コンポーネントで使用できます。 次の例では、MainLayout コンポーネントの NotFound テンプレート (<NotFound>...</NotFound>) に ErrorLayout という名前のレイアウト コンポーネントを設定しています。

Note

次の例は Blazor WebAssembly アプリ専用です。Blazor Web Apps では NotFound テンプレート (<NotFound>...</NotFound>) を使用しないためです。 ただし、フレームワークの破壊的変更を回避するため、下位互換性の目的でテンプレートがサポートされています。 Blazor Web Apps では通常、ブラウザーの組み込みの 404 UI を表示するか、ASP.NET Core のミドルウェア経由で ASP.NET Core サーバーからカスタム 404 ページを返すことによって、不適切な URL 要求を処理します (例: UseStatusCodePagesWithRedirects / API ドキュメント)。

App.razor:

<Router ...>
    <Found ...>
        ...
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(ErrorLayout)">
            <h1>Page not found</h1>
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

注意

ASP.NET Core 5.0.1 のリリースと、その他の 5.x リリースでは、Router コンポーネントに @trueに設定された PreferExactMatches パラメーターが含まれています。 詳細については、「ASP.NET Core 3.1 から 5.0 への移行」を参照してください。

入れ子になったレイアウト

コンポーネントであるレイアウトを参照し、そこからさらに別のレイアウトを参照することができます。 たとえば、複数レベルのメニュー構造を作成するために、入れ子になったレイアウトを使用します。

次の例に、入れ子になったレイアウトの使用方法を示しています。 「コンポーネントにレイアウトを適用する」セクションで示されている Episodes コンポーネントは、表示するコンポーネントです。 そのコンポーネントで、DoctorWhoLayout コンポーネントが参照されています。

次の DoctorWhoLayout コンポーネントは、この記事の前の方で示した例を変更したバージョンです。 ヘッダー要素とフッター要素が削除され、レイアウトで別のレイアウト ProductionsLayout が参照されています。 Episodes コンポーネントは、DoctorWhoLayout 内の @Body が出現する場所にレンダリングされます。

DoctorWhoLayout.razor:

@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}

ProductionsLayout コンポーネントには最上位レベルのレイアウト要素が含まれ、現在はそこにヘッダー要素 (<header>...</header>) とフッター要素 (<footer>...</footer>) が存在します。 Episodes コンポーネントが含まれる DoctorWhoLayout は、@Body が出現する場所にレンダリングされます。

ProductionsLayout.razor:

@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>

レンダリングされた次の HTML マークアップが、前の入れ子になったレイアウトによって生成されます。 関連する 3 つのコンポーネントによって提供される入れ子になったコンテンツに注目するため、余分なマークアップは示されていません。

  • ヘッダー (<header>...</header>)、プロダクション ナビゲーション バー (<nav>...</nav>)、フッター (<footer>...</footer>) の各要素とその内容は、ProductionsLayout コンポーネントから生成されます。
  • Doctor Who™ Episode Database という見出し (<h1>...</h1>)、エピソード ナビゲーション バー (<nav>...</nav>)、商標情報要素 (<div>...</div>) は DoctorWhoLayout コンポーネントから生成されます。
  • Episodes という見出し (<h2>...</h2>) とエピソードの一覧 (<ul>...</ul>) は、Episodes コンポーネントによって生成されたものです。
<body>
    <div id="app">
        <header>
            <h1>Productions</h1>
        </header>

        <nav>
            <a href="main-production-list">Main Production List</a>
            <a href="production-search">Search</a>
            <a href="new-production">Add Production</a>
        </nav>

        <h1>Doctor Who&trade; Episode Database</h1>

        <nav>
            <a href="main-episode-list">Main Episode List</a>
            <a href="episode-search">Search</a>
            <a href="new-episode">Add Episode</a>
        </nav>

        <h2>Episodes</h2>

        <ul>
            <li>...</li>
            <li>...</li>
            <li>...</li>
        </ul>

        <div>
            Doctor Who is a registered trademark of the BBC. 
            https://www.doctorwho.tv/
        </div>

        <footer>
            Footer of Productions Layout
        </footer>
    </div>
</body>

統合コンポーネントと Razor Pages レイアウトを共有する

ルーティング可能なコンポーネントが Razor Pages アプリに統合されている場合、コンポーネントでアプリの共有レイアウトを使用できます。 詳細については、「ASP.NET Core Razor コンポーネントを ASP.NET Core アプリに統合する」をご覧ください。

ルーティング可能なコンポーネントが Razor Pages アプリに統合されている場合、コンポーネントでアプリの共有レイアウトを使用できます。 詳細については、「ASP.NET Core Razor コンポーネントのプリレンダリングと統合を行う」を参照してください。

セクション

子 Razor コンポーネントからレイアウト内のコンテンツを制御するには、「ASP.NET Core Blazor セクション」を参照してください。

その他のリソース