Упражнение. Настройка удостоверения
В предыдущем уроке вы узнали, как настраивать удостоверения ASP.NET Core. В этом уроке вы расширяете модель данных удостоверений и вносите соответствующие изменения пользовательского интерфейса.
Настройка пользовательского интерфейса учетной записи пользователя
В этом разделе вы создадите и настроите файлы пользовательского интерфейса удостоверений, которые будут использоваться вместо библиотеки классов Razor по умолчанию.
Добавьте файлы регистрации пользователей, которые нужно изменить, в проект:
dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail"
В предыдущей команде используются следующие параметры:
- Параметр
--dbContext
сообщает средству о существующем классе, производном отDbContext
, с именемRazorPagesPizzaAuth
. - Параметр
--files
задает разделенный точками с запятой список уникальных файлов, добавляемых в область Identity.-
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 символов. Тип данных столбца базовой таблицы определяется соответствующим образом. Значение по умолчаниюstring.Empty
назначается, так как в этом проекте разрешен контекст, допускающий значение NULL, а свойства являются строками, не допускающими значением NULL.Добавьте следующий оператор
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'';
Изучите базу данных, чтобы проанализировать влияние
UpdateUser
миграции EF Core на схемуAspNetUsers
таблицы.На панели SQL Server разверните узел Столбцы в таблице dbo.AspNetUsers.
Свойства
FirstName
иLastName
в классеRazorPagesPizzaUser
соответствуют столбцамFirstName
иLastName
на предыдущем изображении. Тип данных объектаnvarchar(100)
был назначен каждому из двух столбцов из-за атрибутов[MaxLength(100)]
. Ограничение, не допускающее значения NULL, было добавлено, так какFirstName
иLastName
в классе являются строками, не допускающими значения NULL. В существующих строках в новых столбцах отображаются пустые строковые значения.
Настройка формы регистрации пользователя
Вы добавили новые столбцы для FirstName
и LastName
. Теперь необходимо изменить пользовательский интерфейс, чтобы отобразить соответствующие поля в форме регистрации.
В 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>
В предыдущей разметке поля First name (Имя) и Last name (Фамилия) добавляются в форму регистрации пользователя.
В 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
, чтобы задать свойстваFirstName
иLastName
для объектаRazorPagesPizza
. Добавьте следующие выделенные строки: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)
возвращает объект, допускающий значение RazorPagesPizzaUser
NULL. Оператор null-условный ?.
используется для доступа к FirstName
свойствам, LastName
только если RazorPagesPizzaUser
объект не имеет значения NULL.
Настройка формы управления профилями
Вы добавили новые поля в форму регистрации пользователя, но их также следует добавить в форму управления профилями, чтобы существующие пользователи могли редактировать их.
В 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 и зарегистрировать ее в системе внедрения зависимостей. Чтобы упростить работу, реализация в этом уроке фактически не отправляет сообщения электронной почты на сервер SIMPLE Mail Transfer Protocol (SMTP). Он просто записывает содержимое электронной почты в консоль.
Так как вы собираетесь просмотреть электронное письмо в виде обычного текста в консоли, необходимо изменить созданное сообщение, чтобы исключить текст в формате 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
.Перейдите к приложению в браузере. Выберите выход, если вы вошли в систему.
Выберите Register (Зарегистрировать) и используйте обновленную форму для регистрации нового пользователя.
Примечание.
Ограничения проверки по полям First Name (Имя) и Last Name (Фамилия) отражают заметки данных для свойств
FirstName
иLastName
объектаInputModel
.После регистрации вы перейдете на экран подтверждения регистрации. На панели терминала прокрутите содержимое окна вверх, чтобы найти выходные данные консоли, похожие на следующие:
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>
Перейдите по URL-адресу, нажав клавишу CTRL+и щелкнув левой кнопкой мыши. Отобразится экран подтверждения.
Примечание.
Если вы используете GitHub Codespaces, может потребоваться добавить
-7192
в первую часть переадресованного URL-адреса. Например,scaling-potato-5gr4j4-7192.preview.app.github.dev
.Выберите имя входа и войдите с помощью нового пользователя. Заголовок приложения теперь содержит строку Hello, [First name] [Last name]! (Здравствуйте, [Имя] [Фамилия]).
В области SQL Server в VS Code щелкните правой кнопкой мыши базу данных RazorPagesPizza и выберите Создать запрос. На появившемся вкладке введите следующий запрос и нажмите клавиши CTRL+SHIFT+E, чтобы запустить его.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
Откроется вкладка с результатами, похожими на следующие:
UserName Эл. почта 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
.Введите допустимые значения для First name (Имя) и Last name (Фамилия). Выберите Сохранить.
Заголовок приложения меняется на Hello, [First name] [Last name]! (Здравствуйте, [Имя] [Фамилия].).
Чтобы остановить приложение, нажмите клавиши CTRL+C в области терминала в VS Code.
Итоги
В этом уроке вы настроили хранение настраиваемых сведений о пользователе в удостоверении. Вы также настроили сообщение электронной почты с подтверждением. В следующем уроке вы узнаете о реализации многофакторной проверки подлинности в Identity.