プロジェクトの構成
ここで、プロジェクトがどのような構成になっているか見てみましょう。プロジェクトには、主にPagesフォルダとwwwrootフォルダがあるのみです。いたってシンプルな構成になっています。
Pagesフォルダ
Pagesフォルダには、Razorページが置かれます。あらゆるページのファイルはPagesフォルダ以下に置かれ、フォルダの構成がそのままWebサイトの構成になります。既出のとおり、Razorページは、.cshtmlファイルと.cshtml.csファイルのペアから構成されます。それぞれの役割は以下のとおりです。
- .cshtmlファイル:ビュー。HTMLと、Razor構文を使用したC#コードが保存される。
- .cshtml.csファイル:ページモデル。ページイベントを処理するC#コードが保存される。
簡単に言うと、ビューと、それに対応するページモデルのコードを書くファイルです。
この他に、全体のレイアウトやUI部品などを表すファイルがあり、これらにはアンダースコアで始まる名前が付けられます。重要なファイルに、Pages/Sharedフォルダにある_Layout.cshtmlファイルがあります。このファイルには、すべてのページに共通のレイアウトが記述されています。初期状態では、ページの上部(ヘッダ)に表示されるナビゲーションメニューと、各々のページコンテンツの挿入指示、ページの下部(フッタ)に表示される著作権が記述されています(図6)。このページの記述を変えると、基本的にすべてのページの表示も変わります。
wwwrootフォルダ
wwwrootフォルダには、JavaScriptファイル、CSSファイルなどの静的なファイルが格納されます。wwwroot/jsフォルダにJavaScriptファイル、wwwroot/cssフォルダにCSSファイル、wwwroot/libフォルダにはBootstrapやjQueryなどの外部ライブラリが置かれます。独自にJavaScriptファイルやCSSファイルを使うなら、それぞれjsフォルダ、cssフォルダに置くようにしましょう。
この他のファイル
プロジェクトフォルダ直下には、appSettings.json、Program.csなどがあります。appSettings.jsonには、接続文字列などの構成データが保存されます。Program.csには、プログラムのエントリポイントが保存されます。これらの詳細については、実際にファイルに手を入れる機会があれば、改めて紹介します。
[NOTE]Startup.csは作成されない
ASP.NET Core 6では、それ以前のバージョンでは存在していたStartup.csは作成されなくなりました。替わりに、Program.csの構造が大きく異なっています。ASP.NET Core 5.0で作成したProgram.csとStartup.csをASP.NET Core 6.0で動作させることは可能ですが、新しいProgram.csの使用が推奨されているため、基本的にはそのまま新しいProgram.csだけを使うことをお勧めします。
ソースファイルの解読
プロジェクトのフォルダ構成がわかったら、実際のRazorページファイルを見てみましょう。基本的な構造を示すために、トップページの中身を見ていきますが、実質何もやっていないに等しく、実用的なアプリケーションのためにはRazor構文やデータ処理のためのコードを各々に記述していくことになります。
.cshtmlファイルを見る
図6で、Razor Pagesにおけるページの構築イメージを示しました。最終的なページのイメージは、Pages/Shared/_Layout.cshtmlファイルの中にPages/配下にあるそれぞれの.cshtmlファイルのコンテンツを埋め込んだものとなります。これがどのように実現されているのか見るために、まずはPages/Shared/_Layout.cshtmlファイルを見てみましょう。長いので省略しますが、WebデザインフレームワークであるBootstrapを用いたレイアウトとなっています(リスト1)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesSample</title> (1)
…中略…
</head>
<body>
<header>
…中略…
</header>
<div class="container"> (2)
<main role="main" class="pb-3">
@RenderBody() (3)
</main>
</div>
<footer class="border-top footer text-muted">
…中略…
</footer>
…中略…
@await RenderSectionAsync("Scripts", required: false) (4)
</body>
</html>
(1)では、Razor構文である@ViewData["Title"]が実行されて、結果が<title>要素に反映されています。上述のとおり、@ViewData["Title"]はインライン式、あるいはコードナゲットと呼ばれるRazor構文における式です。ViewDataの役割やViewData["Title"]がどのように生成されるかということについては後述します。
ページのメインコンテンツは、(2)のcontainerクラスが指定された<div>要素です。この内部の(3)に@RenderBody()というメソッド呼び出しのインライン式があります。これが該当ページの.cshtmlの内容に置き換わることによってページ全体が完成します。
(4)は、.cshtmlファイルに@section Scriptsセクションがある場合に、非同期にそれを読み込む指定です。requiredがfalseになっていますので、存在しなければ(これが標準)無視されます。
続けて、トップページに対応するPages/Index.cshtmlの中身を見てみます(リスト2)。
@page (1)
@model IndexModel (2)
@{ (3)
ViewData["Title"] = "Home page";
}
<div class="text-center"> (4)
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
こちらは、打って変わってシンプルな構造です。各ページで共通のヘッダやフッタはレイアウトファイルに持たせることで、ここには各ページで必要な内容のみを記述すればよいというわけです。
(1)は@から始まることからわかるようにRazor構文ですが、ディレクティブという特別な役割を持った文です。@pageディレクティブは、このファイルがRazor Pagesアプリ用の.cshtmlファイルであることを示しています。@pageは、MVCパターンにおけるコントローラ経由ではなく、ページモデルが直接要求を処理するということを示す、Razor Pagesならではのディレクティブです。
(2)は@modelディレクティブであり、このページに対応するページモデルを示しています。ここではIndexModelが指定されており、これはIndex.cshtmlと対になるIndex.cshtml.csファイルにて宣言されています。詳しくは後述します。
(3)はRazor構文によるコードブロックで、{~}の部分がコードとして認識されます。つまり、(3)の中にC#によるコードを書いていきます。コードは1行のみで、ViewData["Title"]に、"Home Page"という文字列を代入しています。レイアウトファイルの説明で、ViewData["Title"]が参照され、これはページの<Title>要素に使用されたということは示しました。その内容を、ここで設定しているというわけです。ここで変更されたViewDataは、ページモデルに反映されて再び変更されない限り値が保持されます。
(4)は、レイアウトファイルに配置されるコンテンツ本体です。リスト1(3)の@RenderBody()の呼び出しで、最終的なページにはここの内容に置き換えられて配置されるというわけです。
.cshtml.csファイルを見る
では、Index.cshtmlファイルに対応するページモデルのコードが書かれている、Index.cshtml.csファイルはどのようになっているでしょうか?(リスト3)
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesSample.Pages;
public class IndexModel : PageModel (1)
{
private readonly ILogger<IndexModel> _logger; (2)
public IndexModel(ILogger<IndexModel> logger) (3)
{
_logger = logger;
}
public void OnGet() (4)
{
}
}
注目すべきは(1)です。これは、PageModelクラスを継承して新たにIndexModelクラスを宣言していますが、このIndexModelはリスト2において@modelディレクティブで参照されていたものです。つまり、index.cshtmlにおいて必要なページモデルのコードはここに書かれている、というわけです。とはいえ、プロジェクト作成直後の内容は(2)のロガーを保持するフィールド、(3)のロガーを指定するコンストラクタ(appsettings.jsonでLoggingの指定をしていた際に呼び出されます)、(4)のページに直接アクセスしたときの呼び出されるメソッドのみです。
ここまでで、自動生成されるサンプルを実行し、そのファイルに何が書かれているかを読み解いてみました。
まとめ
今回は、ページ指向のフレームワークであるRazor Pagesのアプリケーションをテンプレートから作成し、その構成を見てきました。次回は、これを発展させて、データ処理を絡めたアプリケーションに強化していきます。
