ASP.NET Core プロジェクトにおける Identity へのカスタム ユーザーデータの追加、ダウンロードおよび削除Add, download, and delete custom user data to Identity in an ASP.NET Core project

作成者: Rick AndersonBy Rick Anderson

この記事では方法。This article shows how to:

  • ASP.NET Core web アプリにカスタム ユーザー データを追加します。Add custom user data to an ASP.NET Core web app.
  • カスタムユーザーデータモデルをPersonalDataAttribute属性で装飾して、自動的にダウンロードおよび削除できるようにします。Decorate the custom user data model with the PersonalDataAttribute attribute so it's automatically available for download and deletion. データをダウンロードして、削除することを行うには、満たす助けとなるGDPR要件。Making the data able to be downloaded and deleted helps meet GDPR requirements.

プロジェクト サンプルは、Razor ページ web アプリから作成されますが、手順は ASP.NET Core MVC web アプリと同様。The project sample is created from a Razor Pages web app, but the instructions are similar for a ASP.NET Core MVC web app.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

必須コンポーネントPrerequisites

.NET core 2.2 SDK またはそれ以降.NET Core 2.2 SDK or later

Razor Web アプリの作成Create a Razor web app

  • Visual Studio の [ファイル] メニューから、 [新規作成] > [プロジェクト] の順に選択します。From the Visual Studio File menu, select New > Project. プロジェクトに名前をWebApp1にする場合の名前空間と一致、サンプルをダウンロードコード。Name the project WebApp1 if you want to it match the namespace of the download sample code.
  • 選択ASP.NET Core Web アプリケーション > OKSelect ASP.NET Core Web Application > OK
  • ドロップダウンでASP.NET Core 2.2を選択します。Select ASP.NET Core 2.2 in the dropdown
  • 選択Web アプリケーション > OKSelect Web Application > OK
  • プロジェクトをビルドして実行します。Build and run the project.

Identity のスキャフォールディングの実行Run the Identity scaffolder

  • ソリューション エクスプ ローラー、プロジェクトを右クリックして >追加 > スキャフォールディングされた新しい項目します。From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 左側のウィンドウから、スキャフォールディングの追加ダイアログ ボックスで、 Identity > 追加します。From the left pane of the Add Scaffold dialog, select Identity > ADD.
  • ADD アイデンティティダイアログ ボックスで、次のオプション。In the ADD Identity dialog, the following options:
    • 既存のレイアウト ファイルを選択して ~/Pages/Shared/_Layout.cshtmlSelect the existing layout file ~/Pages/Shared/_Layout.cshtml
    • オーバーライドする次のファイルを選択します。Select the following files to override:
      • アカウントまたは登録Account/Register
      • アカウント/管理/インデックスAccount/Manage/Index
    • 選択、 + 新たに作成するボタンデータ コンテキスト クラスします。Select the + button to create a new Data context class. 型を受け入れる (WebApp1.Models.WebApp1Context場合は、プロジェクトの名前はWebApp1)。Accept the type (WebApp1.Models.WebApp1Context if the project is named WebApp1).
    • 選択、 + 新たに作成するボタンユーザー クラスします。Select the + button to create a new User class. 型を受け入れる (WebApp1User場合は、プロジェクトの名前はWebApp1) >追加します。Accept the type (WebApp1User if the project is named WebApp1) > Add.
  • 選択追加します。Select ADD.

指示に従って、移行、UseAuthentication、およびレイアウト次の手順を実行します。Follow the instruction in Migrations, UseAuthentication, and layout to perform the following steps:

  • 移行を作成し、データベースを更新します。Create a migration and update the database.
  • UseAuthenticationStartup.Configure を追加します。Add UseAuthentication to Startup.Configure.
  • 追加<partial name="_LoginPartial" />レイアウト ファイルにします。Add <partial name="_LoginPartial" /> to the layout file.
  • アプリをテストします。Test the app:
    • ユーザーを登録するRegister a user
    • 新しいユーザー名を選択します (次に、ログアウトリンク)。Select the new user name (next to the Logout link). ウィンドウを拡大またはユーザー名とその他のリンクを表示するナビゲーション バーのアイコンを選択する必要があります。You might need to expand the window or select the navigation bar icon to show the user name and other links.
    • 選択、個人データタブ。Select the Personal Data tab.
    • 選択、ダウンロードボタンをクリックし、調査、 PersonalData.jsonファイル。Select the Download button and examined the PersonalData.json file.
    • テスト、削除ボタンで、ユーザーのログオンを削除します。Test the Delete button, which deletes the logged on user.

