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

Dokončeno

V předchozí lekci jste zjistili, jak přizpůsobení funguje v ASP.NET Core Identity. 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 budou používat 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" --userClass RazorPagesPizzaUser --force
    

    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.
    • Parametr --userClass způsobí vytvoření třídy s názvem RazorPagesPizzaUser odvozené od třídy IdentityUser.
    • Možnost --force způsobí přepsání existujících souborů v oblasti Identity .

    Tip

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

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

    • Data/
      • RazorPagesPizzaUser.cs
    • 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

    Soubor Data/RazorPagesPizzaAuth.cs, který existoval před spuštěním předchozího příkazu, byl navíc přepsán, protože byl použit parametr --force. Deklarace třídy RazorPagesPizzaAuth teď odkazuje na nově vytvořený typ uživatele RazorPagesPizzaUser:

    public class RazorPagesPizzaAuth : IdentityDbContext<RazorPagesPizzaUser>
    

    Stránky EnableAuthenticator a ConfirmEmail Razor byly vygenerovány, i když se změnily až později v modulu.

  2. V Program.csje potřeba, aby AddDefaultIdentity volání vědělo o novém typu uživatele Identita. Začleňte následující zvýrazněné změny. (Příklad je pro čitelnost přeformátovaný.)

    using Microsoft.AspNetCore.Identity;
    using Microsoft.EntityFrameworkCore;
    using RazorPagesPizza.Areas.Identity.Data;
    
    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();
    
  3. Aktualizujte Pages/Shared/_LoginPartial.cshtml , aby byly v horní části zahrnuty následující zvýrazněné změny. Uložte provedené změny.

    @using Microsoft.AspNetCore.Identity
    @using RazorPagesPizza.Areas.Identity.Data
    @inject SignInManager<RazorPagesPizzaUser> SignInManager
    @inject UserManager<RazorPagesPizzaUser> UserManager
    
    <ul class="navbar-nav">
    

    Předchozí změny aktualizují typ uživatele předaný do SignInManager<T> a UserManager<T> v direktivách @inject. Místo výchozího typu IdentityUser se teď odkazuje na uživatele RazorPagesPizzaUser. Kvůli překladu odkazů RazorPagesPizzaUser byla přidána direktiva @using.

    Soubor Pages/Shared/_LoginPartial.cshtml je fyzicky umístěný mimo oblast Identity. Proto se soubor automaticky neaktualizoval nástrojem pro generování uživatelského rozhraní. Příslušné změny musí být provedeny ručně.

    Tip

    Jako alternativu k ruční úpravě souboru _LoginPartial.cshtml je možné ho před spuštěním nástroje pro generování kódu odstranit. Soubor _LoginPartial.cshtml se znovu vytvoří s odkazy na novou RazorPagesPizzaUser třídu.

  4. Aktualizujte soubor Areas/Identity/Data/RazorPagesPizzaUser.cs tak, aby podporoval ukládání a načítání dalších dat profilu uživatele. Proveďte následující změny:

    1. Přidejte vlastnosti FirstName a LastName:

      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 je přiřazena, protože kontext s možnou string.Empty hodnotou null je v tomto projektu povolen a vlastnosti jsou řetězce, které nelze null.

    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ž byly provedeny změny modelu, musí být v databázi provedeny doprovodné změny.

  1. Ujistěte se, že jsou všechny 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 vliv UpdateUser migrace EF Core na AspNetUsers schéma tabulky.

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

    Snímek obrazovky se schématem tabulky AspNetUsers

    Vlastnosti FirstName a LastName ve RazorPagesPizzaUser třídě odpovídají sloupcům FirstName a LastName na předchozím obrázku. Kvůli atributům [MaxLength(100)] byl k oběma sloupcům přiřazen datový typ nvarchar(100). Omezení, které nemá hodnotu null, bylo přidáno, protože FirstName a LastName ve třídě jsou řetězce, které nelze použít null. 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ď je potřeba upravit uživatelské rozhraní tak, aby se v registračním formuláři zobrazovala 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"></div>
        <div class="form-floating">
            <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">
            <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">
            <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" />
            <label asp-for="Input.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>

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

Přidali jste nová pole do formuláře pro registraci uživatelů, ale měli byste je také přidat do formuláře pro správu profilu, aby je stávající uživatelé mohli 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"></div>
        <div class="form-floating">
            <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">
            <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">
            <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; }
      
          [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 potvrzovací e-mailu

Pokud chcete odeslat potvrzovací e-mail, musíte vytvořit implementaci a zaregistrovat ho IEmailSender v systému injektáže závislostí. Aby to nebylo jednoduché, vaše implementace ve skutečnosti neodesílá e-maily na server SMTP. Jenom zapíše obsah e-mailu do konzoly.

  1. Vzhledem k tomu, že se e-mail bude zobrazovat v prostém textu v konzole nástroje , měli byste vygenerovanou zprávu změnit tak, aby se vyloučil text kódovaný ve formátu HTML. V části Areas/Identity/Pages/Account/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 na složku Služby 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 nástroj připojil k externí poštovní službě nebo nějaké jiné akci pro odeslání e-mailu.

  3. V souboru 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í se registruje EmailSender jako v IEmailSender systému injektáže závislostí.

Testová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-mailu.

  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 pořád přihlášení, vyberte možnost 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 budete přesměrováni na obrazovku potvrzení registrace . V podokně terminálu přejděte nahoru a 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>
    

    Kliknutím se stisknutou klávesou Ctrl+ přejděte na adresuURL. Zobrazí se potvrzovací obrazovka.

    Poznámka

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

  6. Vyberte Přihlásit se a přihlaste se pod novým uživatelem. 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 na databázi RazorPagesPizza a vyberte Nový dotaz. Na kartě, která se zobrazí, zadejte následující dotaz a spusťte ho stisknutím klávesy Ctrl+Shift+E .

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

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

    Uživatelské jméno 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 je zaregistrovaný před přidáním polí FirstName a LastName do schématu. AspNetUsers Přidružený záznam tabulky tedy 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, které jste udělali ve formuláři pro správu profilu.

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

  2. Vyberte odkaz Hello, ! a přejděte na formulář pro správu profilu.

    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í. Vyberte Uložit.

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

  4. Aplikaci zastavíte stisknutím ctrl+C v podokně terminálu v editoru VS Code.

Souhrn

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