こんにちは、しゅんです。C#の学習をはじめて1年弱ですが、最近、ASP.NET Coreを学び始めました。その際、ユーザ登録時に名前/電話番号/住所/部署といった追加情報を実装したい、と思い立ち、色々調べたものの備忘録です。
今回の実装例では、RazorPages プロジェクトテンプレートを使用します。Razor Pagesではなく、.NET Core MVCのユーザ登録ページをカスタマイズする場合でも、やり方は変わらないため、ご安心ください。
▼前提&開発環境▼
- C#の文法に関する解説はありません
- IDE:Visual Studio2022
- Framework:.NET 6.0
- DB:SQLServer
▼対象読者▼
- .NET Core Razor Pages/MVCを勉強している初学者
▼当記事を読み終わると出来ること▼
- Razor Pages/MVCに認証機能を実装できる
- 認証に独自の登録情報を追加できる
では、ASP.NET Core RazorPagesのプロジェクトにデフォルトの認証機能を実装する方法から見ていきましょう。
目次
認証機能を実装したプロジェクトの作成
.NET CoreにはIdentity(認証)機能がデフォルトで用意されています。認証機能を利用するようにダイアログを選択することで簡単に実装できます。
早速RazorPagesプロジェクトを作成してみましょう!
RazorPagesプロジェクト作成
「ASP.NET Core Webアプリ」を選択し、「次へ」を押します。
プロジェクト名と作成場所はどこでも大丈夫です。が、ここではプロジェクト名「HelloRazorProject」とし、「次へ」を押します。
認証の種類の追加
認証の種類の設定で「個別のアカウント」を選択します。これにより、プロジェクトテンプレートへ認証機能が実装された状態で、プロジェクト作成が行われます。
これで認証機能の設定は完了です!「▶HelloRazorProject」かF5でアプリを実行してください。ログイン認証機能が実装されています。登録ページ(Register)やログインページ(Login)をクリックし、表示を確認してみてください。
登録ページに追加プロパティを登録する方法
認証機能は簡単に実装することが出来ました。では登録ページを確認していきましょう。
デフォルトで作成される認証機能の登録ページ(Register)へアクセスしてみると右のように表示されます。
メールアドレスとパスワードを入力する項目があります。しかし、名前や住所、電話番号といった情報を登録するようにカスタマイズしたい場合があると思います。
ここでは、その方法を解説します。
フォルダ内を確認する
登録やログインといった認証に関する処理はMicrosoft.AspNetCore.Identity名前空間に含まれています。そして、Register.cshtml / Login.cshtmlといったファイルはプロジェクト\Areas\Identityフォルダに保管されます。まずはフォルダを確認してみてください。
Identityファイルのスキャフォールディング
Areasフォルダを右クリック→追加(D)→新規スキャフォールディングアイテム(F)…をクリックします。


登録ページのカスタマイズ
- IdentityUserを継承したモデルクラスを追加する
- モデルをDBに反映させる
- 登録ページに追加するプロパティの入力欄と内部処理を追記する
- 継承したモデルクラスを利用するように各所変更する
したがって、新しいプロパティを作成するために、新しいモデルクラスを追加する必要があります。
モデルクラスを追加する
まず、Modelsフォルダを作成します。ソリューションエクスプローラーのプロジェクト名を右クリック→追加(D)→新しいフォルダーをクリックします。フォルダ名は「Models」としておきます。
次に、Modelsフォルダを右クリック→追加(D)→クラス(C)…をクリックして、「ApplicationUser」という名前で追加します。
クラス内部は以下のように記述してください。
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace HelloRazorProject.Models
{
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public int Age { get; set; }
}
}
モデルをDBに反映する
次に、これらの変更を反映したDBを作成する必要があります。したがって、Migrationフォルダー内にデフォルトで作成されたApplicationDbContextクラス内で変更を加える必要があります。
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using HelloRazorProject.Models;
namespace HelloRazorProject.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{
}
public DbSet<ApplicationUser> ApplicationUser { get; set; }
}
}
To undo this action, use Remove-Migration.
上記のようなメッセージが表示されていたら、成功です。新しく作成されたマイグレーションファイルを調べると、名前、年齢などの列がAspNetUsersテーブルに追加されていることがわかります。
続けて、データベース内で変更を保存する必要があります。データベースを更新するには、パッケージマネージャーコンソールで次のコマンドを入力します。
Done.
DBの更新に成功していたら、上記のメッセージが返されます。DBが更新されているか、確認しましょう。表示(V)→SQLServer オブジェクトエクスプローラーを開きます。
SQL Server→(localdb)XXXX→データベース→aspnet-HelloRazorProject-→テーブル→dbo.AspNetUsersを右クリックし、デザイナーの表示を選択します。
NameとAge、2つの新しいプロパティがテーブル内に追加されていることがわかります。
登録ページに追加するプロパティの入力欄と内部処理を追記する
登録ページの処理が記述されているHelloRazorProject→Areas→Identity→Pages→Account→Register.cshtml→Register.cshtml.csファイルを開きます。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using HelloRazorProject.Models;
namespace HelloRazorProject.Areas.Identity.Pages.Account
{
public class RegisterModel : PageModel
{
//IdentityUserをApplicationUserに書き換え
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IUserStore<ApplicationUser> _userStore;
private readonly IUserEmailStore<ApplicationUser> _emailStore;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
//IdentityUserをApplicationUserに書き換え
public RegisterModel(
UserManager<ApplicationUser> userManager,
IUserStore<ApplicationUser> userStore,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_userStore = userStore;
_emailStore = GetEmailStore();
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
//以下の処理を追記
[Required]
public string Name { get; set; }
public int Age { get; set; }
}
}
}
登録フォームの送信メソッドOnPostAsync()でもNameとAgeを保存するように処理を追記します。
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid){
//var user = CreateUser();
var user = new ApplicationUser
{
Email = Input.Email,
Name = Input.Name,
Age = Input.Age
};
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return Page();
}
Register.cshtmlで登録フォームに名前と年齢欄を追記します。
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<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"></div>
//---以下の処理を追記---
<div class="form-floating">
<input asp-for="Input.Name" class="form-control" autocomplete="username" aria-required="true" />
<label asp-for="Input.Name"></label>
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Age" class="form-control" autocomplete="username" aria-required="true" />
<label asp-for="Input.Age"></label>
<span asp-validation-for="Input.Age" class="text-danger"></span>
</div>
//---ここまで---
<div class="form-floating">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" />
<label asp-for="Input.Email"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" />
<label asp-for="Input.Password"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" />
<label asp-for="Input.ConfirmPassword"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h3>Use another service to register.</h3>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
継承したモデルクラスを利用するように各所変更する
using HelloRazorProject.Models;//using文を追加
//IdentityUserをApplicationUserに
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
//
@using HelloRazorProject.Models
@using Microsoft.AspNetCore.Identity
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
まとめ
.NET Core RazorPageで認証機能を実装する方法は、プロジェクト作成時のダイアログで設定してください。また、登録ページのカスタマイズは以下の手順を踏みます。
- IdentityUserを継承したモデルクラスを追加する
- モデルをDBに反映させる
- 登録ページに追加するプロパティの入力欄と内部処理を追記する
- 継承したモデルクラスを利用するように各所変更する
コメント