演習 - Identity をカスタマイズする
前のユニットでは、ASP.NET Core Identity でのカスタマイズのしくみについて学習しました。 このユニットでは、Identity データ モデルを拡張し、対応する UI を変更します。
ユーザー アカウント データをカスタマイズする
このセクションでは、既定の Razor クラス ライブラリの代わりに使用する Identity UI ファイルを作成およびカスタマイズします。
変更するユーザー登録ファイルをプロジェクトに追加します。
dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail"
上記のコマンドでは次のことが行われます。
--dbContext
オプションでは、RazorPagesPizzaAuth
という名前の既存のDbContext
派生クラスに関する情報をツールに提供します。--files
オプションでは、Identity 領域に追加する一意のファイルのセミコロン区切りのリストを指定します。Account.Manage.Index
はプロファイル管理ページです。 このページは、このユニットの後半で変更されます。Account.Register
はユーザー登録ページです。 このページもこのユニットで変更されます。Account.Manage.EnableAuthenticator
とAccount.ConfirmEmail
はスキャフォールディングされますが、このユニットでは変更されません。
ヒント
--files
オプションの有効な値を表示するには、プロジェクトのルートから次のコマンドを実行します:dotnet aspnet-codegenerator identity --listFiles
。次のファイルが、Areas/Identity ディレクトリに追加されます。
- Pages/
- _ViewImports.cshtml
- Account/
- _ViewImports.cshtml
- ConfirmEmail.cshtml
- ConfirmEmail.cshtml.cs
- Register.cshtml
- Register.cshtml.cs
- Manage/
- _ManageNav.cshtml
- _ViewImports.cshtml
- EnableAuthenticator.cshtml
- EnableAuthenticator.cshtml.cs
- Index.cshtml
- Index.cshtml.cs
- ManageNavPages.cs
IdentityUser
の拡張
ユーザーの名前を保存するという新しい要件が与えられました。 既定の IdentityUser
クラスには姓と名のプロパティが含まれていないため、RazorPagesPizzaUser
クラスを拡張する必要があります。
FirstName
およびLastName
プロパティを追加します。using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Identity; namespace RazorPagesPizza.Areas.Identity.Data; public class RazorPagesPizzaUser : IdentityUser { [Required] [MaxLength(100)] public string FirstName { get; set; } = string.Empty; [Required] [MaxLength(100)] public string LastName { get; set; } = string.Empty; }
上記のスニペットのプロパティでは、基になる
AspNetUsers
テーブルに作成する追加の列が表されています。 どちらのプロパティも必須であるため、[Required]
属性で注釈が付けられています。 さらに、[MaxLength]
属性により、許容最大長が 100 文字であることが示されています。 それに応じて、基になるテーブル列のデータ型が定義されます。 このプロジェクトでは null 許容コンテキストが有効になっており、プロパティが null 許容でない文字列であるため、既定値string.Empty
が割り当てられます。ファイルの先頭に次の
using
ステートメントを追加します。using System.ComponentModel.DataAnnotations;
上記のコードでは、
FirstName
およびLastName
プロパティに適用されるデータ注釈属性が解決されます。
データベースを更新する
モデルの変更が行われたので、それに伴う変更をデータベースに対して行う必要があります。
すべての変更が保存されていることを確かめます。
EF Core の移行を作成して適用し、基になるデータ ストアを更新します。
dotnet ef migrations add UpdateUser dotnet ef database update
UpdateUser
EF Core の移行により、DDL 変更スクリプトがAspNetUsers
テーブルのスキーマに適用されました。 具体的には、次の移行出力の抜粋に示すように、FirstName
およびLastName
列が追加されました。info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (37ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] ALTER TABLE [AspNetUsers] ADD [FirstName] nvarchar(100) NOT NULL DEFAULT N''; info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (36ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] ALTER TABLE [AspNetUsers] ADD [LastName] nvarchar(100) NOT NULL DEFAULT N'';
データベースを調べ、
AspNetUsers
テーブルのスキーマに対するUpdateUser
EF Core の移行の影響を分析します。SQL Server ペインで、dbo.AspNetUsers テーブルの Columns ノードを展開します。
RazorPagesPizzaUser
クラスのFirstName
およびLastName
プロパティは、上の画像のFirstName
およびLastName
列に対応しています。[MaxLength(100)]
属性のため、nvarchar(100)
のデータ型が 2 つの列のそれぞれに割り当てられています。 クラス内のFirstName
およびLastName
が null 非許容文字列であるため、null 以外の制約が追加されました。 既存の行の新しい列には、空の文字列が表示されます。
ユーザー登録フォームをカスタマイズする
FirstName
と LastName
の新しい列を追加しました。 次は、登録フォームに一致するフィールドを表示するように UI を編集する必要があります。
Areas/Identity/Pages/Account/Register.cshtml に、次の強調表示されているマークアップを追加します。
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post"> <h2>Create a new account.</h2> <hr /> <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> <div class="form-floating mb-3"> <input asp-for="Input.FirstName" class="form-control" /> <label asp-for="Input.FirstName"></label> <span asp-validation-for="Input.FirstName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Input.LastName" class="form-control" /> <label asp-for="Input.LastName"></label> <span asp-validation-for="Input.LastName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" /> <label asp-for="Input.Email">Email</label> <span asp-validation-for="Input.Email" class="text-danger"></span> </div>
上記のマークアップでは、[First name](名) と [Last name](姓) のテキスト ボックスがユーザー登録フォームに追加されます。
Areas/Identity/Pages/Account/Register.cshtml.cs では、名前テキスト ボックスのサポートが追加されます。
FirstName
およびLastName
プロパティを入れ子になったInputModel
クラスに追加します。public class InputModel { [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "First name")] public string FirstName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "Last name")] public string LastName { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; }
[Display]
属性では、テキスト ボックスに関連付けられるラベル テキストが定義されています。OnPostAsync
メソッドを変更し、RazorPagesPizza
オブジェクトのFirstName
およびLastName
プロパティを設定します。 次の強調表示されている行を追加します。public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); user.FirstName = Input.FirstName; user.LastName = Input.LastName; await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); var result = await _userManager.CreateAsync(user, Input.Password);
上記の変更により、
FirstName
およびLastName
プロパティが登録フォームからのユーザー入力に設定されます。
サイト ヘッダーをカスタマイズする
ユーザー登録の間に収集された姓と名を表示するように、Pages/Shared/_LoginPartial.cshtml を更新します。 次のスニペットで強調表示されている行が必要です。
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
RazorPagesPizzaUser? user = await UserManager.GetUserAsync(User);
var fullName = $"{user?.FirstName} {user?.LastName}";
<li class="nav-item">
<a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello, @fullName!</a>
</li>
UserManager.GetUserAsync(User)
は Null 許容の RazorPagesPizzaUser
オブジェクトを返します。 null 条件 ?.
演算子は、RazorPagesPizzaUser
オブジェクトが null 値でない場合にのみ、FirstName
および LastName
プロパティにアクセスするために使用されます。
プロファイル管理フォームをカスタマイズする
ユーザー登録フォームに新しいフィールドを追加しましたが、既存のユーザーが編集できるようにプロファイル管理フォームにも追加する必要があります。
Areas/Identity/Pages/Account/Manage/Index.cshtml に、次の強調表示されているマークアップを追加します。 変更内容を保存します。
<form id="profile-form" method="post"> <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> <div class="form-floating mb-3"> <input asp-for="Input.FirstName" class="form-control" /> <label asp-for="Input.FirstName"></label> <span asp-validation-for="Input.FirstName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Input.LastName" class="form-control" /> <label asp-for="Input.LastName"></label> <span asp-validation-for="Input.LastName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Username" class="form-control" disabled /> <label asp-for="Username" class="form-label"></label> </div>
Areas/Identity/Pages/Account/Manage/Index.cshtml.cs で、名前テキスト ボックスをサポートするための次の変更を行います。
FirstName
およびLastName
プロパティを入れ子になったInputModel
クラスに追加します。public class InputModel { [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "First name")] public string FirstName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "Last name")] public string LastName { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Phone] [Display(Name = "Phone number")] public string PhoneNumber { get; set; } }
強調表示された変更を
LoadAsync
メソッドに組み込みます。private async Task LoadAsync(RazorPagesPizzaUser user) { var userName = await _userManager.GetUserNameAsync(user); var phoneNumber = await _userManager.GetPhoneNumberAsync(user); Username = userName; Input = new InputModel { PhoneNumber = phoneNumber, FirstName = user.FirstName, LastName = user.LastName }; }
上記のコードでは、プロファイル管理フォームの対応するテキスト ボックスに表示される姓と名の取得がサポートされています。
強調表示された変更を
OnPostAsync
メソッドに組み込みます。 変更を保存します。public async Task<IActionResult> OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) { return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } if (!ModelState.IsValid) { await LoadAsync(user); return Page(); } user.FirstName = Input.FirstName; user.LastName = Input.LastName; await _userManager.UpdateAsync(user); var phoneNumber = await _userManager.GetPhoneNumberAsync(user); if (Input.PhoneNumber != phoneNumber) { var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); if (!setPhoneResult.Succeeded) { StatusMessage = "Unexpected error when trying to set phone number."; return RedirectToPage(); } } await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your profile has been updated"; return RedirectToPage(); }
上記のコードでは、データベースの
AspNetUsers
テーブルの姓と名の更新がサポートされています。
確認メールの送信者を構成する
アプリを初めてテストするときに、ユーザーを登録し、リンクをクリックしてユーザーのメール アドレスの確認をシミュレートしました。 "実際の" 確認メールを送信するには、IEmailSender の実装を作成して依存関係挿入システムに登録する必要があります。 わかりやすくするために、このユニットの実装では、実際には簡易メール転送プロトコル (SMTP) サーバーにメールを送信しません。 メールの内容をコンソールに書き込むだけです。
コンソールではプレーン テキストでメールを表示するので、HTML エンコードされたテキストを除外するために生成されたメッセージを変更する必要があります。 Areas/Identity/Pages/Account/Register.cshtml.cs で、次のコードを見つけます。
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
次のように変更します。
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
エクスプローラー ペインで、RazorPagesPizza\Services フォルダーを右クリックし、EmailSender.cs という名前の新しいファイルを作成します。 そのファイルを開き、次のコードを追加します。
using Microsoft.AspNetCore.Identity.UI.Services; namespace RazorPagesPizza.Services; public class EmailSender : IEmailSender { public EmailSender() {} public Task SendEmailAsync(string email, string subject, string htmlMessage) { Console.WriteLine(); Console.WriteLine("Email Confirmation Message"); Console.WriteLine("--------------------------"); Console.WriteLine($"TO: {email}"); Console.WriteLine($"SUBJECT: {subject}"); Console.WriteLine($"CONTENTS: {htmlMessage}"); Console.WriteLine(); return Task.CompletedTask; } }
上記のコードでは、メッセージの内容をコンソールに書き込む IEmailSender の実装を作成します。 実世界の実装では、
SendEmailAsync
で外部メール サービスに接続するか、メールを送信する他のアクションを実行します。Program.cs で、強調表示されている行を追加します。
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using RazorPagesPizza.Areas.Identity.Data; using Microsoft.AspNetCore.Identity.UI.Services; using RazorPagesPizza.Services; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("RazorPagesPizzaAuthConnection"); builder.Services.AddDbContext<RazorPagesPizzaAuth>(options => options.UseSqlServer(connectionString)); builder.Services.AddDefaultIdentity<RazorPagesPizzaUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<RazorPagesPizzaAuth>(); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddTransient<IEmailSender, EmailSender>(); var app = builder.Build();
上のコードにより、
EmailSender
がIEmailSender
として依存関係挿入システムに登録されます。
登録フォームの変更をテストする
以上です。 登録フォームと確認メールの変更をテストしてみましょう。
すべての変更が保存済みであることを確認します。
ターミナル ペインで、プロジェクトをビルドし、
dotnet run
を使用してアプリを実行します。ブラウザーでアプリに移動します。 ログインしている場合は、[ログアウト] を選択します。
[Register](登録) を選択し、更新されたフォームを使用して新しいユーザーを登録します。
Note
[First name]\(名) フィールドと [Last name]\(姓) フィールドの検証制約には、
InputModel
のFirstName
およびLastName
プロパティのデータ注釈が反映されます。登録後、[登録確認] 画面にリダイレクトされます。 ターミナル ペインで、上にスクロールして、次のようなコンソール出力を見つけます。
Email Confirmation Message -------------------------- TO: jana.heinrich@contoso.com SUBJECT: Confirm your email CONTENTS: Please confirm your account by visiting the following URL: https://localhost:7192/Identity/Account/ConfirmEmail?<query string removed>
Ctrl キーを押しながら ''クリック'' して URL に移動します。 確認画面が表示されます。
Note
GitHub Codespaces を使っている場合は、必要に応じて転送される URL の最初の部分に
-7192
を追加します。 たとえば、「scaling-potato-5gr4j4-7192.preview.app.github.dev
」のように入力します。[ログイン] を選び、新しいユーザーでログインします。 アプリのヘッダーに [Hello, <名> <姓>!] が含まれるようになります。
VS Code の SQL Server ペインで、RazorPagesPizza データベースを右クリックし、[新しいクエリ] を選択します。 表示されるタブで、次のクエリを入力し、Ctrl+Shift+E キーを押して実行します。
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
タブに、次のような結果が表示されます。
UserName Email 名 LastName kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich 最初のユーザーは、スキーマに
FirstName
とLastName
を追加する前に登録されていました。 そのため、関連付けられているAspNetUsers
テーブルのレコードのこれらの列にはデータがありません。
プロファイル管理フォームに対する変更をテストする
プロファイル管理フォームに加えた変更もテストする必要があります。
Web アプリに、最初に作成したユーザーでサインインします。
[Hello, !] リンクを選んで、プロファイル管理フォームに移動します。
Note
このユーザーに対する
AspNetUsers
テーブルの行にはFirstName
およびLastName
の値が含まれていないため、リンクが正しく表示されません。[First name](名) と [Last name](姓) に有効な値を入力します。 [Save](保存) を選択します。
アプリのヘッダーが [Hello, <名> <姓>!] に更新されます。
アプリを停止するには、VS Code のターミナル ペインで Ctrl+C キーを押します。
まとめ
このユニットでは、カスタム ユーザー情報を格納するために Identity をカスタマイズしました。 確認メールもカスタマイズしました。 次のユニットでは、Identity で多要素認証を実装する方法について学習します。