Indexアクションの動き
プロジェクトのフォルダ構成を見たところで、実際のファイルを見てそれらの関連を理解しましょう。まず、MVCアプリケーションの基本的な動きを理解するために、MvcSampleアプリケーションのトップページに相当するHomeコントローラのIndexアクションを見ていきます。
コントローラのファイル(Indexアクション) - Controllers/HomeContoller.cs
コントローラの役割は、リクエストに基づきアクションを呼び出し、その処理結果をビューに反映することです。アクションとはその名のとおり、コントローラに行わせたいことで、具体的にはアクションメソッドとして定義します。
コントローラのファイルは、Controllersフォルダに置くことになっています。ファイル名の制約はありませんが、通常は「{コントローラ名}Controller.cs」となります。これから見ていくHomeContoller.csファイルは、既定で作成されるHomeコントローラのファイルです(リスト1)。
…略…
namespace MvcSample.Controllers;
public class HomeController : Controller (1)
{
…略…
public IActionResult Index() (2)
{
return View(); (3)
}
…略…
}
コントローラファイルの基本的な構造は、(1)コントローラクラスの定義、(2)アクションメソッドの定義となります。コンストラクタなどもありますが、重要なのはこの2つです。
(1)は、コントローラの基本クラスであるControllerを継承してHomeControllerクラスを定義しています。これで、HomeControllerクラスはコントローラとして認識されます。
(2)は、Indexアクションに対応するIndexメソッドの定義です。アクションメソッドの戻り値は、すべてIActionResultインタフェース型を持つことになっています。ここでは(3)のようにViewメソッドの戻り値をそのまま返していますが、これはIActionResultインタフェース型を継承するViewResult型であり、ビューの表示に使われるさまざまな情報を持ったクラスです。つまり、この場合のアクションメソッドの戻り値はビューの表示情報となり、Indexメソッドの戻り値をもとに、表示すべきビューを決めるという動きになります。
なお、コントローラクラスにあるpublicなメソッドは、すべてアクションメソッドと解釈されます。アクションメソッドとしたくないメソッドはprivateにするか、NoAction属性をメソッドの宣言に付記してください。
[NOTE]コントローラクラスの条件
クラスがコントローラクラスとして認識されるための条件は以下のいずれかになっています。通常はControllerクラスを継承するので、あまり気にする必要はないでしょう。
- クラス名の後半(サフィックス)が「Controller」である
- Controllerクラスを継承している
- Controller属性をクラス宣言に付記する
ビューのファイル(Indexアクション) - Views/Home/Index.cshtml
ビューの役割は、コントローラ(アクションメソッド)の処理結果を受け取ってHTMLなどを生成(レンダリング)することです。上記のとおり、アクションメソッドはビューの表示情報を返しますが、このとき実際の表示に使われるテンプレート(ひな型)がビューのファイルです。
ビューのファイルは、Viewsフォルダに置くことになっています。図3のプロジェクト構成を見るとわかりますが、Viewsフォルダの下にはHomeフォルダがあります。このように、ビューのファイルはコントローラと同名のフォルダに分けて置くことになっています。
また、ビューのファイルは、基本的に「/コントローラ名/アクション名.cshtml」と命名することになっています。よって、HomeコントローラのIndexアクションに対応するビューはViews/Home/Index.cshtmlファイルということになります。このファイルの中身を見てみます(リスト2)。
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<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>
本連載を読み進んできた読者には、見覚えがある内容と思われたのではないでしょうか? それもそのはずで、これはRazor Pagesの既定のビューであるPages/Index.cshtmlとほぼ同じで、同様にRazor構文でコードを記述できます。異なるのは、以下の2点です。
- @pageディレクティブが存在しない――Razor Pagesページ特有のディレクティブで、MVCには不要。
- @modelディレクティブが存在しない――Indexアクションには処理すべきデータがないので省略されている。Razor Pagesではビューに対するページモデルが常に存在するので必須。MVCでもビューがデータを受け取る場合には必要。
ディレクティブについては、Razor PagesにおけるPages/Home/Index.cshtmlについて説明している第3回を参照してください。このViews/Home/Index.cshtmlファイルの内容(実際にはViews/Shared/_Layout.cshtmlとともにレンダリングされた内容)が、HomeコントローラのIndexアクションの実行の結果、Viewメソッドによって呼び出され表示されます。
[NOTE]アクションに対応するビューのひも付け
ASP.NET Core MVCでは、既定でViews/{Controller}/{Action}.cshtmlファイルを探しますが、見つからない場合にはViews/Shared/{Action}.cshtmlファイルを探します。これにより、異なるコントローラの同一のアクションでビューを共有できます。
エントリポイントのファイル(ルーティング) - Program.cs
HomeコントローラとIndexアクション、ビューを見てきましたが、ここで押さえておきたいのがルーティングです。ルーティングとは、URLのパス部から適切なコントローラとアクションを呼び出すためのルール設定のことを言います。例えば、/Home/IndexというパスがURLにある場合、HomeコントローラからIndexアクションを呼び出しなさいという設定がルーティングです。
Razor Pagesでは、Pagesフォルダの構造がそのままURLのパス部を構成するというルーティングルールでした。これに対してMVCでは、物理的なフォルダ/ファイルの構造がパスと一致している必要がないので、柔軟性のあるサイト構成が可能になります。その分、コントローラやアクションの追加、変更に伴いルーティングルールを適宜メンテナンスする必要が生じます。
ルーティングは、エントリポイントというアプリケーションの起点のファイルに対して設定します。ASP.NETの世界では、プロジェクトのルートにあるProgram.csファイルが、エントリポイントです(リスト3)。ルーティングの設定のほかに、アプリケーションの動作に必要な基本的な設定も行うのはRazor Pagesと同様です。
Razor Pagesでは、ルーティングを使用するというUseRoutingメソッドに加えて、既定の動作を指定するMapRazorPagesメソッドが呼ばれるだけでしたが、MVCでは上記のとおりルーティングルールの明示的な設定が必要になっています。
…略…
app.UseRouting(); (1)
app.UseAuthorization();
app.MapControllerRoute( (2)
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"); (3)
app.Run();
(1)と(2)がルーティングの設定です。(2)では、「規約によるルーティング」(Conventional Routing)を設定しています。既定値として、名前(name)が「default」でパターン(pattern)が「{controller=Home}/{action=Index}/{id?}」であるルートをMapControllerRouteメソッドで登録しています。
このパターンは、「コントローラ名/アクション名/id値」を表しており、パターンに合致するURLでは自動的に指定されるコントローラ/アクションが呼び出されます(図4)。なお、「?」の付加されたid値は省略可能を意味し、指定された場合のみアクションに渡されます。このパターンに当てはまらないパス部を構成する場合には、そのためのルート設定を追加していくことになります。
なお、(3)のように等号(=)を使ってコントローラとアクションの既定値も指定できます。この場合は、既定値は「Home/Index」となり、パスが「/」や「/Home」であるURLに対応します。
その他のアクションについて
HomeコントローラのIndexアクションとそのビュー、そしてルーティングを見ることで、MVCにおけるリクエストからページが表示されるまでの基本的な流れを見てきました。最後に、他のアクションについても見ておきましょう。
コントローラのファイル(その他) - Controllers/HomeContoller.cs
コントローラのファイルを再掲します(リスト4)。
…略…
public class HomeController : Controller
{
// ロガーを保持するフィールド
private readonly ILogger<HomeController> _logger; (1)
// ロガーを受け取るコンストラクタ
public HomeController(ILogger<HomeController> logger) (2)
{
_logger = logger;
}
…Indexアクションは既出なので略…
// Privacyアクション
public IActionResult Privacy() (3)
{
return View();
}
// Errorアクション
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() (4)
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
(1)(2)はロガーを受け取るコンストラクタとそれを保持するフィールドで、Razor Pagesにおけるものと同じです。そして(3)(4)が、Index以外のアクションメソッドです。(3)のPrivacyアクションの処理内容はIndexアクションと全く同じなので、ひも付いたビューの内容を表示するだけになります。(4)のErrorアクションもViewメソッドの呼び出しを行っているのですが、他の2つのアクションメソッドとは異なり、引数を伴うものになっています。これは、ビューがデータを受け取る場合の書式です。ビューがデータを受け取る場合については第6回で詳しく紹介しますので、ここではErrorアクションがビューにErrorViewModel型のデータを渡している、ということに触れておくのみにします。
モデルのファイルを見る - ErrorViewModel.cs
ErrorアクションがViewメソッドの引数で渡しているデータのコードが書かれている、ErrorViewModel.csファイルはどのようになっているでしょうか(リスト5)。
namespace MvcSample.Models;
public class ErrorViewModel (1)
{
public string? RequestId { get; set; } (2)
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); (3)
}
ErrorViewModelはErrorアクションのためのクラスで、Homeコントローラ内で何らかのエラーが発生した際に、エラー画面を表示するための情報をビューに渡すために用いられます。
(1)ではErrorViewModelクラスを定義していますが、継承元のクラスがないPOCOです。メンバーは(2)の文字列型のプロパティのみであり、これはエラー画面で表示すべきリクエストIDを保持します。(3)のShowRequestIdメソッドは、RequestIdプロパティを参照可能かを返します。これらのプロパティとメソッドがどのように使われているかは、次のErrorビューで後述します。
Views/Shared/Error.cshtml
Errorアクションに対応するビューError.cshtmlは、Views/Sharedフォルダにあります。つまり、コントローラ間で共有します。リスト6は、Views/Shared/Error.cshtmlの内容です。
@model ErrorViewModel (1)
…略…
@if (Model?.ShowRequestId ?? false) (2)
{
<p>
<strong>Request ID:</strong> <code>@Model?.RequestId</code>
</p>
}
…後略…
ここでは、(1)のように@modelディレクティブが指定されています。Viewメソッドで渡されたデータを扱う場合には、そのデータ型を@modelディレクティブで指定する必要があります。
(2)のブロックは、モデルがnullでなくShowRequestIdメソッドの戻り値がtrueであれば、(3)でRequestIdフィールドの値を表示しています。ここで使用されている疑問符(?)はC#のnull許容演算子(?)とnull結合演算子(??)です。いずれも、nullの可能性のあるオブジェクトから安全かつシンプルに値を取り出すことのできる演算子です。ここで先ほどのモデルの定義にさかのぼると、(2)と(3)の意味が理解できるのではないでしょうか?
まとめ
今回は、MVCパターンのためのフレームワークであるASP.NET Core MVCのアプリケーションをテンプレートから作成し、その基本的な構成を見てきました。次回は、これを発展させて、データ処理を絡めたアプリケーションに強化していきながら、アクションやビューの変化について見ていきます。
