Упражнение. Настройка удостоверения

Завершено

В предыдущем уроке вы узнали, как настраивать удостоверения ASP.NET Core. В этом уроке вы расширяете модель данных удостоверений и вносите соответствующие изменения пользовательского интерфейса.

Настройка пользовательского интерфейса учетной записи пользователя

В этом разделе вы создадите и настроите файлы пользовательского интерфейса удостоверений, которые будут использоваться вместо библиотеки классов Razor по умолчанию.

  1. Добавьте файлы регистрации пользователей, которые нужно изменить, в проект:

    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:

  1. Добавьте свойства 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.

  2. Добавьте следующий оператор using в начало файла.

    using System.ComponentModel.DataAnnotations;
    

    Приведенный выше код разрешает атрибуты аннотации данных, применяемые к свойствам FirstName и LastName.

Обновление базы данных

Теперь, когда изменения модели вносятся, сопровождающие изменения должны быть внесены в базу данных.

  1. Убедитесь, что все изменения сохранены.

  2. Создайте и примените миграцию 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'';
    
  3. Изучите базу данных, чтобы проанализировать влияние UpdateUser миграции EF Core на схему AspNetUsers таблицы.

    На панели SQL Server разверните узел Столбцы в таблице dbo.AspNetUsers.

    Снимок экрана: схема таблицы AspNetUsers.

    Свойства FirstName и LastName в классе RazorPagesPizzaUser соответствуют столбцам FirstName и LastName на предыдущем изображении. Тип данных объекта nvarchar(100) был назначен каждому из двух столбцов из-за атрибутов [MaxLength(100)]. Ограничение, не допускающее значения NULL, было добавлено, так как FirstName и LastName в классе являются строками, не допускающими значения NULL. В существующих строках в новых столбцах отображаются пустые строковые значения.

Настройка формы регистрации пользователя

Вы добавили новые столбцы для FirstName и LastName. Теперь необходимо изменить пользовательский интерфейс, чтобы отобразить соответствующие поля в форме регистрации.

  1. В 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 (Фамилия) добавляются в форму регистрации пользователя.

  2. В Areas/Identity/Pages/Account/Register.cshtml.cs добавьте поддержку для тестовых полей имени.

    1. Добавьте свойства 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] определяют текст метки, сопоставляемый с текстовыми полями.

    2. Измените метод 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.

Настройка формы управления профилями

Вы добавили новые поля в форму регистрации пользователя, но их также следует добавить в форму управления профилями, чтобы существующие пользователи могли редактировать их.

  1. В 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>
    
  2. В Areas/Identity/Pages/Account/Manage/Index.cshtml.cs внесите следующие изменения для поддержки текстовых полей имен.

    1. Добавьте свойства 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; }
      }
      
    2. Внедрите выделенные изменения в метод 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
          };
      }
      

      Приведенный выше код поддерживает извлечение имен и фамилий для вывода в соответствующих текстовых полях формы управления профилями.

    3. Внедрите выделенные изменения в метод 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). Он просто записывает содержимое электронной почты в консоль.

  1. Так как вы собираетесь просмотреть электронное письмо в виде обычного текста в консоли, необходимо изменить созданное сообщение, чтобы исключить текст в формате 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}");
    
  2. В области обозревателя щелкните правой кнопкой мыши папку 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 подключается к внешней почтовой службе или выполняет другое действие для отправки электронной почты.

  3. В файл 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 в системе внедрения зависимостей.

Проверка изменений формы регистрации

Это все! Давайте протестируем изменения в форме регистрации и в сообщении электронной почты с подтверждением.

  1. Убедитесь, что вы сохранили все изменения.

  2. На панели терминала выполните сборку проекта и запустите приложение, выполнив команду dotnet run.

  3. Перейдите к приложению в браузере. Выберите выход, если вы вошли в систему.

  4. Выберите Register (Зарегистрировать) и используйте обновленную форму для регистрации нового пользователя.

    Примечание.

    Ограничения проверки по полям First Name (Имя) и Last Name (Фамилия) отражают заметки данных для свойств FirstName и LastName объекта InputModel.

  5. После регистрации вы перейдете на экран подтверждения регистрации. На панели терминала прокрутите содержимое окна вверх, чтобы найти выходные данные консоли, похожие на следующие:

    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.

  6. Выберите имя входа и войдите с помощью нового пользователя. Заголовок приложения теперь содержит строку Hello, [First name] [Last name]! (Здравствуйте, [Имя] [Фамилия]).

  7. В области 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 запись таблицы не содержит данных в этих столбцах.

Настройка формы управления профилями

Кроме того, следует протестировать изменения, внесенные в форму управления профилями.

  1. В веб-приложении войдите с помощью первого созданного пользователя.

  2. Выберите ссылку Hello, ! чтобы перейти к форме управления профилями.

    Примечание.

    Эта ссылка отображается неправильно, так как строка таблицы AspNetUsers для этого пользователя не содержит значения для FirstName и LastName.

  3. Введите допустимые значения для First name (Имя) и Last name (Фамилия). Выберите Сохранить.

    Заголовок приложения меняется на Hello, [First name] [Last name]! (Здравствуйте, [Имя] [Фамилия].).

  4. Чтобы остановить приложение, нажмите клавиши CTRL+C в области терминала в VS Code.

Итоги

В этом уроке вы настроили хранение настраиваемых сведений о пользователе в удостоверении. Вы также настроили сообщение электронной почты с подтверждением. В следующем уроке вы узнаете о реализации многофакторной проверки подлинности в Identity.