Ćwiczenie — dostosowywanie mechanizmu Identity

Ukończone

W poprzedniej lekcji przedstawiono sposób działania dostosowywania w usłudze ASP.NET Core Identity. W tej lekcji rozszerzysz model danych tożsamości i wprowadzisz odpowiednie zmiany interfejsu użytkownika.

Dostosowywanie interfejsu użytkownika konta użytkownika

W tej sekcji utworzysz i dostosujesz pliki interfejsu użytkownika tożsamości, które będą używane zamiast domyślnej biblioteki klas Razor.

  1. Dodaj do projektu pliki rejestracji użytkownika, które mają zostać zmodyfikowane:

    dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail"
    

    W powyższym poleceniu:

    • Opcja --dbContext informuje narzędzie o istniejącej klasie o nazwie RazorPagesPizzaAuth pochodzącej od klasy DbContext.
    • Opcja --files określa rozdzielaną średnikami listę unikatowych plików, które mają zostać dodane do obszaru Tożsamość.
      • Account.Manage.Index to strona zarządzania profilami. Ta strona zostanie zmodyfikowana w dalszej części tej lekcji.
      • Account.Register to strona rejestracji użytkownika. Ta strona jest również modyfikowana w tej lekcji.
      • Account.Manage.EnableAuthenticator i Account.ConfirmEmail są rusztowane, ale nie są modyfikowane w tej lekcji.

    Napiwek

    Uruchom następujące polecenie z katalogu głównego projektu, aby wyświetlić prawidłowe wartości dla --files opcji: dotnet aspnet-codegenerator identity --listFiles

    Do katalogu Areas/Identity dodawane są następujące pliki:

    • 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

Rozszerzyć IdentityUser

Otrzymujesz nowe wymaganie dotyczące przechowywania nazw użytkowników. Ponieważ klasa domyślna IdentityUser nie zawiera właściwości dla imienia i nazwiska, należy rozszerzyć klasę RazorPagesPizzaUser .

Wprowadź następujące zmiany w :Areas/Identity/Data/RazorPagesPizzaUser.cs

  1. Dodaj właściwości FirstName i 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;
    }
    

    Właściwości w poprzednim fragmencie kodu reprezentują dodatkowe kolumny, które mają zostać utworzone w tabeli źródłowej AspNetUsers. Obie właściwości są wymagane i dlatego są opatrzone atrybutem [Required]. Ponadto ten atrybut [MaxLength] wskazuje, że dozwolona jest maksymalna długość 100 znaków. Typ danych kolumny tabeli źródłowej jest zdefiniowany odpowiednio do tego. Wartość domyślna jest przypisywana string.Empty , ponieważ kontekst dopuszczalny do wartości null jest włączony w tym projekcie, a właściwości są ciągami bez wartości null.

  2. Dodaj następującą instrukcję using na początku pliku.

    using System.ComponentModel.DataAnnotations;
    

    Poprzedni kod rozwiązuje atrybuty adnotacji danych zastosowane do właściwości FirstName i LastName.

Aktualizowanie bazy danych

Po wprowadzeniu zmian w modelu należy wprowadzić zmiany towarzyszące bazie danych.

  1. Upewnij się, że wszystkie zmiany zostały zapisane.

  2. Utwórz i zastosuj migrację platformy EF Core, aby zaktualizować źródłowy magazyn danych:

    dotnet ef migrations add UpdateUser
    dotnet ef database update
    

    Migracja platformy EF Core UpdateUser zastosowała skrypt zmiany DDL do schematu tabeli AspNetUsers. W szczególności zostały dodane kolumny FirstName i LastName, jak pokazano na poniższym fragmencie danych wyjściowych migracji:

    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. Sprawdź bazę danych, aby przeanalizować wpływ UpdateUser migracji platformy EF Core na AspNetUsers schemat tabeli.

    W okienku SQL Server rozwiń węzeł Kolumny w bazie danych. Tabela AspNetUsers .

    Zrzut ekranu przedstawiający schemat tabeli AspNetUsers.

    Właściwości FirstName i LastName w RazorPagesPizzaUser klasie odpowiadają kolumnom FirstName i LastName na powyższym obrazie. Typ danych nvarchar(100) został przypisany do każdej z dwóch kolumn z powodu atrybutów [MaxLength(100)]. Dodano ograniczenie inne niż null, ponieważ FirstName i LastName w klasie są ciągami bez wartości null. Istniejące wiersze wyświetlają puste ciągi w nowych kolumnach.

