Övning – Anpassa Identity

Slutförd

I föregående lektion lärde du dig hur anpassning fungerar i ASP.NET Core Identity. I den här lektionen utökar du identitetsdatamodellen och gör motsvarande ändringar i användargränssnittet.

Anpassa användargränssnittet för användarkontot

I det här avsnittet ska du skapa och anpassa de identitetsgränssnittsfiler som ska användas i stället för standardbiblioteket för Razor-klass.

  1. Lägg till användarregistreringsfilerna som ska ändras till projektet:

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

    I kommandot ovan:

    • Alternativet --dbContext tillhandahåller verktyget med kunskap om den befintliga DbContext-härledda klassen med namnet RazorPagesPizzaAuth.
    • Alternativet --files anger en semikolonavgränsad lista över unika filer som ska läggas till i området Identity.
      • Account.Manage.Index är profilhanteringssidan. Den här sidan ändras senare i den här lektionen.
      • Account.Register är sidan för användarregistrering. Den här sidan ändras också i den här lektionen.
      • Account.Manage.EnableAuthenticator och Account.ConfirmEmail är scaffolded men ändras inte i den här enheten.

    Dricks

    Kör följande kommando från projektroten för att visa giltiga värden för --files alternativet: dotnet aspnet-codegenerator identity --listFiles

    Följande filer läggs till i katalogen 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

Förlänga IdentityUser

Du får ett nytt krav för att lagra användarnas namn. Eftersom standardklassen IdentityUser inte innehåller egenskaper för för- och efternamn måste du utöka RazorPagesPizzaUser klassen.

Gör följande ändringar Areas/Identity/Data/RazorPagesPizzaUser.csi :

  1. Lägg till egenskaperna FirstName och 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;
    }
    

    Egenskaperna i det föregående kodavsnittet representerar ytterligare kolumner som ska skapas i den underliggande tabellen AspNetUsers. Båda egenskaperna krävs och kommenteras därför med attributet[Required]. Dessutom anger attributet [MaxLength] att en maxlängd på 100 tecken är tillåten. Den underliggande tabellkolumnens datatyp definieras enligt detta. Ett standardvärde string.Empty för tilldelas eftersom nullbar kontext är aktiverad i det här projektet och egenskaperna är icke-nullbara strängar.

  2. Lägg till följande using-instruktion överst i filen.

    using System.ComponentModel.DataAnnotations;
    

    Koden ovan löser dataanteckningsattributen som tillämpas på egenskaperna FirstName och LastName.

Uppdatera databasen

Nu när modelländringarna har gjorts måste tillhörande ändringar göras i databasen.

  1. Kontrollera att alla ändringar sparas.

  2. Skapa och tillämpa en EF Core-migrering för att uppdatera det underliggande datalagret:

    dotnet ef migrations add UpdateUser
    dotnet ef database update
    

    UpdateUser EF Core-migreringen tillämpade ett DDL-ändringsskript på AspNetUsers-tabellens schema. Mer specifikt lades kolumnerna FirstName och LastName, som visas i följande utdrag av migreringsutdata:

    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. Granska databasen för att analysera effekten av UpdateUser EF Core-migreringen på AspNetUsers tabellens schema.

    I fönstret SQL Server expanderar du noden Kolumnerdbo. AspNetUsers-tabell .

    Skärmbild av schemat för tabellen AspNetUsers.

    Egenskaperna FirstName och LastName i RazorPagesPizzaUser klassen motsvarar kolumnerna FirstName och LastName i föregående bild. Datatypen nvarchar(100) tilldelades till båda kolumnerna på grund av [MaxLength(100)]-attributen. Villkoret icke-null lades till eftersom FirstName och LastName i klassen är icke-nullbara strängar. Befintliga rader visar tomma strängar i de nya kolumnerna.

Anpassa användarregistreringsformuläret

Du har lagt till nya kolumner för FirstName och LastName. Nu måste du redigera användargränssnittet för att visa matchande fält i registreringsformuläret.

  1. I Areas/Identity/Pages/Account/Register.cshtml lägger du till följande markering:

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

    Med föregående markering läggs textfälten Förnamn och Efternamn till i användarregistreringsformuläret.

  2. I Areas/Identity/Pages/Account/Register.cshtml.cs lägger du till stöd för namntextrutorna.

    1. Lägg till egenskaperna FirstName och LastName i den kapslade klassen 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]-attributen definierar etikettexten som ska associeras med textrutorna.

    2. Ändra metoden OnPostAsync för att ange egenskaperna FirstName och LastName för objektet RazorPagesPizza. Lägg till följande markerade rader:

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

      Föregående ändringar anger egenskaperna FirstName och LastName för användarindata från registreringsformuläret.

Anpassa platshuvudet

Uppdatera Pages/Shared/_LoginPartial.cshtml för att visa det första och sista namnet som samlats in under användarregistreringen. De markerade raderna i följande kodavsnitt behövs:

<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) returnerar ett null-objekt RazorPagesPizzaUser . Den null-villkorsstyrda ?. operatorn används endast för att komma åt FirstName egenskaperna och LastName om RazorPagesPizzaUser objektet inte är null.

