ASP.NET Core 프로젝트에서 사용자 지정 사용자 데이터 추가, 다운로드 및 삭제 IdentityAdd, 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 웹 앱에 사용자 지정 사용자 데이터를 추가 합니다.Add custom user data to an ASP.NET Core web app.
  • 사용자 지정 사용자 데이터 모델을 특성으로 표시 하 여 PersonalDataAttribute 자동으로 다운로드 및 삭제에 사용할 수 있도록 합니다.Mark 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 페이지 웹 앱에서 생성 되지만 지침은 ASP.NET CORE MVC 웹 앱과 유사 합니다.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

Razor웹 앱 만들기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 웹 응용 프로그램 > 확인을 선택 합니다.Select ASP.NET Core Web Application > OK
  • 드롭다운에서 ASP.NET Core 3.0 을 선택 합니다.Select ASP.NET Core 3.0 in the dropdown
  • 웹 응용 프로그램 > 확인을 선택 합니다.Select Web Application > OK
  • 프로젝트를 빌드하고 실행합니다.Build and run the project.
  • 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 웹 응용 프로그램 > 확인을 선택 합니다.Select ASP.NET Core Web Application > OK
  • 드롭다운에서 ASP.NET Core 2.2 을 선택 합니다.Select ASP.NET Core 2.2 in the dropdown
  • 웹 응용 프로그램 > 확인을 선택 합니다.Select Web Application > OK
  • 프로젝트를 빌드하고 실행합니다.Build and run the project.

스 캐 폴더 실행 IdentityRun the Identity scaffolder

  • 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 > Add > 새 스 캐 폴드 항목추가를 클릭 합니다.From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 스 캐 폴드 추가 대화 상자의 왼쪽 창에서 추가를 선택 Identity > Add합니다.From the left pane of the Add Scaffold dialog, select Identity > Add.
  • **추가 Identity ** 대화 상자에서 다음 옵션을 선택 합니다.In the Add Identity dialog, the following options:
    • 기존 레이아웃 파일 ~/Pages/Shared/_Layout를 선택 합니다.Select 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인 경우 형식 (WebApp1. WebApp1Context )을 적용 합니다.Accept the type (WebApp1.Models.WebApp1Context if the project is named WebApp1).
    • 단추를 선택 + 하 여 새 사용자 클래스를 만듭니다.Select the + button to create a new User class. 형식 (프로젝트 이름이 WebApp1인 경우WebApp1User )을 적용 > 추가합니다.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.js 를 검사 합니다.Select the Download button and examined the PersonalData.json file.
    • 로그온 한 사용자를 삭제 하는 삭제 단추를 테스트 합니다.Test the Delete button, which deletes the logged on user.

DB에 사용자 지정 사용자 데이터 추가 IdentityAdd 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 System;
using Microsoft.AspNetCore.Identity;

namespace WebApp1.Areas.Identity.Data
{
    public class WebApp1User : IdentityUser
    {
        [PersonalData]
        public string Name { get; set; }
        [PersonalData]
        public DateTime DOB { get; set; }
    }
}
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 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.
  • 영역/ Identity /Pages/Account/Manage/DownloadPersonalData.cshtml 페이지에서 다운로드 한 데이터에 포함 Razor 됩니다.Included in the downloaded data by the Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml Razor Page.

계정/관리/인덱스를 업데이트 합니다. cshtml 페이지Update the Account/Manage/Index.cshtml page

InputModel다음 강조 표시 된 코드를 사용 하 여 영역/ 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;

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

    public string Username { 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; }

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

    private async Task LoadAsync(WebApp1User user)
    {
        var userName = await _userManager.GetUserNameAsync(user);
        var phoneNumber = await _userManager.GetPhoneNumberAsync(user);

        Username = userName;

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

    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)}'.");
        }

        await LoadAsync(user);
        return Page();
    }

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

        if (!ModelState.IsValid)
        {
            await LoadAsync(user);
            return Page();
        }

        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}'.");
            }
        }
        
        if (Input.Name != user.Name)
        {
            user.Name = Input.Name;
        }

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

        await _userManager.UpdateAsync(user);

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

다음 강조 표시 된 태그를 사용 하 여 영역/ 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" model="Model.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.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>
            <div class="form-group">
                <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" />
}
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();
    }
}

다음 강조 표시 된 태그를 사용 하 여 영역/ 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">
                <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>
            <div class="form-group">
                <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" />
}