Identity DB へのカスタム ユーザーデータの追加Add custom user data to the Identity DB

更新プログラム、IdentityUserカスタム プロパティを持つクラスを派生します。Update the IdentityUser derived class with custom properties. ファイルの名前は WebApp1 プロジェクトの名前を付けた場合Areas/Identity/Data/WebApp1User.csします。If you named the project WebApp1, the file is named Areas/Identity/Data/WebApp1User.cs. 次のコード ファイルを更新します。Update the file with the following code:

using Microsoft.AspNetCore.Identity;
using System;

namespace WebApp1.Areas.Identity.Data
{
    public class WebApp1User : IdentityUser
    {
        [PersonalData]
        public string Name { get; set; }
        [PersonalData]
        public DateTime DOB { get; set; }
    }
}

修飾されたプロパティ、 PersonalData属性には。Properties decorated with the PersonalData attribute are:

  • 削除されたときに、 Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml Razor ページを呼び出してUserManager.Deleteします。Deleted when the Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml Razor Page calls UserManager.Delete.
  • によってデータのダウンロードに含まれる、 Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml Razor ページ。Included in the downloaded data by the Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml Razor Page.

Account/Manage/Index.cshtml ページの更新Update the Account/Manage/Index.cshtml page

更新プログラム、InputModelAreas/Identity/Pages/Account/Manage/Index.cshtml.cs次のようにコードを強調表示されます。Update the InputModel in Areas/Identity/Pages/Account/Manage/Index.cshtml.cs with the following highlighted code:

public partial class IndexModel : PageModel
{
    private readonly UserManager<WebApp1User> _userManager;
    private readonly SignInManager<WebApp1User> _signInManager;
    private readonly IEmailSender _emailSender;

    public IndexModel(
        UserManager<WebApp1User> userManager,
        SignInManager<WebApp1User> signInManager,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
    }

    public string Username { get; set; }
    public bool IsEmailConfirmed { get; set; }

    [TempData]
    public string StatusMessage { get; set; }

    [BindProperty]
    public InputModel Input { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Full name")]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Birth Date")]
        [DataType(DataType.Date)]
        public DateTime DOB { get; set; }

        [Required]
        [EmailAddress]
        public string Email { get; set; }

        [Phone]
        [Display(Name = "Phone number")]
        public string PhoneNumber { get; set; }
    }

    public async Task<IActionResult> OnGetAsync()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        var userName = await _userManager.GetUserNameAsync(user);
        var email = await _userManager.GetEmailAsync(user);
        var phoneNumber = await _userManager.GetPhoneNumberAsync(user);

        Username = userName;

        Input = new InputModel
        {
            Name = user.Name,
            DOB = user.DOB,
            Email = email,
            PhoneNumber = phoneNumber
        };

        IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);

        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        var email = await _userManager.GetEmailAsync(user);
        if (Input.Email != email)
        {
            var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
            if (!setEmailResult.Succeeded)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
            }
        }

        if (Input.Name != user.Name)
        {
            user.Name = Input.Name;
        }

        if (Input.DOB != user.DOB)
        {
            user.DOB = Input.DOB;
        }

        var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
        if (Input.PhoneNumber != phoneNumber)
        {
            var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
            if (!setPhoneResult.Succeeded)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
            }
        }

        await _userManager.UpdateAsync(user);

        await _signInManager.RefreshSignInAsync(user);
        StatusMessage = "Your profile has been updated";
        return RedirectToPage();
    }

    public async Task<IActionResult> OnPostSendVerificationEmailAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }


        var userId = await _userManager.GetUserIdAsync(user);
        var email = await _userManager.GetEmailAsync(user);
        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var callbackUrl = Url.Page(
            "/Account/ConfirmEmail",
            pageHandler: null,
            values: new { userId = userId, code = code },
            protocol: Request.Scheme);
        await _emailSender.SendEmailAsync(
            email,
            "Confirm your email",
            $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

        StatusMessage = "Verification email sent. Please check your email.";
        return RedirectToPage();
    }
}

更新プログラム、 Areas/Identity/Pages/Account/Manage/Index.cshtmlを次の強調表示されているマークアップ。Update the Areas/Identity/Pages/Account/Manage/Index.cshtml with the following highlighted markup:

@page
@model IndexModel
@{
    ViewData["Title"] = "Profile";
    ViewData["ActivePage"] = ManageNavPages.Index;
}

