ASP.NET Core MVCを初めて触る人でも理解できるように、公式チュートリアルを丁寧に解説する記事を全4回に分けて作ってみました。
公式チュートリアルでは少しわかりにくい表現をかみ砕いて説明してみたり、英語圏向けの表記を日本語向けに直したりしています。また、冗長だと思われる部分を省いています。
自分の勉強用でもあるため、もし誤っている箇所等あればご指摘いただけると幸いです。
第1回では、プロジェクトの作成から、コントローラーとビューの基本的な使い方までを見ていきます。(公式チュートリアルのパート1~3に該当)
公式チュートリアルはこちら

本記事の対象
- ASP.NET Core MVCの基本的な使い方を理解したい人
- C#の基本文法がある程度わかる人
環境
- Windows10
- Visual Studio Community 2019 16.10.0
- .NET 5(.NET 6や.NET Core 3.1でも内容に大きく変わりはないと思います。)
準備と新規プロジェクトの作成
.NET5を使う場合は、Visual Studio 2019のバージョンが16.8以降であることを確認してください。それ以前のバージョンでは.NET5が使えないため、バージョンを上げる必要があります。

また、Visual Studio Installerのワークロードに「ASP.NETとWeb開発」が追加されていない場合は追加しておきましょう。

新規プロジェクトの作成画面を開き、「ASP.NET Core Web アプリ(Model-View-Controller)」を選択します。

プロジェクト名はチュートリアルと同様に「MvcMovie」にします。

ターゲットフレームワークに「.NET5.0(現在)」を選択します。

プロジェクトが作成できたら、上部の「IIS Express」をクリックするとプログラムが実行され、ブラウザが起動します。「F5」キーでもOKです。

ちなみにIIS Expressとは、Visual Studioに標準搭載されている開発用の軽量Webサーバーです。開発・検証用の場合は自分でWebサーバを用意する必要がないので便利です。
このようなページが表示されれば成功です。もし「証明書をインストールしますか?」という確認が表示された場合は「はい」を選択してください。

アドレスバーのlocalhostはアプリが動作してるコンピュータ自身を表す特殊なホスト名で、ここで実行されたアプリは自分のPCでしか見ることができません。
その後ろのポート番号は実行環境によって異なります。
(参考)起動するブラウザは以下から変更可能です。

コントローラーの追加
コントローラーとは、クライアントからのリクエストを処理し、モデルやビューを制御する役割を担います。
プロジェクト作成時に「HomeController.cs」が作成されていますが、今回は新しいコントローラーを追加してみましょう。
ソリューションエクスプローラーの「Controllers」フォルダを右クリック→追加→コントローラーをクリックします。

「MVCコントローラー-空」選択します。

名前を「HelloWorldController」にして追加します。ASP.NET Core MVCではコントローラー名は必ず○○Controllerとしなければなりません。

HelloWorldController.csに2つのpublicメソッドを追加します。
using Microsoft.AspNetCore.Mvc;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
// /HelloWorld にアクセスした時の処理
public string Index()
{
return "Hello World!";
}
// /HelloWorld/Welcome にアクセスした時の処理
public string Welcome()
{
return "Welcomeページです。";
}
}
}
このようにコントローラークラス内で定義されたpublicメソッドをアクションメソッドといいます。
F5でデバッグ実行し、/helloworldにアクセスすると「Hello World!」と表示されます。

さらに/helloworld/welcomeに移動するとWelcomeメソッドで設定したメッセージが表示されます。

このように、指定したURLによってコントローラーのアクションメソッドが実行されます。
このルーティングのルールは{コントローラー名}/{アクションメソッド名}/{パラメーター(省略可能)}となっており、Startup.csのConfigureメソッド内で決められています。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
既定のルールでは、アプリを起動するとまずHomeControllerのIndexメソッドの内容が表示されます。
試しにコントローラー名をHelloWorld、アクションメソッド名をWelcomeに変えて実行してみると、初期表示はHelloWorldControllerのIndexメソッドの内容になります。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=HelloWorld}/{action=Welcome}/{id?}");
});
では次にHelloWorldControllerのWelcomeメソッドを次のように変更してみます。
public string Welcome(string name, int age = 1)
{
return $"名前:{name}さん, 年齢:{age}歳";
}
第1引数に文字列型、第2引数に数値型(既定値は1)の値を取るメソッドに変更しました。
これで /HelloWorld/welcome?name=Taro&age=20
のようなアドレスを指定すると、指定した値が画面に表示されます。

このようにnameおよびageパラメーターはクエリ文字列※で渡すことができます。
これはMVCに備わっているモデルバインドシステム※によって実現されています。
※クエリ文字列とは?
クエリ文字列(URLパラメーター)とは、サーバーに情報を送るためにURLの末尾につけ足す文字列(変数)のこと。
「?」をURLの末尾につけ、その次に「パラメーター=値」をつけ、複数のパラメーターをつけたい場合は「&」を使用します。
※モデルバインドシステムとは?
フォーム等から送信された値やクエリ文字列の値を自動で.NET 型に変換して、コントローラーのアクションメソッドに提供する仕組みです。
今回の処理の流れを簡潔に示すとこのようになります。
- URLで要求を受信
- アクションメソッドの最初の引数(今回はnameというstring型の変数)を検索
- クエリ文字列内のname=Taroを見つける
- 次の引数(ageというint型)を検索
- クエリ文字列内のage=20を取得
- 文字列の”20″を数値の20に変換
- Welcomeメソッドが呼ばれて引数に取得した値が渡される

