情報応用演習Ⅰ(2024)

【T8b】ASP.NET Core Identity の基礎(7/9)

プロジェクトタイプ(注意: 本文参照)
プロジェクト名T8b
ソリューション名PIT8
注意
  • 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
    • 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
    • プロジェクトの作成作業については準備作業を参照せよ.

8b-7. ログイン/ログアウトを処理するためのコントローラー

次に本題であるAdminコントローラーを作成しよう.プロジェクト内の Controllers フォルダを右クリックし「追加」→「コントローラー」をクリックしよう. 「MVC コントローラー - 空」を選択して「追加」ボタンをクリックし,「名前」にAdminController と指定する( .cs は省略可能). AdminController.cs が追加されるので_のように書き換えよう.

AdminControllerクラスの変更内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity; // 追記
using T8b.Models;                    // 追記

namespace T8b.Controllers
{
    public class AdminController : Controller
    {
        private readonly UserManager<IdentityUser> _userManager;      // ユーザーマネージャー(ユーザー管理用のクラス)
        private readonly SignInManager<IdentityUser> _signInManager;  // サインインマネージャー(サインイン/アウト管理用のクラス)

        public AdminController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) 
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }

        // Indexアクション(GETのみ)
        public IActionResult Index()
        {
            return RedirectToAction(nameof(Login)); // Loginアクションにリダイレクト
        }

        // Loginアクション(GET用)
        public IActionResult Login() 
        {
            return View(new LoginUserInfo());
        }

        // Loginアクション(POST用)
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(LoginUserInfo loginUserInfo) 
        {
            if (ModelState.IsValid) 
            {
                // 与えられたユーザー名とパスワードでサインインを試みる.
                var result = await _signInManager.PasswordSignInAsync(loginUserInfo.Username, loginUserInfo.Password, false, false);

                if (result.Succeeded)                             // サインイン成功の場合は,
                    return RedirectToAction("Index", "Home");     // /Home/Index にリダイレクト
                                                                  //
                else                                              // サインイン失敗の場合は,
                    ViewBag.Message = "ログインに失敗しました."; // エラーメッセージを ViewBag に入れる.
                                                                  //
            }

            return View(loginUserInfo);
        }

        // Logoutアクション(POSTのみ)
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Logout() 
        {
            // 現在ログイン中のユーザーをサインアウトさせる
            await _signInManager.SignOutAsync();
            
            return RedirectToAction("Index", "Home"); // /Home/Index にリダイレクト
        }

        // AccessDeniedアクション(GETのみ)
        public IActionResult AccessDenied() 
        {
            return View();
        }
    }
}

ログイン/ログアウトの処理にはSignInManager(さいん・いん・まねーじゃー)クラス のインスタンスが必要である.ASP.NET Core ではコンストラクタ引数でコントローラー内で必要となるクラスを明示することによって, 前述した DI の仕組みがこれらのクラスのオブジェクトを作成して渡してくれるようになる.

実際のログイン処理を行っているのは38行目でSignInManager<TUser>クラスの.PasswordSignInAsync()メソッドに ユーザー名とパスワードを渡して認証を試みている.ログアウトの処理は57行目で.SignOutAsync()メソッドを呼び出すことで 現在のセッション中でログインしているユーザーをログアウト状態にすることができる.

つぎにこれらのアクションのためのビューを作成しよう.図8b-6-1に示したように,Adminコントローラーは, IndexLoginLogoutAccessDeniedの4つのアクションをもつがこのうちビューを持つのはLoginAccessDeniedの二つのみである. また前述したようにログイン中はすべてのページの「ログアウト」ボタンが表示されるようにするため,すべてのビューの大枠となっている _Layout.cshtml にも手を加える.

最初にLoginアクションのためのビューを作成しよう.手動で .cshtml を作成して適切な場所に配置するか, あるいはVisual Studio の機能を使ってRazorビューを作成しよう. Views/Admin/Login.cshtml を_のように記述する.

Views/Admin/Login.cshtmlの内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@model LoginUserInfo

@{
    ViewData["Title"] = "管理者ログイン";

    if (Model is null) throw new ArgumentNullException(nameof(Model)); // ad-hoc! 非null保証のための回避策
}

<form asp-action="Login">
    @* ユーザー名(Username)のための入力欄 *@
    <label asp-for="@Model.Username"></label>    <input asp-for="@Model.Username" />
    <br />

    @* パスワード(Password)のための入力欄 *@
    <label asp-for="@Model.Password"></label>    <input asp-for="@Model.Password" />
    <br />

    <input type="submit" value="ログイン" />
    <br />
    <div asp-validation-summary="All"></div> @* エラーは集約表示する *@
</form>

<p>@ViewBag.Message</p>

