Cvičení – přizpůsobení architektury Identity

Dokončeno

V předchozí lekci jste zjistili, jak funguje přizpůsobení v ASP.NET Základní identitě. V této lekci rozšíříte datový model identity a provedete odpovídající změny uživatelského rozhraní.

Přizpůsobení dat uživatelského účtu

V této části vytvoříte a přizpůsobíte soubory uživatelského rozhraní identity, které se použijí místo výchozí knihovny tříd Razor.

  1. Přidejte do projektu registrační soubory uživatele, které mají být upraveny:

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

    V předcházejícím příkazu:

    • Parametr --dbContext informuje nástroj o existující třídě RazorPagesPizzaAuth odvozené od třídy DbContext.
    • Parametr --files určuje středníkem oddělený seznam jedinečných souborů, které se mají přidat do oblasti Identity.
      • Account.Manage.Index je stránka pro správu profilu. Tato stránka se později v této lekci upraví.
      • Account.Register je stránka registrace uživatele. Tato stránka je také upravena v této lekci.
      • Account.Manage.EnableAuthenticator a Account.ConfirmEmail jsou vygenerovány, ale nejsou upraveny v této lekci.

    Tip

    Spuštěním následujícího příkazu z kořenového adresáře projektu zobrazte platné hodnoty pro příslušnou --files možnost: dotnet aspnet-codegenerator identity --listFiles

    Do adresáře Areas/Identity jsou přidány následující soubory:

    • 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

Rozšířit IdentityUser

Máte nový požadavek na uložení jmen uživatelů. Vzhledem k tomu, že výchozí IdentityUser třída neobsahuje vlastnosti pro křestní jména a příjmení, musíte třídu rozšířit RazorPagesPizzaUser .

Proveďte následující změny Areas/Identity/Data/RazorPagesPizzaUser.cs:

  1. Přidejte vlastnosti FirstName a 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;
    }
    

    Vlastnosti v předchozím fragmentu kódu představují další sloupce, které se mají vytvořit v podkladové tabulce AspNetUsers. Obě vlastnosti jsou povinné a jsou proto opatřené atributem [Required]. Atribut [MaxLength] navíc udává, že maximální povolená délka je 100 znaků. Datový typ sloupce podkladové tabulky je definován odpovídajícím způsobem. Výchozí hodnota string.Empty je přiřazena, protože v tomto projektu je povolen kontext s možnou hodnotou null a vlastnosti jsou nenulové řetězce.

  2. Na začátek souboru přidejte následující příkaz using.

    using System.ComponentModel.DataAnnotations;
    

    Předchozí kód přeloží atributy datových poznámek použitých na vlastnosti FirstName a LastName.

Aktualizace databáze

Teď, když jsou změny modelu provedeny, musí být v databázi provedeny doprovodné změny.

  1. Ujistěte se, že jsou všechny vaše změny uložené.

  2. Vytvořením a použitím migrace EF Core aktualizujte podkladové úložiště dat:

    dotnet ef migrations add UpdateUser
    dotnet ef database update
    

    Při migraci EF Core UpdateUser se na schéma tabulky AspNetUsers použil změnový skript DDL. Konkrétně byly přidány sloupce FirstName a LastName, jak je vidět na následujícím výňatku z výstupu migrace:

    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. Prozkoumejte databázi a analyzujte účinek UpdateUser migrace EF Core na AspNetUsers schéma tabulky.

    V podokně SQL Server rozbalte uzel Sloupce v dbo. Tabulka AspNetUsers

    Snímek obrazovky se schématem tabulky AspNetUsers

    LastName Vlastnosti FirstName a vlastnosti ve RazorPagesPizzaUser třídě odpovídají FirstName sloupcům LastName v předchozím obrázku. Kvůli atributům [MaxLength(100)] byl k oběma sloupcům přiřazen datový typ nvarchar(100). Bylo přidáno omezení, které není null, protože FirstName ve LastName třídě jsou nenulové řetězce. U existujících řádků jsou v nových sloupcích prázdné řetězce.

Přizpůsobení formuláře pro registraci uživatele

Přidali jste nové sloupce pro FirstName a LastName. Teď potřebujete upravit uživatelské rozhraní, aby se v registračním formuláři zobrazila odpovídající pole.

  1. V souboru Areas/Identity/Pages/Account/Register.cshtml přidejte následující zvýrazněný kód:

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

    V předchozím kódu jsou do formuláře pro registraci uživatele přidána textová pole pro jméno a příjmení.

  2. V souboru Areas/Identity/Pages/Account/Register.cshtml.cs přidejte podporu pro textová pole se jménem.

    1. Do vnořené třídy InputModel přidejte vlastnosti FirstName a LastName:

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

      Atributy [Display] definují text popisku, který má být přidružen k těmto textovým polím.

    2. Úpravou metody OnPostAsync nastavte vlastnosti FirstName a LastName u objektu RazorPagesPizza. Přidejte následující zvýrazněné řádky:

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

      Předchozí změna nastaví vlastnosti FirstName a LastName na zadání uživatele z registračního formuláře.

Přizpůsobení záhlaví webu

Aktualizací souboru Pages/Shared/_LoginPartial.cshtml zobrazte jméno a příjmení shromážděné během registrace uživatele. Jsou zapotřebí zvýrazněné řádky v následujícím fragmentu kódu:

<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) vrátí objekt s možnou RazorPagesPizzaUser hodnotou null. Podmíněný ?. operátor null se používá pro přístup k vlastnostem FirstName a LastName přístup pouze v případě RazorPagesPizzaUser , že objekt není null.

Přizpůsobení formuláře pro správu profilu

