Упражнение. Настройка удостоверения
В предыдущем уроке вы узнали, как настраивать удостоверения 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="[email protected]" /> <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
, чтобы задать свойства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
.Перейдите к приложению в браузере. Выберите Выход, если вы вошли в систему.
Выберите "Регистрация " и используйте обновленную форму для регистрации нового пользователя.
Примечание.
Ограничения проверки полей Имя и Фамилия отражают аннотации данных о
FirstName
иLastName
свойствахInputModel
.После регистрации вы перейдете на экран подтверждения регистрации . На панели терминала прокрутите содержимое окна вверх, чтобы найти выходные данные консоли, похожие на следующие:
Email Confirmation Message -------------------------- TO: [email protected] 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, [Имя] [Фамилия]!.
В области SQL Server в VS Code щелкните правой кнопкой мыши базу данных RazorPagesPizza и выберите новый запрос. На появившейся вкладке введите следующий запрос и нажмите Ctrl+Shift+E, чтобы выполнить его.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
Откроется вкладка с результатами, похожими на следующие:
Имя пользователя Эл. почта Имя Фамилия [email protected] [email protected] [email protected] [email protected] Яна Генрих Первый пользователь, зарегистрированный перед добавлением
FirstName
иLastName
схемой. Поэтому связаннаяAspNetUsers
запись таблицы не содержит данных в этих столбцах.
Настройка формы управления профилями
Кроме того, следует протестировать изменения, внесенные в форму управления профилями.
В веб-приложении войдите с помощью первого созданного пользователя.
Выберите ссылку Hello, ! чтобы перейти к форме управления профилями.
Примечание.
Эта ссылка отображается неправильно, так как строка таблицы
AspNetUsers
для этого пользователя не содержит значения дляFirstName
иLastName
.Введите допустимые значения для имени и фамилии. Нажмите кнопку "Сохранить".
Обновление заголовка приложения на Hello, [Имя] [Фамилия]!.
Чтобы остановить приложение, нажмите клавиши CTRL+C в области терминала в VS Code.
Итоги
В этом уроке вы настроили хранение настраиваемых сведений о пользователе в удостоверении. Вы также настроили сообщение электронной почты с подтверждением. В следующем уроке вы узнаете о реализации многофакторной проверки подлинности в Identity.