LoginSignup
4
11

More than 1 year has passed since last update.

ASP.NET Coreを使ったToDoアプリの作成(前半)

Last updated at Posted at 2022-05-30

はじめに

調べすぎて記事内がリンクだらけになっていますがご容赦ください。
また、間違い等ございましたらご指摘いただけると幸いです。
記事が長いなと思ったので、

  • 解説、調査の前半
  • 実際の作成過程の後半
    の構成にしたいと思います

前半概要

  • 実務でAsp.netを使用しているが、いまいち理解していないことが多いので自分で一から作ってみる。
  • Web系の仕事をするためにも必要な知識を高めたい

プロジェクト作成

画像1.png

VisualStudioでテンプレートを作成します
MVCで作成したいのでMVCテンプレートを選びます。

画像2.png
「Razorランタイムコンパイルを有効にする」にはチェックを入れています。
Razorランタイムコンパイルとはアプリの実行時にRazor拡張子(.cshtml)を編集するとリアルタイムで変更が反映されるものっぽいです。

Razor ファイルのビルド時および発行時のコンパイルは、既定で Razor SDK によって有効になっています。
有効になっていると、実行時コンパイルはビルド時のコンパイルを補完し、編集された Razor ファイルを更新できます。

引用:ASP.NET Core での Razor ファイルのコンパイル

いざ作成!

画像3.png

いっぱいファイルが作成されました。。。

とりあえず今の状態で起動してみるとどうなるのでしょうか?

起動確認

画像4.png

おお!なんだかそれっぽいようなサイトがすでに出来上がっています。
ご丁寧にプライバシーポリシーまで用意されています。
Webサービスを作りたいならここから少し手を加えるだけで公開できそうな感じがしています。

さて、次にソースを紐解いていきます。

ソースの確認

順番に見ていきます。

各種設定ファイル

launchSetting.jsonappsetting.jsonが作成されています。
launchSetting.jsonはアプリの起動設定、appsetting.jsonはアプリの設定ファイルになります。

appsetting.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}
launchSetting.json
{
  "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つの違いは、dotnetRunMessagesapplicationUrlの設定の有無だけです。
dotnetRunMessagesの項目が公式で文書化されていませんでしたが、この記事を見るにおそらく内部コンソールにフィードバックを表示するかしないか。。。でしょうか?

その他の項目については以下で解説されていましたので参考にさせていただきます。
参考:ASP.NET Core と launchSettings.json と appSettings.json

Program.cs

アプリを起動すると、Program.csのエントリポイントMainメソッドから始まります。
C#ではMainという名前のメソッドがエントリーポイントになります。

Program.cs
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>()のジェネリクスで指定していたクラスでアプリの動作を定義するクラスになります。

Startup.cs
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

やっと難しそうなところから解放されそうです。

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.cshtmlPrivacy()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のテンプレートですが、デフォルトで生成したソースだと下記のようになっています。

Index.cshtml
@{
    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>
Privacy.cshtml
@{
    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にはヘッダーやフッターの記述はありませんでした。
ではどこで定義しているかというと、、、

_layout.cshtml
<!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">
            &copy; 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に記述します。

_ViewStart.cshtml
@{
    Layout = "_Layout";
}

ある条件によって適応するレイアウトを変更したい場合は

_ViewStart.cshtml
@{
    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

上記の他にもう一つ特殊なテンプレートが生成されていました。
それが下記になります。

_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点でしょうか。

終わりに

さて、長々と書いてしまいましたが一旦ここで前半は締めて、後半では実際に手を動かして行きたいと思います。

読みづらい文章ですが、間違いなどございましたらご指摘いただけると幸いです。

4
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
11