Do registračního formuláře uživatele jste přidali nová pole, ale měli byste je také přidat do formuláře pro správu profilů, aby je mohli stávající uživatelé upravovat.

  1. V souboru Areas/Identity/Pages/Account/Manage/Index.cshtml přidejte následující zvýrazněný kód. Uložte provedené změny.

    <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. V souboru Areas/Identity/Pages/Account/Manage/Index.cshtml.cs proveďte následující změny pro podporu textových polí se jménem.

    1. Do vnořené třídy InputModel přidejte vlastnosti FirstName a LastName:

      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. Do metody LoadAsync začleňte zvýrazněné změny:

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

      Předchozí kód umožňuje načtení jména a příjmení, které se zobrazí v odpovídajících textových polích formuláře pro správu profilu.

    3. Do metody OnPostAsync začleňte zvýrazněné změny. Uložte provedené změny.

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

      Předchozí kód umožňuje aktualizaci jména a příjmení v tabulce databáze AspNetUsers.

Konfigurace odesílatele e-mailu s potvrzením

Při prvním otestování aplikace jste zaregistrovali uživatele a potom jste klikli na odkaz pro simulaci potvrzení e-mailové adresy uživatele. Pokud chcete odeslat skutečný potvrzovací e-mail, musíte vytvořit implementaci IEmailSender a zaregistrovat ji v systému injektáže závislostí. Aby bylo všechno jednoduché, implementace v této lekci ve skutečnosti neodesílá e-mail na server SMTP (Simple Mail Transfer Protocol). Jenom zapíše e-mailový obsah do konzoly.

  1. Vzhledem k tomu, že e-mail zobrazíte ve formátu prostého textu v konzole, měli byste vygenerovanou zprávu změnit tak, aby se vyloučil text kódovaný ve formátu HTML. V oblastech, identitě, stránkách, účtu nebo Register.cshtml.cs vyhledejte následující kód:

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

    Změňte ho na:

    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
        $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
    
  2. V podokně Průzkumník klikněte pravým tlačítkem myši na složku RazorPagesPizza\Services a vytvořte nový soubor s názvem EmailSender.cs. Otevřete soubor a přidejte následující kód:

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

    Předchozí kód vytvoří implementaci IEmailSender , která zapíše obsah zprávy do konzoly. V reálné implementaci SendEmailAsync by se připojil k externí poštovní službě nebo k nějaké jiné akci pro odesílání e-mailů.

  3. V Program.cs přidejte zvýrazněné řádky:

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

    Předchozí registrace jsou IEmailSender v systému injektáže závislostíEmailSender.

Otestování změn registračního formuláře

To je všechno! Pojďme otestovat změny registračního formuláře a potvrzovací e-mail.

  1. Ujistěte se, že jste uložili všechny změny.

  2. V podokně terminálu sestavte projekt a spusťte aplikaci pomocí dotnet runpříkazu .

  3. V prohlížeči přejděte na aplikaci. Pokud jste přihlášení, vyberte Odhlášení .

  4. Vyberte možnost registrace a pomocí aktualizovaného formuláře zaregistrujte nového uživatele.

    Poznámka:

    V ověřovacích omezeních u polí pro jméno a příjmení jsou promítnuty datové poznámky vlastností FirstName a LastName pro InputModel.

  5. Po registraci se přesměruje na potvrzovací obrazovku registrace . V podokně terminálu vyhledejte výstup konzoly podobný následujícímu:

    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>
    

    Přejděte na adresu URL stisknutím klávesy Ctrl+. Zobrazí se potvrzovací obrazovka.

    Poznámka:

    Pokud používáte GitHub Codespaces, možná budete muset přidat -7192 první část přeposílané adresy URL. Například scaling-potato-5gr4j4-7192.preview.app.github.dev.

  6. Vyberte Přihlášení a přihlaste se pomocí nového uživatele. Záhlaví aplikace teď obsahuje Hello, [jméno] [příjmení]!.

  7. V podokně SQL Server v editoru VS Code klikněte pravým tlačítkem myši na databázi RazorPagesPizza a vyberte Nový dotaz. Na kartě, která se zobrazí, zadejte následující dotaz a stisknutím kláves Ctrl+Shift+E ho spusťte.

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

    Zobrazí se karta s výsledky podobnými následujícímu:

    UserName E-mail FirstName LastName
    kai.klein@contoso.com kai.klein@contoso.com
    jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich

    První uživatel se zaregistroval před přidáním FirstName a LastName do schématu. AspNetUsers Přidružený záznam tabulky proto v těchto sloupcích neobsahuje data.

Otestování změn ve formuláři pro správu profilu

Měli byste také otestovat změny provedené ve formuláři pro správu profilů.

  1. Ve webové aplikaci se přihlaste pomocí prvního uživatele, který jste vytvořili.

  2. Vyberte odkaz Hello, ! a přejděte do formuláře pro správu profilů.

    Poznámka:

    Odkaz se nezobrazuje správně, protože řádek tabulky AspNetUsers pro tohoto uživatele neobsahuje hodnoty polí FirstName a LastName.

  3. Zadejte platné hodnoty pro jméno a příjmení. Zvolte Uložit.

    Záhlaví aplikace se aktualizuje na Hello, [jméno] [příjmení]!.

  4. Pokud chcete aplikaci zastavit, stiskněte ctrl+C v podokně terminálu v editoru VS Code.

Shrnutí

V této lekci jste přizpůsobili identitu tak, aby ukládaly vlastní informace o uživatelích. Přizpůsobili jste si také potvrzovací e-mail. V další lekci se dozvíte o implementaci vícefaktorového ověřování v identity.