最後に、先ほど説明していなかったパラメーターのidを指定した場合の挙動について見てみます。
Welcomeメソッドを以下のように書き換えます。
public string Welcome(string name, int Id = 1)
{
return $"名前:{name}さん, ID:{Id}";
}
そのうえで、/HelloWorld/welcome/3?name=Taroにアクセスしてみると以下のように表示されます。

今まではidを省略していましたが、今回はidを3と指定したことで、Startup.csのURLパターンのidパラメーターと一致し、Welcomeメソッドに値が渡されました。
ビューの追加
今まではコントローラーで直接文字列を返していましたが、今後はビューテンプレートを返すように変更します。
Razorテンプレートの呼び出し
Razorとは、HTMLにC#の構文を埋め込んだテンプレートを生成することができる仕組みです。
テンプレートの拡張子は○○.cshtmlとなります。
まずHelloWorldControllerクラスのIndexメソッドを次のように書き換えます。
public IActionResult Index()
{
return View();
}
前回は単純な文字列を返すだけだったので戻り値にstring型を指定していましたが、今回はIActionResult型となっています。
IActionResultを実装すると、RazorのテンプレートやJSON形式の文字列をレスポンスとして返すことができたり、他のURLへリダイレクト処理ができたりと様々なパターンに対応できます。
このようにViewメソッドを引数なしで呼び出すと「/Views/コントローラー名/アクション名.cshtml」が返されます。
つまり今回の場合は「/Views/HelloWorld/Index.cshtml」が呼び出されます。
テンプレートの追加
Indexメソッドで返すためのテンプレートを作成しましょう。
まずViewsフォルダの直下に「HelloWorld」という名前のフォルダを作成します。
(Viewsフォルダ右クリック→追加→新しいフォルダー)
次に、作成したHelloWorldフォルダを右クリック→追加→ビューの追加で「新規スキャフォールディング アイテムの追加」ウィンドウを開きます。

「Razorビュー – 空」を選択後、ファイル名を「Index.cshtml」として追加してください。


追加できたら、index.cshtmlの中身を書き換えます。
ViewData[“Title”]には各ページのページタイトルを設定します。
@{
ViewData["Title"] = "Movie List";
}
<h2>My Movie List</h2>
F5で実行し、/HelloWorld/ にアクセスしてみましょう。このように表示されれば成功です。

共通レイアウト
ヘッダーやフッターなど各ページに共通するレイアウトは、Views/Shared/_Layout.cshtmlに書かれています。
6行目のViewData[“Title”]には、先ほどのように各ページで指定したタイトルが代入されます。
34行目の@RenderBody()の部分では、各ページ固有のコンテンツを表示しています。
少し表記を変更してみましょう。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-controller="Movies" asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2021 - Movie App - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
アプリの名称をMovie Appに変更しました。
また、ヘッダーのアプリ名をクリックするとMovies/Indexに飛ぶようにしました。Moviesコントローラーは次回作成します。
F5で実行し、ヘッダーとフッターの文言が変更されていることを確認してみましょう。
もし共通レイアウトを変更しても反映されない場合は、Ctrl + F5 キーを押して強制リロードしてみてください。

ちなみにViews/_ViewStart.cshtmlの中身はこのようになっており
@{
Layout = "_Layout";
}
ビューの処理にあたってまずこの_ViewStart.cshtmlが呼び出され、_Layout.cshtmlが各ページに適用されるという流れになります。
共通レイアウトを各ファイルに適用したくない場合はnullを指定すればOKです。
コントローラーからビューへデータを渡す
Welcomeメソッドでもビューテンプレートを返すように変更してみましょう。
既に登場したViewDataを使って、コントローラーからビューテンプレートにデータを渡します。
using Microsoft.AspNetCore.Mvc;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
// /HelloWorld
public IActionResult Index()
{
return View();
}
// /HelloWorld/Welcome
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = $"こんにちは!{name}さん";
ViewData["NumTimes"] = numTimes;
return View();
}
}
}
nameとnumTimes変数をそれぞれViewDataに格納することで、ビューに渡すことができます。
ViewDataとは
ViewDataはすべての型を使用できる動的な辞書型オブジェクトで、実行時に型が決まるため「弱い型指定」と呼ばれます。
一方で、第2回で紹介するビューモデルはコンパイル時に型チェックが行われるため「厳密な型指定」と呼ばれます。
弱い型指定のデータはビューモデルに比べて便利なものの、型チェックができない分エラーが発生しやすいため、多用するのは避けるべきです。
詳しくは以下を参照してください。

Views/HelloWorldフォルダ内にWelcome.cshtmlを新規追加し、次のように書き換えます。
@{
ViewData["Title"] = "Welcome";
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
コントローラーから渡されたViewDataの中身をループで取り出し、リストで表示しています。
実行後に、/HelloWorld/Welcome?name=Taro&numtimes=4
というアドレスにアクセスしてみると、ViewData[“Message”]の内容がnumTimes分だけ繰り返されて表示されます。

以上でプロジェクトの作成からビューの追加までの説明を終了します。
第2回では、ViewDataではなくビューモデルを使ってデータを渡す方法について見ていきたいと思います。
【参考書籍】
.NET5についてより詳しく学びたい場合は以下の本がおすすめです。
コメント
これはありがたい・・・
.NET6がリリースされたので、15年ぶりに色試してみようと思ったのですが、いろいろ拡張されまくってまって、どこから手を付けたらいいのか途方に暮れていたところでした。
まずはひらひらさんのわかりやすいチュートリアルから手をつけてみようと思います。
感謝してます。
ありがとうございました!
コメントありがとうございます。
15年前とは相当仕様が変わってしまっていますよね…
私もまだまだ勉強中の身ですが、そう言っていただけるととても励みになります!
いずれは.NET6に対応した記事も書いていきたいと思いますので、その際もぜひ参考にしていただけたら嬉しいです。