添加、 下载和删除标识到 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.2Select ASP.NET Core 2.2 in the dropdown
  • 选择Web 应用程序 > 确定Select Web Application > OK
  • 生成并运行该项目。Build and run the project.

运行标识基架Run the Identity scaffolder

  • 解决方案资源管理器,右键单击该项目 >添加 > 新基架项From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 从左窗格添加基架对话框中,选择标识 > 添加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).
    • 选择 + 按钮以创建一个新User 类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程序包管理器控制台: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.