연습 - ID 사용자 지정
이전 단원에서는 ASP.NET Core ID에서 사용자 지정이 작동하는 방법을 알아보았습니다. 이 단원에서는 ID 데이터 모델을 확장하고 해당 UI를 변경합니다.
사용자 계정 데이터 사용자 지정
이 섹션에서는 기본 Razor 클래스 라이브러리 대신 사용할 ID UI 파일을 만들고 사용자 지정합니다.
수정할 사용자 등록 파일을 프로젝트에 추가합니다.
dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail"
이전 명령에서 다음을 확인할 수 있습니다.
--dbContext
옵션은RazorPagesPizzaAuth
이라는 기존DbContext
파생 클래스에 대한 정보를 도구에 제공합니다.--files
옵션은 ID 영역에 추가할 고유 파일의 세미콜론으로 구분된 목록을 지정합니다.Account.Manage.Index
는 프로필 관리 페이지입니다. 이 페이지는 이 단원의 뒷부분에서 수정됩니다.Account.Register
는 사용자 등록 페이지입니다. 이 페이지도 이 단원에서 수정됩니다.Account.Manage.EnableAuthenticator
및Account.ConfirmEmail
은 이 단원에서 스캐폴드되지만 수정되지는 않습니다.
팁
프로젝트 루트에서 다음 명령을 실행하여
--files
옵션에 대한 유효한 값을 확인합니다.dotnet aspnet-codegenerator identity --listFiles
Areas/Identity 디렉터리에 다음 파일이 추가됩니다.
- Pages/
- _ViewImports.cshtml
- Account/
- _ViewImports.cshtml
- ConfirmEmail.cshtml
- ConfirmEmail.cshtml.cs
- Register.cshtml
- Register.cshtml.cs
- Manage/
- _ManageNav.cshtml
- _ViewImports.cshtml
- EnableAuthenticator.cshtml
- EnableAuthenticator.cshtml.cs
- Index.cshtml
- Index.cshtml.cs
- ManageNavPages.cs
IdentityUser
확장
사용자의 이름을 저장하기 위한 새로운 요구 사항이 제공됩니다. 기본 IdentityUser
클래스에는 이름과 성 속성이 포함되어 있지 않으므로 RazorPagesPizzaUser
클래스를 확장해야 합니다.
Areas/Identity/Data/RazorPagesPizzaUser.cs에 다음 변경 내용을 적용합니다.
FirstName
및LastName
속성을 추가합니다.using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Identity; namespace RazorPagesPizza.Areas.Identity.Data; public class RazorPagesPizzaUser : IdentityUser { [Required] [MaxLength(100)] public string FirstName { get; set; } = string.Empty; [Required] [MaxLength(100)] public string LastName { get; set; } = string.Empty; }
이전 코드 조각의 속성은 기본
AspNetUsers
테이블에 만들 추가 열을 나타냅니다. 두 속성이 모두 필요하므로[Required]
특성을 주석으로 추가합니다. 그뿐 아니라[MaxLength]
특성은 최대 100자 길이가 허용됨을 나타냅니다. 기본 테이블 열의 데이터 형식은 그에 따라 정의됩니다. 이 프로젝트에서 nullable 컨텍스트가 활성화되고 속성이 nullable이 아닌 문자열이므로 기본값string.Empty
이 할당됩니다.다음
using
을 파일의 맨 위에 추가합니다.using System.ComponentModel.DataAnnotations;
위의 코드는
FirstName
및LastName
속성에 적용된 데이터 주석 특성을 확인합니다.
데이터베이스 업데이트
이제 모델을 변경했으므로 데이터베이스에 함께 변경해야 합니다.
모든 변경 내용이 저장되었는지 확인합니다.
EF Core 마이그레이션을 만들고 적용하여 기본 데이터 저장소를 업데이트합니다.
dotnet ef migrations add UpdateUser dotnet ef database update
UpdateUser
EF Core 마이그레이션은 DDL 변경 스크립트를AspNetUsers
테이블의 스키마에 적용했습니다. 특히, 다음 마이그레이션 출력과 같이FirstName
및LastName
열이 추가되었습니다.info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (37ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] ALTER TABLE [AspNetUsers] ADD [FirstName] nvarchar(100) NOT NULL DEFAULT N''; info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (36ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] ALTER TABLE [AspNetUsers] ADD [LastName] nvarchar(100) NOT NULL DEFAULT N'';
데이터베이스를 검사하여
AspNetUsers
테이블의 스키마에UpdateUser
EF Core 마이그레이션이 미치는 영향을 분석합니다.SQL Server 창의 dbo.AspNetUsers 테이블에서 열 노드를 확장합니다
RazorPagesPizzaUser
클래스의FirstName
및LastName
속성은 앞의 이미지에서FirstName
및LastName
열에 해당합니다.[MaxLength(100)]
특성으로 인해nvarchar(100)
의 데이터 형식이 두 열 각각에 할당되었습니다. null이 아닌 제약 조건이 추가된 이유는 클래스에서FirstName
및LastName
이(가) null을 허용하지 않는 문자열이기 때문입니다. 기존 행은 새 열에 빈 문자열을 표시합니다.
사용자 등록 양식 사용자 지정
및 에 대한 FirstName
LastName
새 열을 추가했습니다. 이제 등록 양식에 일치하는 필드를 표시하도록 UI를 편집해야 합니다.
Areas/Identity/Pages/Account/Register.cshtml에서 강조 표시된 다음 태그를 추가합니다.
<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" role="alert"></div> <div class="form-floating mb-3"> <input asp-for="Input.FirstName" class="form-control" /> <label asp-for="Input.FirstName"></label> <span asp-validation-for="Input.FirstName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Input.LastName" class="form-control" /> <label asp-for="Input.LastName"></label> <span asp-validation-for="Input.LastName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" /> <label asp-for="Input.Email">Email</label> <span asp-validation-for="Input.Email" class="text-danger"></span> </div>
위의 태그를 사용하여 이름 및 성 텍스트 상자를 사용자 등록 양식에 추가합니다.
Areas/Identity/Pages/Account/Register.cshtml.cs에서 이름 텍스트 상자에 대한 지원을 추가합니다.
FirstName
및LastName
속성을InputModel
중첩 클래스에 추가합니다.public class InputModel { [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "First name")] public string FirstName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "Last name")] public string LastName { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; }
[Display]
특성은 텍스트 상자와 연결할 레이블 텍스트를 정의합니다.OnPostAsync
메서드를 수정하여RazorPagesPizza
개체에 대해FirstName
및LastName
속성을 설정합니다. 강조 표시된 다음 줄을 추가합니다.public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); user.FirstName = Input.FirstName; user.LastName = Input.LastName; await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); var result = await _userManager.CreateAsync(user, Input.Password);
위의 변경 내용은
FirstName
및LastName
속성을 등록 양식의 사용자 입력으로 설정합니다.
사이트 헤더 사용자 지정
사용자 등록 중에 수집된 성과 이름을 표시하도록 Pages/Shared/_LoginPartial.cshtml을 업데이트합니다. 다음 코드 조각의 강조 표시된 줄이 필요합니다.
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
RazorPagesPizzaUser? user = await UserManager.GetUserAsync(User);
var fullName = $"{user?.FirstName} {user?.LastName}";
<li class="nav-item">
<a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello, @fullName!</a>
</li>
UserManager.GetUserAsync(User)
는 null 허용 RazorPagesPizzaUser
개체를 반환합니다. null 조건부 ?.
연산자는 개체가 null이 아닌 경우에만 및 LastName
속성에 RazorPagesPizzaUser
액세스 FirstName
하는 데 사용됩니다.
프로필 관리 양식 사용자 지정
새 필드를 사용자 등록 양식에 추가했지만 기존 사용자가 편집할 수 있도록 프로필 관리 양식에도 추가해야 합니다.
Areas/Identity/Pages/Account/Manage/Index.cshtml에서 강조 표시된 다음 태그를 추가합니다. 변경 내용을 저장합니다.
<form id="profile-form" method="post"> <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div> <div class="form-floating mb-3"> <input asp-for="Input.FirstName" class="form-control" /> <label asp-for="Input.FirstName"></label> <span asp-validation-for="Input.FirstName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Input.LastName" class="form-control" /> <label asp-for="Input.LastName"></label> <span asp-validation-for="Input.LastName" class="text-danger"></span> </div> <div class="form-floating mb-3"> <input asp-for="Username" class="form-control" disabled /> <label asp-for="Username" class="form-label"></label> </div>
Areas/Identity/Pages/Account/Manage/Index.cshtml.cs에서 이름 텍스트 상자를 지원하도록 다음과 같이 변경합니다.
FirstName
및LastName
속성을InputModel
중첩 클래스에 추가합니다.public class InputModel { [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "First name")] public string FirstName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "Last name")] public string LastName { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Phone] [Display(Name = "Phone number")] public string PhoneNumber { get; set; } }
강조 표시된 변경 내용을
LoadAsync
메서드에 통합합니다.private async Task LoadAsync(RazorPagesPizzaUser user) { var userName = await _userManager.GetUserNameAsync(user); var phoneNumber = await _userManager.GetPhoneNumberAsync(user); Username = userName; Input = new InputModel { PhoneNumber = phoneNumber, FirstName = user.FirstName, LastName = user.LastName }; }
위의 코드는 프로필 관리 양식의 해당 텍스트 상자에 표시할 이름과 성을 검색할 수 있도록 지원합니다.
강조 표시된 변경 내용을
OnPostAsync
메서드에 통합합니다. 변경 내용을 저장합니다.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(); } user.FirstName = Input.FirstName; user.LastName = Input.LastName; await _userManager.UpdateAsync(user); var phoneNumber = await _userManager.GetPhoneNumberAsync(user); if (Input.PhoneNumber != phoneNumber) { var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); if (!setPhoneResult.Succeeded) { StatusMessage = "Unexpected error when trying to set phone number."; return RedirectToPage(); } } await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your profile has been updated"; return RedirectToPage(); }
위의 코드는 데이터베이스의
AspNetUsers
테이블에서 이름과 성을 업데이트하도록 지원합니다.
확인 메일 보낸 사람 구성
앱을 처음 테스트할 때 사용자를 등록한 다음 링크를 클릭하여 사용자의 이메일 주소를 확인하는 시뮬레이션을 했습니다. 실제 확인 이메일을 보내려면 IEmailSender 구현을 생성하고 이를 종속성 주입 시스템에 등록해야 합니다. 작업을 간단하게 유지하기 위해 이 단원의 구현은 실제로 SMTP(Simple Mail Transfer Protocol) 서버로 전자 메일을 보내지 않습니다. 콘솔에 메일 콘텐츠만 작성합니다.
콘솔에서 메일을 일반 텍스트로 보려고 하므로 HTML로 인코딩된 텍스트를 제외하도록 생성된 메시지를 변경해야 합니다. Areas/Identity/Pages/Account/Register.cshtml.cs에서 다음 코드를 찾습니다.
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
다음으로 변경합니다.
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
탐색기 창에서 RazorPagesPizza\Services 폴더를 마우스 오른쪽 단추로 클릭하고 EmailSender.cs라는 새 파일을 만듭니다. 파일을 열고 다음 코드를 추가합니다.
using Microsoft.AspNetCore.Identity.UI.Services; namespace RazorPagesPizza.Services; public class EmailSender : IEmailSender { public EmailSender() {} public Task SendEmailAsync(string email, string subject, string htmlMessage) { Console.WriteLine(); Console.WriteLine("Email Confirmation Message"); Console.WriteLine("--------------------------"); Console.WriteLine($"TO: {email}"); Console.WriteLine($"SUBJECT: {subject}"); Console.WriteLine($"CONTENTS: {htmlMessage}"); Console.WriteLine(); return Task.CompletedTask; } }
위의 코드는 메시지 내용을 콘솔에 쓰는 IEmailSender의 구현을 만듭니다. 실제 구현에서는
SendEmailAsync
에서 외부 메일 서비스 또는 다른 작업에 연결하여 메일을 보냅니다.Program.cs에서 다음과 같이 강조 표시된 줄을 추가합니다.
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using RazorPagesPizza.Areas.Identity.Data; using Microsoft.AspNetCore.Identity.UI.Services; using RazorPagesPizza.Services; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("RazorPagesPizzaAuthConnection"); builder.Services.AddDbContext<RazorPagesPizzaAuth>(options => options.UseSqlServer(connectionString)); builder.Services.AddDefaultIdentity<RazorPagesPizzaUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<RazorPagesPizzaAuth>(); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddTransient<IEmailSender, EmailSender>(); var app = builder.Build();
위의 코드는
EmailSender
를 종속성 삽입 시스템에IEmailSender
(으)로 등록합니다.
등록 양식 변경 내용을 테스트합니다.
이게 전부입니다! 등록 양식 및 확인 메일의 변경 내용을 테스트해 보겠습니다.
모든 변경 내용을 저장했는지 확인합니다.
터미널 창에서
dotnet run
을(를) 사용하여 프로젝트를 빌드하고 앱을 실행합니다.브라우저에서 앱으로 이동합니다. 로그인한 경우 로그아웃을 선택합니다.
등록을 선택하고 업데이트된 양식을 사용하여 새 사용자를 등록합니다.
참고
이름 및 성 필드에 대한 유효성 검사 제약 조건에는
InputModel
의FirstName
및LastName
속성에 대한 데이터 주석이 반영됩니다.등록하면 등록 확인 화면으로 리디렉션됩니다. 터미널 창에서 위로 스크롤하여 다음과 유사한 콘솔 출력을 찾습니다.
Email Confirmation Message -------------------------- TO: jana.heinrich@contoso.com SUBJECT: Confirm your email CONTENTS: Please confirm your account by visiting the following URL: https://localhost:7192/Identity/Account/ConfirmEmail?<query string removed>
Ctrl+클릭을 사용하여 URL로 이동합니다. 확인 화면이 표시됩니다.
참고
GitHub Codespaces를 사용하는 경우 전달된 URL의 첫 번째 부분에
-7192
를 추가해야 할 수 있습니다. 예들 들어scaling-potato-5gr4j4-7192.preview.app.github.dev
입니다.로그인을 선택하고 새 사용자로 로그인합니다. 이제 앱의 헤더에는 Hello, [First name] [Last name]!이 포함됩니다.
VS Code SQL Server 창에서 RazorPagesPizza 데이터베이스를 마우스 오른쪽 단추로 클릭하고 새 쿼리를 선택합니다. 표시되는 탭에서 다음 쿼리를 입력하고 Ctrl+Shift+E를 눌러 실행합니다.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
다음과 유사한 결과가 있는 탭이 나타납니다.
사용자 이름 Email FirstName LastName kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich 스키마를 추가
FirstName
LastName
하기 전에 등록한 첫 번째 사용자입니다. 따라서 연결된AspNetUsers
테이블 레코드의 해당 열에는 데이터가 들어 있지 않습니다.
프로필 관리 양식에 대한 변경 내용 테스트
프로필 관리 양식에 대한 변경 내용도 테스트해야 합니다.
웹앱에서 만든 첫 번째 사용자로 로그인합니다.
Hello, ! 링크를 선택하여 프로필 관리 양식으로 이동합니다.
참고
이 사용자에 대한
AspNetUsers
테이블 행에FirstName
및LastName
의 값이 포함되어 있지 않으므로 링크가 올바르게 표시되지 않습니다.이름 및 성의 유효한 값을 입력합니다. 저장을 선택합니다.
이제 앱의 헤더가 Hello, [First name] [Last name]!으로 업데이트됩니다.
앱을 중지하려면 VS Code의 터미널 창에서 Ctrl+C를 누릅니다.
요약
이 단원에서는 사용자 지정 사용자 정보를 저장하도록 ID를 사용자 지정했습니다. 확인 메일도 사용자 지정했습니다. 다음 단원에서는 Identity에서 다단계 인증을 구현하는 방법을 알아보겠습니다.