はじめに
調べすぎて記事内がリンクだらけになっていますがご容赦ください。
また、間違い等ございましたらご指摘いただけると幸いです。
記事が長いなと思ったので、
- 解説、調査の前半
- 実際の作成過程の後半
の構成にしたいと思います
前半概要
- 実務でAsp.netを使用しているが、いまいち理解していないことが多いので自分で一から作ってみる。
- Web系の仕事をするためにも必要な知識を高めたい
プロジェクト作成
VisualStudioでテンプレートを作成します
MVCで作成したいのでMVCテンプレートを選びます。
「Razorランタイムコンパイルを有効にする」にはチェックを入れています。
Razorランタイムコンパイルとはアプリの実行時にRazor拡張子(.cshtml)を編集するとリアルタイムで変更が反映されるものっぽいです。
Razor ファイルのビルド時および発行時のコンパイルは、既定で Razor SDK によって有効になっています。
有効になっていると、実行時コンパイルはビルド時のコンパイルを補完し、編集された Razor ファイルを更新できます。
引用:ASP.NET Core での Razor ファイルのコンパイル
いざ作成!
いっぱいファイルが作成されました。。。
とりあえず今の状態で起動してみるとどうなるのでしょうか?
起動確認
おお!なんだかそれっぽいようなサイトがすでに出来上がっています。
ご丁寧にプライバシーポリシーまで用意されています。
Webサービスを作りたいならここから少し手を加えるだけで公開できそうな感じがしています。
さて、次にソースを紐解いていきます。
ソースの確認
順番に見ていきます。
各種設定ファイル
launchSetting.json
とappsetting.json
が作成されています。
launchSetting.json
はアプリの起動設定、appsetting.json
はアプリの設定ファイルになります。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55370",
"sslPort": 44383
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
},
"ToDoManage": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
}
}
}
プロファイルはデフォルトで2つのプロファイルが用意されています。
2つの違いは、dotnetRunMessages
とapplicationUrl
の設定の有無だけです。
dotnetRunMessages
の項目が公式で文書化されていませんでしたが、この記事を見るにおそらく内部コンソールにフィードバックを表示するかしないか。。。でしょうか?
その他の項目については以下で解説されていましたので参考にさせていただきます。
参考:ASP.NET Core と launchSettings.json と appSettings.json
Program.cs
アプリを起動すると、Program.csのエントリポイントMain
メソッドから始まります。
C#ではMain
という名前のメソッドがエントリーポイントになります。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
これだけのコードですが、何をしているのかが一つもわからない…
調べてみると、上記はGenericHostの生成と実行のコードらしい。
GenericHostとは何ぞや?に関しては以下の記事で詳しく解説されていました。
参考:.Net Core の Generic Host とは何か
本番用途のアプリならここのソースをメソッドチェーンの形でいろいろカスタマイズしていくらしい。
参考:.NET Core の設定情報の仕組みをしっかり理解したい方向け基本のキ
が、今回は勉強用のToDoアプリなのでここはデフォルトのままで行きます。
startup.cs
Program.csのCreateHostBuilderメソッドのwebBuilder.UseStartup<Startup>()
のジェネリクスで指定していたクラスでアプリの動作を定義するクラスになります。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
ConfigureServices
メソッドはDI(Dependency Injection)を定義するためのメソッド
DIとは、クラスに固定された定数、変数、インスタンス等を外部から注入することだそうです。
参考:猿でも分かる! Dependency Injection: 依存性の注入
じゃあ実際にConfigureServices
メソッドはどういう風に活用するのか、という点でいうとまだ理解できていないので、
ここでは、記述されている、services.AddControllersWithViews()
に注目します。
これはMVCアプリケーションを開発する際に必要なものらしいです。おまじないですね。
上記を含め、他にもおまじないがあるそうです。
パターン | メソッド |
---|---|
WebAPIを作る場合 | AddControllers() |
RazorPageアプリケーションを作る場合 | AddRazorPages() |
MVCアプリケーションを作る場合 | AddControllersWithViews() |
上記すべてのパターンに使用できる ※ただしパフォーマンスに影響がある |
AddMvc() |
Configure
メソッドはASP.net Core
のランタイムから呼ばれるメソッドでHTTPリクエストのパイプライン(どのようにHTTPリクエストに応答するか)を設定します。
画像引用元:多分わかりやすいASP.NET Core Webアプリケーション
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
DevelopモードならUseDeveloperExceptionPage()
メソッドでエラーをブラウザに出力し、そうでなければ、エラーページへ飛ばします。
app.UseHttpsRedirection();
すべての要求に対してHttpsを要求し、またHttp要求をHttpsにリダイレクトする。
app.UseStaticFiles();
静的ファイルを提供する。
これによって、wwwrootフォルダ以下の静的ファイル(Html、Css、画像、JavaScript等)をユーザーに提供できます。
またView側から呼び出す際には"~/"
に続けてパスを指定します。
app.UseRouting();
Webアプリに必須のルーティング機能を有効化します。
app.UseAuthorization();
AuthenticationMiddlewareを有効にするもの。
Cookieから認証情報を取り出してなんやかんやするようですが正直わかりません。。
今回のアプリには不要かも。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
ルートを定義しているのは内部のMapControllerRoute()
メソッドになります。
name
では設定を識別するための一意の名前。
pattern
ではURLパターンを示す文字列を入れています。
細かく見ていきます。
"{controller=Home}/{action=Index}/{id?}"
上記に関して、わかりやすく解説してくださった型がいたので引用してます。
controllerとactionは予約されている名前で、それぞれコントローラー名と
アクションメソッド名と紐づきます。
{id}にはアクションメソッドに渡される任意のパラメーターを設定することができます。従って~/Home/Index/5であればHomeControllerのIndexアクションメソッドを呼び出し、パラメーターとして5を渡す という意味になります。
引用元:ASP.NET Core MVC 3.1 入門 その4 「Routing」
また、=Home
等としているのはデフォルト値で、上記のような指定の仕方だと、
https://localhost:44383/
と https://localhost:44383/Home/Index
は同じ場所を見ていることになります。
HomeController.cs
やっと難しそうなところから解放されそうです。
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
このあたりに関しては実務で触っているのもあって比較的わかりそうです。
loggerに関しては後述します。
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
View()
というメソッドを返しているだけのメソッドです。
このView()
というメソッドが呼び出されると、用意してあるViewテンプレート(.cshtmlファイル)と、自身のメソッドから渡されるデータを組み合わせてHTMLを生成します。
ここでいう用意してあるViewテンプレートは、View()
に明示的に指定しない場合は、/Views/コントローラー名/アクションメソッド名.cshtml
に該当します。
つまり、Index()
のView()
に該当するViewテンプレートはViews/Home/Index.cshtml
、Privacy()
のView()
に該当するViewテンプレートはViews/Home/Privacy.cshtml
になります。
ここで先ほどのルーティングの話を思い出します
"{controller=Home}/{action=Index}/{id?}"
controllerとactionは予約されている名前で、それぞれコントローラー名とアクションメソッド名と紐づきます。
つまり、アプリのホーム画面を表示する際の流れとしては、
https://localhost:44383/
(=https://localhost:44383/Home/Index
)にアクセスする
↓
HomeControllerのIndexメソッドが呼び出され、View()
をreturnする。
↓
プロジェクトのViews/Home/Index.cshtmlを読み込み、Htmlを生成
という流れになります。
Viewテンプレート
わかりやすく解説してくださっている記事がありましたので、引用しながら進めていきます
下記引用元:ASP.NET Core MVC 3.1 入門 その5 「View」
上で出てきたViewのテンプレートですが、デフォルトで生成したソースだと下記のようになっています。
@{
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>
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
すこし見慣れない構文があります。
@{
ViewData["Title"] = "Home Page";
}
@(コードナゲット)はViewスクリプトにおいてC#のコードであることを示すキーワードとなります。
指定の仕方は上記の他に@ViewData["Message"]
のように1行で終わるものがあります。
ViewData
とここでは出ていませんがViewbag
というものがあり、これらをView変数と呼びます。
上記はControllerからViewにデータを渡す際に、ViewDataならDictionaryとして、ViewBagならプロパティとして値を設定します。
ViewBagを使うと、ViewDataのDictionaryベースの読みづらいコードを記述する必要はなくなりますが、DLRによって解釈されるコードを使いたくないのであればViewDataをおすすめします。
なぜViewBagとViewData二つあるのか疑問でしたが、どちらを使ってもシステム的には問題はないそうです。
特殊なViewテンプレート - _ViewStart.cshtml
上の2つのhtmlにはヘッダーやフッターの記述はありませんでした。
ではどこで定義しているかというと、、、
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - ToDoManage</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-area="" asp-controller="Home" asp-action="Index">ToDoManage</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">
© 2022 - ToDoManage - <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>
Sharedフォルダ内の_layout.cshtml
というファイルに記述がありました。
header、footerの記述や、各種スクリプトやcssの呼出まであることが確認できます。
ここに変更を加えることで一気に適応されます。
<main role="main" class="pb-3">
@RenderBody()
</main>
この@RenderBody()
という箇所に各Viewテンプレート(index.cshtml等)を埋め込んで出力しています。
@RenderBody()
の他にもrenderSection()
というものがあり、前者はテンプレートに一か所のみとなりますが、後者は複数記述できます。
ここではデフォルトで_layout.cshtml
という名前で、Views/Shared
配下に生成されていますが、必ずしもそうするべきではないそうで、
_layoutLogin.cshtml
、_layoutMaster.cshtml
などのように任意に決められるようです。
全体に適応するレイアウトは_ViewStart.cshmtml
に記述します。
@{
Layout = "_Layout";
}
ある条件によって適応するレイアウトを変更したい場合は
@{
var controller = HttpContext.Current.Request.RequestContext.RouteData.Values["Controller"].ToString();
string layout = "";
if (controller == "Admin")
{
layout = "~/Views/Shared/_AdminLayout.cshtml";
}
else
{
layout = "~/Views/Shared/_Layout.cshtml";
}
Layout = layout;
}
等のように記述するようです。
引用元:Layout を変更する4種類の方法(ASP.NET MVC)
特殊なViewテンプレート - _ViewImport.cshtml
上記の他にもう一つ特殊なテンプレートが生成されていました。
それが下記になります。
@using ToDoManage
@using ToDoManage.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
何をしているかというと、共通で使う名前空のインポート、またやインクルードをここで定義出来ます。
また、このテンプレートでは関数やセクションの定義等の機能はサポートしていません。
ここで定義したusing句などの効力は_ViewImport.cshtml
が配置してあるフォルダー、およびサブフォルダーに適応されます。
なので、各ページごとに定義したい場合には、~/Views/Login/_ViewImport.cshtml
のような形で配置すれば良いようです。
ちなみにデフォルトで宣言されている、
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
はTagHelperというものらしいです。
_layout.cshtml
の中をよく見ると、普通のhtmlには見慣れない者があったのに気づきました。
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
asp-
と名の付くタグは明らかにASP.net core
の者だとわかりますね。
これが、TagHelper
というものでrazorファイル(.cshtml)
にてhtmlの生成、レンダリング等が出来るものになります。
メリットとしては
-
<label for=@Model.Name>
のようなHtmlとC#の明示的な切り替えが減り可読性が上がること。 - 同じパーツとして使い回せるので生産性が上がること。
の2点でしょうか。
終わりに
さて、長々と書いてしまいましたが一旦ここで前半は締めて、後半では実際に手を動かして行きたいと思います。
読みづらい文章ですが、間違いなどございましたらご指摘いただけると幸いです。