Dostosowywanie formularza rejestracji użytkownika

Dodano nowe kolumny dla elementów FirstName i LastName. Teraz musisz edytować interfejs użytkownika, aby wyświetlić pasujące pola w formularzu rejestracji.

  1. W pliku Areas/Identity/Pages/Account/Register.cshtml dodaj następujący wyróżniony kod:

    <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>
    

    Powyższy kod dodaje pola tekstowe First name (Imię) i Last name (Nazwisko) do formularza rejestracji użytkownika.

  2. W pliku Areas/Identity/Pages/Account/Register.cshtml.cs dodaj obsługę pól tekstowych imienia i nazwiska.

    1. Dodaj właściwości FirstName i LastName do klasy zagnieżdżonej 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; }
      

      Atrybuty [Display] definiują tekst etykiety, która ma być skojarzona z polami tekstowymi.

    2. Zmodyfikuj metodę OnPostAsync, aby ustawić właściwości FirstName i LastName dla obiektu RazorPagesPizza. Dodaj następujące wyróżnione wiersze:

      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);
      
      

      Poprzednia zmiana ustawia właściwości FirstName i LastName na dane wejściowe użytkownika z formularza rejestracji.

Dostosowywanie nagłówka witryny

Zaktualizuj plik Pages/Shared/_LoginPartial.cshtml w celu wyświetlenia imienia i nazwiska zebranych podczas rejestracji użytkownika. Wymagane są wyróżnione wiersze z poniższego fragmentu kodu:

<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) Zwraca obiekt dopuszczany RazorPagesPizzaUser do wartości null. Operator warunkowy ?. o wartości null jest używany do uzyskiwania dostępu do FirstName właściwości i LastName tylko wtedy, gdy RazorPagesPizzaUser obiekt nie ma wartości null.

Dostosowywanie formularza zarządzania profilami

Nowe pola zostały dodane do formularza rejestracji użytkownika, ale należy je również dodać do formularza zarządzania profilami, aby istniejący użytkownicy mogli je edytować.

  1. W pliku Areas/Identity/Pages/Account/Manage/Index.cshtml dodaj następujący wyróżniony kod. Zapisz zmiany.

    <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. W pliku Areas/Identity/Pages/Account/Manage/Index.cshtml.cs wprowadź następujące zmiany, aby obsługiwać pola tekstowe imienia i nazwiska.

    1. Dodaj właściwości FirstName i LastName do klasy zagnieżdżonej 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. Uwzględnij wyróżnione zmiany w metodzie 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
          };
      }
      

      Poprzedni kod obsługuje pobieranie imion i nazwisk na potrzeby wyświetlania ich w odpowiednich polach tekstowych formularza zarządzania profilami.

    3. Wprowadź wyróżnione zmiany w metodzie OnPostAsync. Zapisz zmiany.

      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();
      }
      

      Poprzedni kod obsługuje aktualizowanie imion i nazwisk w tabeli AspNetUsers bazy danych.

Konfigurowanie nadawcy wiadomości e-mail z potwierdzeniem

Po pierwszym przetestowaniu aplikacji zarejestrowano użytkownika, a następnie kliknięliśmy link, aby symulować potwierdzenie adresu e-mail użytkownika. Aby wysłać rzeczywistą wiadomość e-mail z potwierdzeniem, należy utworzyć implementację IEmailSender i zarejestrować ją w systemie wstrzykiwania zależności. Aby zachować prostotę, implementacja w tej lekcji nie wysyła wiadomości e-mail na serwer SMTP (Simple Mail Transfer Protocol). Po prostu zapisuje zawartość wiadomości e-mail w konsoli.

  1. Ponieważ zamierzasz wyświetlić wiadomość e-mail w postaci zwykłego tekstu w konsoli programu , należy zmienić wygenerowaną wiadomość, aby wykluczyć tekst zakodowany w formacie HTML. W obszarze Obszary/Tożsamość/Strony/Konto/Register.cshtml.cs znajdź następujący kod:

    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
        $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
    

    Zmień ją na:

    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
        $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
    
  2. W okienku Eksplorator kliknij prawym przyciskiem myszy folder RazorPagesPizza\Services i utwórz nowy plik o nazwie EmailSender.cs. Otwórz plik i dodaj następujący kod:

    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;
        }
    }
    

    Powyższy kod tworzy implementację IEmailSender , która zapisuje zawartość komunikatu w konsoli programu . W rzeczywistej implementacji SendEmailAsync połączy się z zewnętrzną usługą poczty lub inną akcją w celu wysłania wiadomości e-mail.

  3. W Program.cs dodaj wyróżnione wiersze:

    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();
    

    Powyższe rejestry EmailSender są rejestrowane jako IEmailSender w systemie wstrzykiwania zależności.

