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

Dokončeno

Architektura Identity standardně reprezentuje uživatele pomocí třídy IdentityUser. Jeden ze způsobů, jak rozšířit data zaznamenaná během registrace, spočívá ve vytvoření třídy odvozené od IdentityUser. V této lekci se vytvoří odvozená třída s názvem ContosoPetsUser. ContosoPetsUser bude obsahovat vlastnosti pro uložení jména a příjmení uživatele.

odvozenou třídu IdentityUser.

Ke shromáždění dalších informací o profilu uživatele se také vyžadují změny uživatelského rozhraní. Následující postup vysvětluje postup shromáždění jména a příjmení registrovaného uživatele.

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

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

    dotnet aspnet-codegenerator identity \
        --dbContext ContosoPetsAuth \
        --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register" \
        --userClass ContosoPetsUser \
        --force
    

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

    • Parametr --dbContext informuje nástroj o existující třídě ContosoPetsAuth 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 ContosoPetsUser odvozené od třídy IdentityUser.
    • Parametr --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 zobrazíte platné hodnoty parametru --files:

    dotnet aspnet-codegenerator identity --listFiles
    

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

    • Data/
      • ContosoPetsUser.cs
    • Pages/
      • _ViewImports.cshtml
      • Account/
        • _ViewImports.cshtml
        • Register.cshtml
        • Register.cshtml.cs
        • Manage/
          • _ManageNav.cshtml
          • _ViewImports.cshtml
          • EnableAuthenticator.cshtml
          • EnableAuthenticator.cshtml.cs
          • Index.cshtml
          • Index.cshtml.cs
          • ManageNavPages.cs

    Soubor Data/ContosoPetsAuth.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 ContosoPetsAuth teď odkazuje na nově vytvořený typ uživatele ContosoPetsUser:

    public class ContosoPetsAuth : IdentityDbContext<ContosoPetsUser>
    

    Byl vygenerován kód stránky Razor EnableAuthenticator, který bude upraven později v tomto modulu.

  2. V metodě Configure souboru Areas/Identity/IdentityHostingStartup.cs musí být volání AddDefaultIdentity informováno o novém typu uživatele architektury Identity. Začleňte následující zvýrazněnou změnu a soubor uložte.

    services.AddDefaultIdentity<ContosoPetsUser>()
        .AddDefaultUI()
        .AddEntityFrameworkStores<ContosoPetsAuth>();
    
  3. Aktualizací souboru Pages/Shared/_LoginPartial.cshtml začleňte následující zvýrazněné změny. Uložte provedené změny.

    @using Microsoft.AspNetCore.Identity
    @using ContosoPets.Ui.Areas.Identity.Data
    @inject SignInManager<ContosoPetsUser> SignInManager
    @inject UserManager<ContosoPetsUser> 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 ContosoPetsUser. Kvůli překladu odkazů ContosoPetsUser byla přidána direktiva @using.

    Soubor Pages/Shared/_LoginPartial.cshtml je fyzicky umístěný mimo oblast Identity. V důsledku toho nebyl tento soubor aktualizován automaticky pomocí nástroje pro generování kódu. Příslušné změny musely 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 vytvoří znovu s odkazy na novou třídu ContosoPetsUser.

  4. Aktualizujte soubor Areas/Identity/Data/ContosoPetsUser.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 ContosoPetsUser : IdentityUser
      {
          [Required]
          [MaxLength(100)]
          public string FirstName { get; set; }
      
          [Required]
          [MaxLength(100)]
          public string LastName { get; set; }
      }
      

      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 [Required] rovněž nastavuje omezení, že sloupec v podkladové databázová tabulce nesmí obsahovat hodnotu null. 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.

    2. Na začátek souboru přidejte následující příkaz using. Uložte provedené změny.

      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

  1. 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 (1,005ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      ALTER TABLE "AspNetUsers" ADD "FirstName" character varying(100) NOT NULL DEFAULT '';
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
        Executed DbCommand (517ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
        ALTER TABLE "AspNetUsers" ADD "LastName" character varying(100) NOT NULL DEFAULT '';
    
    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'';
    

    Provedením následujících kroků analyzujte dopad migrace EF Core UpdateUser na schéma tabulky AspNetUsers. Budete tak mít přehled o dopadu, jaký má rozšíření datového modelu Identity na podkladové úložiště dat.

  1. Spuštěním následujícího příkazu zobrazte schéma tabulky:

    db -c '\d "AspNetUsers"'
    

    Zobrazí se následující výstup:

                                        Table "public.AspNetUsers"
            Column        |           Type           | Collation | Nullable |        Default
    ----------------------+--------------------------+-----------+----------+-----------------------
     Id                   | text                     |           | not null |
     UserName             | character varying(256)   |           |          |
     NormalizedUserName   | character varying(256)   |           |          |
     Email                | character varying(256)   |           |          |
     NormalizedEmail      | character varying(256)   |           |          |
     EmailConfirmed       | boolean                  |           | not null |
     PasswordHash         | text                     |           |          |
     SecurityStamp        | text                     |           |          |
     ConcurrencyStamp     | text                     |           |          |
     PhoneNumber          | text                     |           |          |
     PhoneNumberConfirmed | boolean                  |           | not null |
     TwoFactorEnabled     | boolean                  |           | not null |
     LockoutEnd           | timestamp with time zone |           |          |
     LockoutEnabled       | boolean                  |           | not null |
     AccessFailedCount    | integer                  |           | not null |
     FirstName            | character varying(100)   |           | not null | ''::character varying
     LastName             | character varying(100)   |           | not null | ''::character varying
    

    Vlastnosti FirstName a LastName ve třídě ContosoPetsUser odpovídají sloupcům FirstName a LastName v předchozím výstupu. Kvůli atributům [MaxLength(100)] byl k oběma sloupcům přiřazen datový typ character varying(100). Kvůli atributům [Required] bylo přidáno omezení, že nesmí být použita hodnota null.

  2. Posuňte se v příkazovém prostředí dolů, dokud se nezobrazí následující informace o indexu:

    Indexes:
        "PK_AspNetUsers" PRIMARY KEY, btree ("Id")
        "UserNameIndex" UNIQUE, btree ("NormalizedUserName")
        "EmailIndex" btree ("NormalizedEmail")
    

    Index PK_AspNetUsers ukazuje, že sloupec Id je jedinečným identifikátorem uživatelského účtu.

  3. Stisknutím klávesy q ukončete prohlížeč textu v příkazovém prostředí.

  1. Spuštěním následujícího příkazu zobrazte schéma tabulky:

    db -Q "SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH AS MAX_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='AspNetUsers'" -Y 20
    

    Zobrazí se následující výstup:

    COLUMN_NAME          IS_NULLABLE DATA_TYPE            MAX_LENGTH
    -------------------- ----------- -------------------- -----------
    Id                   NO          nvarchar                     450
    UserName             YES         nvarchar                     256
    NormalizedUserName   YES         nvarchar                     256
    Email                YES         nvarchar                     256
    NormalizedEmail      YES         nvarchar                     256
    EmailConfirmed       NO          bit                         NULL
    PasswordHash         YES         nvarchar                      -1
    SecurityStamp        YES         nvarchar                      -1
    ConcurrencyStamp     YES         nvarchar                      -1
    PhoneNumber          YES         nvarchar                      -1
    PhoneNumberConfirmed NO          bit                         NULL
    TwoFactorEnabled     NO          bit                         NULL
    LockoutEnd           YES         datetimeoffset              NULL
    LockoutEnabled       NO          bit                         NULL
    AccessFailedCount    NO          int                         NULL
    FirstName            NO          nvarchar                     100
    LastName             NO          nvarchar                     100
    

    Vlastnosti FirstName a LastName ve třídě ContosoPetsUser odpovídají sloupcům FirstName a LastName v předchozím výstupu. Kvůli atributům [MaxLength(100)] byl k oběma sloupcům přiřazen datový typ nvarchar(100). Kvůli atributům [Required] bylo přidáno omezení, že nesmí být použita hodnota null. U existujících řádků jsou v nových sloupcích prázdné řetězce.

  2. Spuštěním následujícího příkazu zobrazte primární klíč tabulky:

    db -i $setupWorkingDirectory/list-aspnetusers-pk.sql -Y 15
    

    Následující výstup ukazuje, že sloupec Id je jedinečným identifikátorem uživatelského účtu:

    Table           Column          Primary key
    --------------- --------------- ---------------
    AspNetUsers     Id              PK_AspNetUsers
    

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

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

    <form asp-route-returnUrl="@Model.ReturnUrl" method="post">
        <h4>Create a new account.</h4>
        <hr />
        <div asp-validation-summary="All" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="Input.FirstName"></label>
            <input asp-for="Input.FirstName" class="form-control" />
            <span asp-validation-for="Input.FirstName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Input.LastName"></label>
            <input asp-for="Input.LastName" class="form-control" />
            <span asp-validation-for="Input.LastName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Input.Email"></label>
            <input asp-for="Input.Email" class="form-control" />
            <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; }
      
          [Required]
          [EmailAddress]
          [Display(Name = "Email")]
          public string Email { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
          [DataType(DataType.Password)]
          [Display(Name = "Password")]
          public string Password { get; set; }
      
          [DataType(DataType.Password)]
          [Display(Name = "Confirm password")]
          [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
          public string ConfirmPassword { 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 ContosoPetsUser. Proveďte následující zvýrazněné změny:

      public async Task<IActionResult> OnPostAsync(string returnUrl = null)
      {
          returnUrl = returnUrl ?? Url.Content("~/");
          if (ModelState.IsValid)
          {
              var user = new ContosoPetsUser
              {
                  FirstName = Input.FirstName,
                  LastName = Input.LastName,
                  UserName = Input.Email,
                  Email = Input.Email,
              };
              var result = await _userManager.CreateAsync(user, Input.Password);
              if (result.Succeeded)
              {
      

      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:

@using Microsoft.AspNetCore.Identity
@using ContosoPets.Ui.Areas.Identity.Data
@inject SignInManager<ContosoPetsUser> SignInManager
@inject UserManager<ContosoPetsUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    ContosoPetsUser 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>
    <li class="nav-item">
        <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new { area = "" })">
            <button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

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

  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="All" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="Input.FirstName"></label>
            <input asp-for="Input.FirstName" class="form-control" />
            <span asp-validation-for="Input.FirstName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Input.LastName"></label>
            <input asp-for="Input.LastName" class="form-control" />
            <span asp-validation-for="Input.LastName" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="Username"></label>
            <input asp-for="Username" class="form-control" disabled />
        </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(ContosoPetsUser 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)
              {
                  var userId = await _userManager.GetUserIdAsync(user);
                  throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
              }
          }
      
          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.

Sestavení, nasazení a otestování

  1. Spuštěním následujícího příkazu sestavte aplikaci:

    dotnet build --no-restore
    

    Parametr --no-restore je použitý, protože od posledního sestavení nebyly přidány žádné balíčky NuGet. Proces sestavení vynechá obnovení balíčků NuGet a úspěšně se dokončí bez upozornění. Pokud se sestavení nezdaří, zkontrolujte výstupní informace o odstraňování potíží.

  2. Spuštěním následujícího příkazu nasaďte aplikaci do Azure App Service:

    az webapp up
    
  3. V prohlížeči přejděte na aplikaci. Pokud jste pořád přihlášení, vyberte možnost odhlášení.

    Tip

    Pokud potřebujete adresu URL vaší aplikace, zobrazíte ji následujícím příkazem:

    echo $webAppUrl
    
  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.

    Po registraci jste přesměrováni na domovskou stránku. Záhlaví aplikace teď obsahuje Hello, [jméno] [příjmení]!.

  5. Spuštěním následujícího příkazu potvrďte, že jméno a příjmení jsou uložené v databázi:

    db -c 'SELECT "UserName", "Email", "FirstName", "LastName" FROM "AspNetUsers"'
    

    Zobrazí se varianta následujícího výstupu:

             UserName          |            Email          | FirstName | LastName
    ---------------------------+---------------------------+-----------+----------
     kai.klein@contoso.com     | kai.klein@contoso.com     |           |
     jana.heinrich@contoso.com | jana.heinrich@contoso.com | Jana      | Heinrich
    (2 rows)
    
    db -Q "SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers" -Y 25
    

    Zobrazí se varianta následujícího výstupu:

    UserName                  Email                     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. Následkem toho neobsahuje přidružený záznam tabulky AspNetUsers v těchto sloupcích žádná data.

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

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

  2. Kliknutím na odkaz Hello, ! 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í]!.