ASP.NET Core でのレイアウト

作成者: Steve SmithDave Brock

ページやビューは、多くの場合、ビジュアルおよびプログラムの要素を共有します。 この記事では、次の方法を示します。

  • 共通のレイアウトを使用する。
  • ディレクティブを共有する。
  • ページまたはビューを表示する前に、共通のコードを実行する。

このドキュメントでは、ASP.NET Core MVC に対するアプローチとして、Razor Pages とビューを含むコントローラーの 2 種類のレイアウトについて説明します。 このトピックでは、違いは最小限です。

  • Razor Pages は、Pages フォルダーにあります。
  • ビューを含むコントローラーでは、Views フォルダーをビューに使用します。

レイアウトとは

ほとんどの Web アプリには、ユーザーがページ間を移動する際に一貫性のあるエクスペリエンスを提供する共通レイアウトがあります。 通常、このレイアウトには、アプリのヘッダー、ナビゲーションまたはメニュー要素、フッターなどの共通のユーザー インターフェイス要素が含まれます。

Page Layout example

スクリプトやスタイルシートなどの共通の HTML 構造も、アプリ内の多くのページで頻繁に使用されます。 これらの共有要素をすべて layout ファイルで定義することで、アプリ内で使用する任意のビューで参照できるようになります。 レイアウトにより、ビュー内の重複するコードを削減できます。

規則により、ASP.NET Core アプリの既定のレイアウトには _Layout.cshtml という名前が付けられます。 テンプレートを使用すると、次のような新しい ASP.NET Core プロジェクトのレイアウト ファイルが作成されます。

  • Razor Pages: Pages/Shared/_Layout.cshtml

    Pages folder in Solution Explorer

  • ビューを含むコントローラー: Views/Shared/_Layout.cshtml

    Views folder in Solution Explorer

レイアウトでは、アプリのビューの最上位のテンプレートが定義されています。 アプリにはレイアウトは必要ありません。 アプリでは、異なるレイアウトを指定するさまざまなビューで、複数のレイアウトを定義できます。

次のコードでは、コントローラーとビューを含むテンプレートで作成されたプロジェクトのレイアウト ファイルを示します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - WebApplication1</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">WebApplication1</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Contact">Contact</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - WebApplication1</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

レイアウトの指定

Razor ビューには Layout プロパティがあります。 個々のビューは、このプロパティを設定することでレイアウトを指定します。

@{
    Layout = "_Layout";
}

指定されたレイアウトでは、完全なパス (/Pages/Shared/_Layout.cshtml/Views/Shared/_Layout.cshtml など) または部分的な名前 (例: _Layout) を使用できます。 名前の部分を指定すると、Razor ビュー エンジンが標準の検出プロセスを使用して、レイアウト ファイルを検索します。 ハンドラー メソッド (またはコントローラー) が存在するフォルダーが最初に検索され、その後で Shared フォルダーが検索されます。 この検出プロセスは、部分ビューの検出に使用されるのと同じプロセスです。

既定では、すべてのレイアウトで RenderBody を呼び出す必要があります。 RenderBody への呼び出しが配置されると、ビューのコンテンツがレンダリングされます。

セクション

レイアウトは、必要に応じて RenderSection を呼び出すことで、1 つ以上のセクションを参照することができます。 セクションは、特定のページ要素の配置場所を整理する方法を提供します。 RenderSection の呼び出しごとに、そのセクションを必須またはオプションにするかどうかを指定できます。

<script type="text/javascript" src="~/scripts/global.js"></script>

@RenderSection("Scripts", required: false)

必須のセクションが見つからない場合、例外がスローされます。 個々のビューには、@sectionRazor 構文を使用して、セクション内にレンダリングされるコンテンツを指定します。 ページまたはビューでセクションを定義する場合は、レンダリングされる必要があります (そうしないと、エラーが発生します)。

Razor Pages ビューでの @section 定義の例:

@section Scripts {
     <script type="text/javascript" src="~/scripts/main.js"></script>
}

前のコードでは、ページまたはビューの scripts セクションに scripts/main.js が追加されています。 同じアプリの他のページまたはビューではこのスクリプトは必要なく、スクリプト セクションは定義されていません。

次のマークアップでは、部分タグ ヘルパーを使用して _ValidationScriptsPartial.cshtml をレンダリングしています:

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

