.NET Core RazorPages/MVCでユーザ登録ページをカスタマイズする方法

スポンサーリンク
プログラミング

こんにちは、しゅんです。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)

ログインページ(Login)

この時点では新規ユーザーを登録し、ログインすることはできません。なぜなら、データベースが作成されていないからです。データベースの作成はPMC(パッケージマネージャーコンソール)からマイグレーションする必要があります。マイグレーションは後ほど解説しますので、現時点ではスルーしてください。

登録ページに追加プロパティを登録する方法

認証機能は簡単に実装することが出来ました。では登録ページを確認していきましょう。

デフォルトで作成される認証機能の登録ページ(Register)へアクセスしてみると右のように表示されます。

メールアドレスパスワードを入力する項目があります。しかし、名前や住所、電話番号といった情報を登録するようにカスタマイズしたい場合があると思います。

ここでは、その方法を解説します。

ASP.NET Core RazorPagesWebアプリケーションでのRazorページの登録

フォルダ内を確認する

登録やログインといった認証に関する処理はMicrosoft.AspNetCore.Identity名前空間に含まれています。そして、Register.cshtml / Login.cshtmlといったファイルはプロジェクト\Areas\Identityフォルダに保管されます。まずはフォルダを確認してみてください。

あれっと思うかもしれませんが、「Register.cshtml / Login.cshtml」といった登録ページやログインページに関するファイルはなく、「_ViewStart.cshtml」ファイルしかありません。デフォルトの認証機能を利用して実装する場合、デフォルトからカスタマイズするためには認証関連のファイルをスキャフォールディングする必要があります。
スキャフォールディング…Visual Studioがコードを自動生成する機能のこと
スキャフォールディングする前は内部処理で登録ページやログインページを表示していますが、今回デフォルトの登録画面を書き換える必要があるため、内部処理を行なうコード部分をスキャフォールディングして、直接処理を書き換えなければなりません。

Identityファイルのスキャフォールディング

Areasフォルダを右クリック→追加(D)→新規スキャフォールディングアイテム(F)…をクリックします。

下のようなダイアログが開くため、追加をクリックします。
※Nugetパッケージのインストールが開始され、以下のエラーが発生するかもしれません。その場合は再度、上記の手順を実行してください。2度目は成功すると思います。
次に「IDの追加」ダイアログが表示されます。Account\Registerにチェックを付け、データコンテキストクラスのドロップダウンからApplicationDbContextを選択し、追加を押します。
オーバーライドするファイルを選択することで認証機能に関わるページを生成することができます。例えば、Account\ForgotPasswordではパスワードを忘れた方の再設定メールを送信するページを生成し、カスタマイズできます。
※追加を押すと、またもや以下のようなエラーが発生するかもしれませんが、こちらも同様に追加を再度クリックすることで2度目はスキャフォールディングに成功すると思います。
スキャフォールディングに成功すると、以下のようなファイルが生成されました。
これで、登録ページをカスタマイズする準備が整いました。

登録ページのカスタマイズ

では、登録ページをカスタマイズしていきます。前述の通り、デフォルトの登録機能には、メールアドレスとパスワードのフィールドしかありません。ここに、名前や年齢、住所、部署名など、様々な項目を追加できるようにカスタマイズしていきます。例では、名前と年齢のフィールドを追加します。
ASP.NET Core RazorPagesWebアプリケーションでのRazorページの登録
結論から言うと、登録ページの項目をカスタマイズするには以下の手順を踏む必要があります。
  1. IdentityUserを継承したモデルクラスを追加する
  2. モデルをDBに反映させる
  3. 登録ページに追加するプロパティの入力欄と内部処理を追記する
  4. 継承したモデルクラスを利用するように各所変更する
追加プロパティをデータベース内に追加するには、すでに実装されているIdentityUserを拡張し、そこに新たなプロパティを追加する必要があります。

したがって、新しいプロパティを作成するために、新しいモデルクラスを追加する必要があります。

モデルクラスを追加する

まず、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; } 
 } 
} 
このApplicationUserクラスはIdentityUserを継承しています。継承されたApplicationUserクラス内に、Name、Ageの2つのプロパティを追加します。

モデルを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 { getset; }
  }
}
ApplicationDbContextクラス内で、ApplicationUser用の新しいDbSetを作成する必要があります。DbSetによりマイグレーション実行時にIdentityUserを継承したApplicationUserのプロパティを引き継いだUserテーブルが作成されるようになります。
マイグレーション(Migration)….NET CoreにおけるマイグレーションとはDBを作成、更新、削除などのクエリ文をEF Coreが自動生成してくれる機能のこと。PMC(パッケージマネージャーコンソール)で「Add-Migration xxxx(マイグレーション名)」と入力する。マイグレーションした段階ではDBの更新はされないので、その後に「Update-Database」を実行することでDBに結果が反映される。
ツール(T)→NuGetパッケージマネージャー(N)→パッケージマネージャーコンソール(O)をクリックし、マイグレーションを実行します。
Add-Migration CustomizeUserTable

To undo this action, use Remove-Migration.

上記のようなメッセージが表示されていたら、成功です。新しく作成されたマイグレーションファイルを調べると、名前、年齢などの列がAspNetUsersテーブルに追加されていることがわかります。

続けて、データベース内で変更を保存する必要があります。データベースを更新するには、パッケージマネージャーコンソールで次のコマンドを入力します。

Update-Database

Done. 

DBの更新に成功していたら、上記のメッセージが返されます。DBが更新されているか、確認しましょう。表示(V)→SQLServer オブジェクトエクスプローラーを開きます。

SQL Server→(localdb)XXXX→データベース→aspnet-HelloRazorProject-→テーブル→dbo.AspNetUsersを右クリックし、デザイナーの表示を選択します。

NameとAge、2つの新しいプロパティがテーブル内に追加されていることがわかります。

DBに登録データを保存する準備が出来たので、残るはエンドユーザーが入力する入力欄とDBへ保存する内部処理を追記するだけです。

登録ページに追加するプロパティの入力欄と内部処理を追記する

登録ページの処理が記述されている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" />
}

継承したモデルクラスを利用するように各所変更する

続けて、Program.csを開き、IdentityUserからApplicationUserを認証に利用するように処理を変更します。
using HelloRazorProject.Models;//using文を追加

//IdentityUserをApplicationUserに
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
  .AddEntityFrameworkStores<ApplicationDbContext>();
//
Pages→Shared→_LoginPartial.cshtmlを開き、ヘッダーのRegisterボタンとLoginボタンもApplicationUserを利用するように切り替えます。
@using HelloRazorProject.Models
@using Microsoft.AspNetCore.Identity
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
最後に、アプリを実行してみましょう。登録ページにアクセスします。
NameとAgeが追加されていることが分かると思います。登録し、ログインページでログイン出来るか、確認してみましょう。
ログイン後は以下の画面になります。
以上で、登録ページのカスタマイズは終了です。

まとめ

.NET Core RazorPageで認証機能を実装する方法は、プロジェクト作成時のダイアログで設定してください。また、登録ページのカスタマイズは以下の手順を踏みます。

  1. IdentityUserを継承したモデルクラスを追加する
  2. モデルをDBに反映させる
  3. 登録ページに追加するプロパティの入力欄と内部処理を追記する
  4. 継承したモデルクラスを利用するように各所変更する
以上で認証機能の実装、登録ページのカスタマイズに関する解説は終了です。登録時に管理者権限を持ったユーザー追加したいという場合は下の記事を参考にしてみてください!
ここまで読んでいただき、ありがとうございました!

コメント

タイトルとURLをコピーしました