계정/레지스터를 업데이트 합니다. cshtml 페이지Update the Account/Register.cshtml page

InputModel다음 강조 표시 된 코드를 사용 하 여 영역/ 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 IList<AuthenticationScheme> ExternalLogins { 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 async Task OnGetAsync(string returnUrl = null)
    {
        ReturnUrl = returnUrl;
        ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl = returnUrl ?? Url.Content("~/");
        ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
        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);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", 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>.");

                if (_userManager.Options.SignIn.RequireConfirmedAccount)
                {
                    return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
                }
                else
                {
                    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();
    }
}

다음 강조 표시 된 태그를 사용 하 여 영역/ 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 class="col-md-6 col-md-offset-2">
        <section>
            <h4>Use another service to register.</h4>
            <hr />
            @{
                if ((Model.ExternalLogins?.Count ?? 0) == 0)
                {
                    <div>
                        <p>
                            There are no external authentication services configured. See 
                             <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                            for details on setting up this ASP.NET application to support 
                            logging in via external services.
                        </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" />
}
[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();
    }
}

다음 강조 표시 된 태그를 사용 하 여 영역/ 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.

IUserClaimsPrincipalFactory를 사용 하 여 클레임 추가 IdentityAdd claims to Identity using IUserClaimsPrincipalFactory

참고

이 섹션은 이전 자습서의 확장이 아닙니다.This section isn't an extension of the previous tutorial. 자습서를 사용 하 여 빌드한 앱에 다음 단계를 적용 하려면 이 GitHub 문제를 참조 하세요.To apply the following steps to the app built using the tutorial, see this GitHub issue.

인터페이스를 사용 하 여 ASP.NET Core에 추가 클레임을 추가할 수 있습니다 Identity IUserClaimsPrincipalFactory<T> .Additional claims can be added to ASP.NET Core Identity by using the IUserClaimsPrincipalFactory<T> interface. 이 클래스는 메서드의 응용 프로그램에 추가할 수 있습니다 Startup.ConfigureServices .This class can be added to the app in the Startup.ConfigureServices method. 다음과 같이 클래스의 사용자 지정 구현을 추가 합니다.Add the custom implementation of the class as follows:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, 
        AdditionalUserClaimsPrincipalFactory>();

데모 코드는 클래스를 사용 합니다 ApplicationUser .The demo code uses the ApplicationUser class. 이 클래스는 IsAdmin 추가 클레임을 추가 하는 데 사용 되는 속성을 추가 합니다.This class adds an IsAdmin property which is used to add the additional claim.

public class ApplicationUser : IdentityUser
{
    public bool IsAdmin { get; set; }
}

AdditionalUserClaimsPrincipalFactoryUserClaimsPrincipalFactory 인터페이스를 구현합니다.The AdditionalUserClaimsPrincipalFactory implements the UserClaimsPrincipalFactory interface. 새 역할 클레임이에 추가 됩니다 ClaimsPrincipal .A new role claim is added to the ClaimsPrincipal.

public class AdditionalUserClaimsPrincipalFactory 
        : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public AdditionalUserClaimsPrincipalFactory( 
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager, 
        IOptions<IdentityOptions> optionsAccessor) 
        : base(userManager, roleManager, optionsAccessor)
    {}

    public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        var principal = await base.CreateAsync(user);
        var identity = (ClaimsIdentity)principal.Identity;

        var claims = new List<Claim>();
        if (user.IsAdmin)
        {
            claims.Add(new Claim(JwtClaimTypes.Role, "admin"));
        }
        else
        {
            claims.Add(new Claim(JwtClaimTypes.Role, "user"));
        }

        identity.AddClaims(claims);
        return principal;
    }
}

그러면 앱에서 추가 클레임을 사용할 수 있습니다.The additional claim can then be used in the app. 페이지에서 Razor IAuthorizationService 인스턴스를 사용 하 여 클레임 값에 액세스할 수 있습니다.In a Razor Page, the IAuthorizationService instance can be used to access the claim value.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

@if ((await AuthorizationService.AuthorizeAsync(User, "IsAdmin")).Succeeded)
{
    <ul class="mr-auto navbar-nav">
        <li class="nav-item">
            <a class="nav-link" asp-controller="Admin" asp-action="Index">ADMIN</a>
        </li>
    </ul>
}