上記のマークアップは、スキャフォールディング Identity で生成されました。

ページまたはビューで定義されたセクションは、そのイミディエイト レイアウト ページでのみ使用できます。 これらは、部分、ビュー コンポーネント、またはビュー システムの他の部分からは参照できません。

セクションの無視

既定では、コンテンツ ページの本文とすべてのセクションがレイアウト ページですべてレンダリングされる必要があります。 Razor ビュー エンジンは、本文と各セクションがレンダリングされているかどうかを追跡することによってこれを実行します。

本文またはセクションを無視するようにビュー エンジンに指示するには、IgnoreBody メソッドと IgnoreSection メソッドを呼び出します。

Razor ページ内の本文とすべてのセクションは、レンダリングされるか無視される必要があります。

共有ディレクティブのインポート

ビューやページでは、Razor ディレクティブを使用して名前空間をインポートして依存関係の挿入を使用できます。 多くのビューで共有されるディレクティブは、共通の _ViewImports.cshtml ファイルで指定できます。 _ViewImports ファイルは、次のディレクティブをサポートします。

  • @addTagHelper
  • @removeTagHelper
  • @tagHelperPrefix
  • @using
  • @model
  • @inherits
  • @inject
  • @namespace

このファイルは、関数やセクションの定義などの Razor のその他の機能はサポートしていません。

_ViewImports.cshtml ファイルのサンプル:

@using WebApplication1
@using WebApplication1.Models
@using WebApplication1.Models.AccountViewModels
@using WebApplication1.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

ASP.NET Core MVC アプリの _ViewImports.cshtml ファイルは、通常、Pages (または Views) フォルダーに配置されます。 _ViewImports.cshtml ファイルは任意のフォルダー内に配置できます。その場合、そのフォルダーとそのサブフォルダー内のページまたはビューにのみ適用されます。 _ViewImports ファイルの処理はルート レベルで開始されてから、フォルダーごとに、ページまたはビュー自体の場所に至るまで行われます。 ルート レベルで指定された _ViewImports の設定は、フォルダー レベルでオーバーライドされる可能性があります。

たとえば、次のように想定します。

  • ルート レベルの _ViewImports.cshtml ファイルには、@model MyModel1@addTagHelper *, MyTagHelper1 が含まれます。
  • サブフォルダーの _ViewImports.cshtml ファイルには、@model MyModel2@addTagHelper *, MyTagHelper2 が含まれます。

サブフォルダー内のページおよびビューは、タグ ヘルパーと MyModel2 モデルの両方にアクセスできます。

ファイル階層内に複数の _ViewImports.cshtml ファイルが見つかった場合、ディレクティブの組み合わせ動作は次のようになります。

  • @addTagHelper@removeTagHelper: 順番どおりにすべて実行
  • @tagHelperPrefix: ビューに最も近いものが、他のものをすべてをオーバーライドする
  • @model: ビューに最も近いものが、他のものをすべてをオーバーライドする
  • @inherits: ビューに最も近いものが、他のものをすべてをオーバーライドする
  • @using: すべてが含まれ、重複は無視される
  • @inject: プロパティごとに、ビューに最も近いものが、同じプロパティ名を持つ他のものすべてをオーバーライドする

各ビューの前にコードを実行する

各ビューまたはページの前に実行する必要があるコードは、_ViewStart.cshtml ファイルに配置する必要があります。 慣例により、_ViewStart.cshtml ファイルは Pages (または Views) フォルダーに配置されます。 _ViewStart.cshtml に一覧表示されているステートメントは、すべての (レイアウトでもなく、部分ビューでもない) 完全なビューより前に実行されます。 ViewImports.cshtml と同様に、_ViewStart.cshtml は階層構造です。 _ViewStart.cshtml ファイルがビューまたはページ フォルダーで定義されている場合、Pages (または Views) フォルダーのルートで定義されているもの (存在する場合) の後に実行されます。

_ViewStart.cshtml ファイルのサンプル:

@{
    Layout = "_Layout";
}

上記のファイルは、すべてのビューで _Layout.cshtml レイアウトを使用することを指定します。

_ViewStart.cshtml_ViewImports.cshtml は、通常、/Pages/Shared (または /Views/Shared) フォルダーに配置されません。 これらのファイルのアプリ レベルのバージョンは、/Pages (または /Views) フォルダーに直接配置する必要があります。