加入、 下載及刪除身分識別的 ASP.NET Core 專案的自訂使用者資料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 應用程式 > 確定Select ASP.NET Core Web Application > OK
  • 選取下拉式清單中的 [ ASP.NET Core 2.2 ]Select ASP.NET Core 2.2 in the dropdown
  • 選取 Web 應用程式 > 確定Select Web Application > OK
  • 建置並執行專案。Build and run the project.

執行身分識別框架Run the Identity scaffolder

  • 方案總管,以滑鼠右鍵按一下專案 >新增 > 新增 Scaffold 項目From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 從左窗格新增 Scaffold對話方塊中,選取識別 > 新增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.
  • UseAuthentication 加入 Startup.ConfigureAdd 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.

識別資料庫中加入自訂的使用者資料Add custom user data to the Identity DB

更新IdentityUser衍生的類別具有自訂屬性。Update the IdentityUser derived class with custom properties. 如果您名為 WebApp1 的專案,檔案會命名為Areas/Identity/Data/WebApp1User.csIf 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.DeleteDeleted 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 Package Manager Console: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.