Testowanie zmian w formularzu rejestracji

To wszystko! Przetestujmy zmiany w formularzu rejestracji i wiadomość e-mail z potwierdzeniem.

  1. Upewnij się, że wszystkie zmiany zostały zapisane.

  2. W okienku terminalu skompiluj projekt i uruchom aplikację za pomocą polecenia dotnet run.

  3. W przeglądarce przejdź do aplikacji. Wybierz pozycję Wyloguj się, jeśli się zalogowano.

  4. Wybierz pozycję Register (Zarejestruj) i użyj zaktualizowanego formularza, aby zarejestrować nowego użytkownika.

    Uwaga

    Ograniczenia weryfikacji w polach Imię i Nazwisko odzwierciedlają adnotacje danych we właściwościach FirstName i LastName elementu InputModel.

  5. Po zarejestrowaniu nastąpi przekierowanie do ekranu Potwierdzenia rejestracji. W okienku terminalu przewiń w górę, aby znaleźć dane wyjściowe konsoli podobne do następujących:

    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>
    

    Przejdź do adresu URL za pomocą kombinacji Ctrl+. Zostanie wyświetlony ekran potwierdzenia.

    Uwaga

    Jeśli używasz usługi GitHub Codespaces, może być konieczne dodanie -7192 do pierwszej części przekazanego adresu URL. Na przykład scaling-potato-5gr4j4-7192.preview.app.github.dev.

  6. Wybierz pozycję Zaloguj się i zaloguj się przy użyciu nowego użytkownika. Nagłówek aplikacji zawiera teraz komunikat Hello, [imię] [nazwisko]! (Witaj, [imię] [nazwisko]!).

  7. W okienku PROGRAMU SQL Server w programie VS Code kliknij prawym przyciskiem myszy bazę danych RazorPagesPizza i wybierz pozycję Nowe zapytanie. Na wyświetlonej karcie wprowadź następujące zapytanie i naciśnij Ctrl+Shift+E, aby go uruchomić.

    SELECT UserName, Email, FirstName, LastName
    FROM dbo.AspNetUsers
    

    Zostanie wyświetlona karta z wynikami podobnymi do następujących:

    UserName Email FirstName LastName
    kai.klein@contoso.com kai.klein@contoso.com
    jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich

    Pierwszy użytkownik zarejestrował się przed dodaniem FirstName i LastName do schematu. Dlatego skojarzony AspNetUsers rekord tabeli nie zawiera danych w tych kolumnach.

Testowanie zmian w formularzu zarządzania profilami

Należy również przetestować zmiany wprowadzone w formularzu zarządzania profilami.

  1. W aplikacji internetowej zaloguj się przy użyciu pierwszego utworzonego użytkownika.

  2. Wybierz link Witaj , ! , aby przejść do formularza zarządzania profilami.

    Uwaga

    Link nie jest wyświetlany poprawnie, ponieważ wiersz tabeli AspNetUsers dla tego użytkownika nie zawiera wartości FirstName i LastName.

  3. Wprowadź prawidłowe wartości First name (Imię) i Last name (Nazwisko). Wybierz pozycję Zapisz.

    Nagłówek aplikacji zostanie zaktualizowany do Hello, [Imię] [Nazwisko]! (Witaj, [Imię] [Nazwisko]!).

  4. Aby zatrzymać aplikację, naciśnij Ctrl+C w okienku terminalu w programie VS Code.

Podsumowanie

W tej lekcji dostosowano tożsamość do przechowywania niestandardowych informacji o użytkowniku. Dostosowano również wiadomość e-mail z potwierdzeniem. W następnej lekcji dowiesz się więcej na temat implementowania uwierzytelniania wieloskładnikowego w usłudze Identity.