<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
    <div class="col-md-6">
        <form id="profile-form" method="post">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Username"></label>
                <input asp-for="Username" class="form-control" disabled />
            </div>
            <div class="form-group">
                <label asp-for="Input.Email"></label>
                @if (Model.IsEmailConfirmed)
                {
                    <div class="input-group">
                        <input asp-for="Input.Email" class="form-control" />
                        <span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
                    </div>
                }
                else
                {
                    <input asp-for="Input.Email" class="form-control" />
                    <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
                }
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <div class="form-group">
                    <label asp-for="Input.Name"></label>
                    <input asp-for="Input.Name" class="form-control" />
                </div>
                <div class="form-group">
                    <label asp-for="Input.DOB"></label>
                    <input asp-for="Input.DOB" class="form-control" />
                </div>
                <label asp-for="Input.PhoneNumber"></label>
                <input asp-for="Input.PhoneNumber" class="form-control" />
                <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
            </div>
            <button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Account/Register.cshtml ページの更新Update the Account/Register.cshtml page

更新プログラム、InputModelAreas/Identity/Pages/Account/Register.cshtml.cs次のようにコードを強調表示されます。Update the InputModel in Areas/Identity/Pages/Account/Register.cshtml.cs with the following highlighted code:

[AllowAnonymous]
public class RegisterModel : PageModel
{
    private readonly SignInManager<WebApp1User> _signInManager;
    private readonly UserManager<WebApp1User> _userManager;
    private readonly ILogger<RegisterModel> _logger;
    private readonly IEmailSender _emailSender;

    public RegisterModel(
        UserManager<WebApp1User> userManager,
        SignInManager<WebApp1User> signInManager,
        ILogger<RegisterModel> logger,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
        _emailSender = emailSender;
    }

    [BindProperty]
    public InputModel Input { get; set; }

    public string ReturnUrl { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Full name")]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Birth Date")]
        [DataType(DataType.Date)]
        public DateTime DOB { get; set; }

        [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; }
    }

    public void OnGet(string returnUrl = null)
    {
        ReturnUrl = returnUrl;
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl = returnUrl ?? Url.Content("~/");
        if (ModelState.IsValid)
        {
            var user = new WebApp1User {
                Name = Input.Name,
                DOB = Input.DOB,
                UserName = Input.Email,
                Email = Input.Email
            };
            var result = await _userManager.CreateAsync(user, Input.Password);
            if (result.Succeeded)
            {
                _logger.LogInformation("User created a new account with password.");

                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { userId = user.Id, code = code },
                    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>.");

                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }

        // If we got this far, something failed, redisplay form
        return Page();
    }
}

更新プログラム、 Areas/Identity/Pages/Account/Register.cshtmlを次の強調表示されているマークアップ。Update the Areas/Identity/Pages/Account/Register.cshtml with the following highlighted markup:

@page
@model RegisterModel
@{
    ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>

<div class="row">
    <div class="col-md-4">
        <form asp-route-returnUrl="@Model.ReturnUrl" method="post">
            <h4>Create a new account.</h4>
            <hr />
            <div asp-validation-summary="All" class="text-danger"></div>

            <div class="form-group">
                <label asp-for="Input.Name"></label>
                <input asp-for="Input.Name" class="form-control" />
                <span asp-validation-for="Input.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.DOB"></label>
                <input asp-for="Input.DOB" class="form-control" />
                <span asp-validation-for="Input.DOB" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="Input.Email"></label>
                <input asp-for="Input.Email" class="form-control" />
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.Password"></label>
                <input asp-for="Input.Password" class="form-control" />
                <span asp-validation-for="Input.Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.ConfirmPassword"></label>
                <input asp-for="Input.ConfirmPassword" class="form-control" />
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Register</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

プロジェクトをビルドします。Build the project.

カスタム ユーザー データのマイグレーションの追加Add a migration for the custom user data

Visual Studio でパッケージ マネージャー コンソール:In the Visual Studio Package Manager Console:

Add-Migration CustomUserData
Update-Database

カスタム ユーザー データの作成、表示、ダウンロード、削除のテストTest create, view, download, delete custom user data

アプリをテストします。Test the app:

  • 新しいユーザーを登録します。Register a new user.
  • カスタムのユーザー データを表示、/Identity/Account/Manageページ。View the custom user data on the /Identity/Account/Manage page.
  • ダウンロードしてから、ユーザーの個人データを表示、/Identity/Account/Manage/PersonalDataページ。Download and view the users personal data from the /Identity/Account/Manage/PersonalData page.