Anpassa profilhanteringsformuläret

Du har lagt till de nya fälten i användarregistreringsformuläret, men du bör också lägga till dem i profilhanteringsformuläret så att befintliga användare kan redigera dem.

  1. I Areas/Identity/Pages/Account/Manage/Index.cshtml lägger du till följande markering. Spara dina ändringar.

    <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. I Areas/Identity/Pages/Account/Manage/Index.cshtml.cs gör du följande ändringar för att stödja namntextrutorna.

    1. Lägg till egenskaperna FirstName och LastName i den kapslade klassen 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. Inkludera de markerade ändringarna i metoden 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
          };
      }
      

      Koden ovan stöder hämtning av för- och efternamn för visning i motsvarande textrutor i profilhanteringsformuläret.

    3. Inkludera de markerade ändringarna i metoden OnPostAsync. Spara dina ändringar.

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

      Koden ovan stöder uppdatering av för- och efternamnen i databasens AspNetUsers-tabell.

Konfigurera e-postbekräftelsens avsändare

Första gången du testade appen registrerade du en användare och klickade sedan på en länk för att simulera bekräftelse av användarens e-postadress. För att kunna skicka ett faktiskt bekräftelsemeddelande måste du skapa en implementering av IEmailSender och registrera den i beroendeinmatningssystemet. För att hålla det enkelt skickar implementeringen i den här enheten inte e-post till en SMTP-server (Simple Mail Transfer Protocol). Den skriver bara e-postinnehållet till konsolen.

  1. Eftersom du ska visa e-postmeddelandet i oformaterad text i konsolen bör du ändra det genererade meddelandet så att html-kodad text undantas. I Områden/Identitet/Sidor/Konto/Register.cshtml.cs hittar du följande kod:

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

    Ändra till:

    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
        $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
    
  2. Högerklicka på mappen RazorPagesPizza\Services i utforskarfönstret och skapa en ny fil med namnet EmailSender.cs. Öppna filen och lägg till följande 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;
        }
    }
    

    Föregående kod skapar en implementering av IEmailSender som skriver innehållet i meddelandet till konsolen. I en verklig implementering SendEmailAsync ansluter du till en extern e-posttjänst eller någon annan åtgärd för att skicka e-post.

  3. Lägg till de markerade raderna i 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();
    

    Föregående register som EmailSender en IEmailSender i beroendeinmatningssystemet.

Testa ändringarna i registreringsformuläret

Det är allt! Nu ska vi testa ändringarna i registreringsformuläret och e-postbekräftelsen.

  1. Kontrollera att du har sparat alla dina ändringar.

  2. I terminalfönstret skapar du projektet och kör appen med dotnet run.

  3. Gå till appen i en webbläsare. Välj Logga ut om du är inloggad.

  4. Välj Registrera och använd det uppdaterade formuläret för att registrera en ny användare.

    Kommentar

    Valideringsvillkoren för fälten Förnamn och Efternamn speglar de dataanteckningarna på egenskaperna FirstName och LastName för InputModel.

  5. När du har registrerat dig omdirigeras du till skärmen Registrera bekräftelse . Rulla uppåt i terminalfönstret för att hitta de konsolutdata som liknar följande:

    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>
    

    Gå till URL:en med Ctrl-klicka+. Bekräftelseskärmen visas.

    Kommentar

    Om du använder GitHub Codespaces kan du behöva lägga -7192 till den första delen av den vidarebefordrade URL:en. Exempel: scaling-potato-5gr4j4-7192.preview.app.github.dev

  6. Välj Logga in och logga in med den nya användaren. Appens sidhuvud innehåller nu Hello, [Förnamn] [Efternamn]!.

  7. I FÖNSTRET SQL Server i VS Code högerklickar du på RazorPagesPizza-databasen och väljer Ny fråga. På fliken som visas anger du följande fråga och trycker på Ctrl++E för att köra den.

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

    En flik med resultat som liknar följande visas:

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

    Den första användaren registrerade sig innan han eller hon lade till FirstName och LastName i schemat. Den associerade AspNetUsers tabellposten har därför inte data i dessa kolumner.

Testa ändringarna i profilhanteringsformuläret

Du bör också testa de ändringar som du har gjort i profilhanteringsformuläret.

  1. Logga in med den första användaren som du skapade i webbappen.

  2. Välj länken Hello, ! för att navigera till profilhanteringsformuläret.

    Kommentar

    Länken visas inte på rätt sätt eftersom AspNetUsers-tabellens rad för den här användaren inte innehåller värden för FirstName och LastName.

  3. Ange giltiga värden för Förnamn och Efternamn. Välj Spara.

    Appens sidhuvud uppdateras till Hello, [Förnamn] [Efternamn]!.

  4. Om du vill stoppa appen trycker du på Ctrl+C i terminalfönstret i VS Code.

Sammanfattning

I den här lektionen har du anpassat identiteten för att lagra anpassad användarinformation. Du har också anpassat bekräftelsemeddelandet. I nästa lektion får du lära dig hur du implementerar multifaktorautentisering i Identitet.