@section Scripts
{
    @{ await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ここでは特筆すべことはない.モデルクラスとしてリスト8b-6-2LoginUserInfoを指定しており,それをフォームで入力させているのみである. 次にAccessDeniedアクションのためのビューを作成しよう. Views/Admin/AccessDenied.cshtml を_のように記述する.

Views/Admin/AccessDenied.cshtmlの内容
1
2
3
@{
    ViewData["Title"] = "アクセスが拒否されました";
}

ここも特筆すべことはない.ViewDataを通じてページのタイトルをセットしているだけである.

つぎにログインしているあいだだけ, すべてのページに 「ログアウト」ボタンが表示されるようにしてみよう. このためのもっとも単純な方法は,すべてのビューの大枠となっている _Layout.cshtml に手を加えることである. ただし,ログイン済みかどうかといった判定を含む処理を直接書き込むと見通しが悪くなるため, ファイルを分割することにしよう . ASP.NET Core の Razorページには一つのビューの内容に別のファイルに記述したビューの内容を読み込む パーシャルビュー と呼ばれる機能がある.今回はこれを利用してみよう.

パーシャルビューは .cshtml ファイル内に,別の .cshtml ファイルに記述した内容を挿入する機能である. これを利用するには .cshtml ファイルで_に示す パーシャルタグヘルパーを 使用する.

<partial name="ファイル名" />
パーシャルタグヘルパーの記法

ファイル名で指定したファイルは,これを記述した .cshtml ファイルと同じフォルダや Views/Shared フォルダなどから検索される. 通常は拡張子は省略できる.このファイル検索の詳細に関してはPartial views in ASP.NET Core - Microsoft Docsを参照せよ.

まずはログイン済みかどうかといった判定を含む .cshtml ファイル(=読み込まれる側)を作成しよう. Views/Shared フォルダを右クリックし「追加」→「ビュー」で 「Razorビュー - 空」 を選択する. ファイル名は_LoginPartial.cshtml にして,_の内容を書き込もう.

Views/Shared/_LoginPartial.cshtmlの内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@using Microsoft.AspNetCore.Identity;

@inject SignInManager<IdentityUser> signInManager
@inject UserManager<IdentityUser> userManager

@if (signInManager.IsSignedIn(User))
{
    <p>@userManager.GetUserName(User) としてログイン中</p>
    <form asp-controller="Admin" asp-action="Logout">
        <input type="submit" value="ログアウト" />
    </form>
}

ここではビュー内で直接サインインマネージャーやユーザーマネージャーを使用するため 3~4行目で@injectディレクティブを使用している.これは_Adminコントローラーの コンストラクタで,使用するクラスのオブジェクトを明示しているのと同じく DI の仕組みを使ってこれらのオブジェクトを 使用可能にするための記述である.

6行目ではまずサインインマネージャーの.IsSignedIn()メソッドを使用してサインイン済みかどうかをチェックしている. サインイン済みである場合は,8行目でユーザーマネージャーからログイン中のユーザー名を取得して表示し, 9~11行目でAdminコントローラーのLogoutアクションにPOSTするためのフォームを表示している.

これで「読み込まれる側」ができたので, _Layout.cshtml でこの _LoginPartial.cshtml を読み込もう. Views/Shared/_Layout.cshtml に_に示す内容を追記する.

Views/Shared/_Layout.cshtmlの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - T8b</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</head>
<body>
    @* ① ヘッダー *@
    <header>
        <h1>チュートリアルT8b</h1>
        <partial name="_LoginPartial" />
    </header>
 
    @* ② ナビゲーション *@
    <nav>
        @* いまのところは空にしておく *@
    </nav>
 
    @* ③ メインコンテンツ *@
    <main>
        <h2>@ViewData["Title"]</h2>
        @RenderBody()
    </main>
 
    @* ④ フッター *@
    <footer>
        &copy; @DateTime.Today.Year - Iryo Taro
    </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>

①ヘッダーの部分に,_で示したパーシャルタグヘルパーを使用して _の _LoginPartial.cshtml ファイルを読み込んでいる.

ここまで書けたら実行してみよう.プロジェクトを起動してURLの末尾を/Admin/Loginに変更してログインのためのページにアクセスする(_). するとログイン画面が表示されるので,ユーザー名にはリスト8b-5-1で指定したadmin,パスワードには リスト8b-4-3で指定したp@55W0rDを入力して「ログイン」ボタンをクリックする(_). するとHomeコントローラーのIndexアクションに遷移して,_で作成したログイン済みの場合に表示される部分が 全てのページに表示されるようになる(_).どのページからでもよいので「ログアウト」ボタンをクリックすると, 同じくHomeコントローラーのIndexアクションに遷移してログイン済みの表示が消える(_). 何度かログインとログアウトを繰り返して正常に動作することを確認しておこう. また終了する前に忘れずにログアウトしておこう

実行結果
Last updated on 2024-06-10
Published on 2024-06-10

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.