Exercice - Personnaliser Identity

Effectué

Dans l’unité précédente, vous avez appris comment fonctionne la personnalisation dans ASP.NET Core Identity. Dans cette unité, vous étendez le modèle de données Identity et apportez les modifications correspondantes dans l’interface utilisateur.

Personnaliser les données du compte d’utilisateur

Dans cette section, vous allez créer et personnaliser les fichiers de l’interface utilisateur d’Identity pour les utiliser à la place de la bibliothèque de classes Razor par défaut.

  1. Ajoutez les fichiers d’inscription utilisateur à modifier dans le projet :

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

    Dans la commande précédente :

    • L’option --dbContext permet à l’outil de prendre connaissance de la classe dérivée de DbContext existante appelée RazorPagesPizzaAuth.
    • L’option --files spécifie une liste de fichiers uniques séparés par des points-virgules à ajouter à la zone Identity.
    • L’option --userClass aboutit à la création d’une classe dérivée de IdentityUser appelée RazorPagesPizzaUser.
    • L’option --force entraîne le remplacement des fichiers existants dans la zone Identity.

    Conseil

    Exécutez la commande suivante à partir de la racine du projet pour voir les valeurs valides pour l’option --files : dotnet aspnet-codegenerator identity --listFiles

    Les fichiers suivants sont ajoutés au répertoire Areas/Identity :

    • 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

    Par ailleurs, le fichier Data/RazorPagesPizzaAuth.cs, qui existait avant l’exécution de la commande précédente, a été remplacé parce que l’option --force a été utilisée. La déclaration de la classe RazorPagesPizzaAuth référence maintenant le type d’utilisateur nouvellement créé de RazorPagesPizzaUser :

    public class RazorPagesPizzaAuth : IdentityDbContext<RazorPagesPizzaUser>
    

    Les pages Razor EnableAuthenticator et ConfirmEmail ont été générées automatiquement, même si elles ne sont modifiées qu’à une étape ultérieure du module.

  2. Dans Program.cs, l’appel à AddDefaultIdentity doit être informé du nouveau type d’utilisateur Identity. Incorporez les changements en surbrillance suivants. (Exemple retouché pour une meilleure lisibilité.)

    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. Mettez à jour Pages/Shared/_LoginPartial.cshtml pour incorporer les changements en surbrillance suivants en haut. Enregistrez vos changements.

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

    Les changements précédents mettent à jour le type d’utilisateur passé à la fois à SignInManager<T> et à UserManager<T> dans les directives @inject. Au lieu du type IdentityUser par défaut, l’utilisateur RazorPagesPizzaUser est maintenant référencé. La directive @using a été ajoutée pour résoudre les références RazorPagesPizzaUser.

    Pages/Shared/_LoginPartial.cshtml se trouve physiquement en dehors de la zone Identity. Ainsi, le fichier n’a pas été mis à jour automatiquement par l’outil de génération automatique. Les changements appropriés doivent être apportés manuellement.

    Conseil

    L’alternative à la modification manuelle du fichier _LoginPartial.cshtml consiste à le supprimer avant d’exécuter l’outil de génération automatique. Le fichier _LoginPartial.cshtml est recréé avec des références à la nouvelle classe RazorPagesPizzaUser.

  4. Mettez à jour Areas/Identity/Data/RazorPagesPizzaUser.cs pour prendre en charge le stockage et la récupération des données de profil utilisateur supplémentaires. Apportez les changements suivants :

    1. Ajoutez les propriétés FirstName et 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;
      }
      

      Les propriétés de l’extrait de code précédent représentent les colonnes supplémentaires à créer dans la table AspNetUsers sous-jacente. Les deux propriétés sont obligatoires et sont donc annotées avec l’attribut [Required]. De plus, l’attribut [MaxLength] indique qu’une longueur maximale de 100 caractères est autorisée. Le type de données de la colonne de la table sous-jacente est défini en conséquence. Une valeur par défaut string.Empty est affectée, car le contexte nullable est activé dans ce projet et les propriétés sont des chaînes non nullables.

    2. Ajoutez l’instruction using suivante au début du fichier.

      using System.ComponentModel.DataAnnotations;
      

      Le code précédent résout les attributs d’annotation de données appliqués aux propriétés FirstName et LastName.

Mettre à jour la base de données

Maintenant que les changements du modèle ont été apportés, les changements associés doivent être apportés à la base de données.

  1. Vérifiez que tous vos changements sont enregistrés.

  2. Créez et appliquez une migration EF Core pour mettre à jour le magasin de données sous-jacent :

    dotnet ef migrations add UpdateUser
    dotnet ef database update
    

    La migration EF Core UpdateUser a appliqué un script de changement de DDL au schéma de la table AspNetUsers. Plus précisément, les colonnes FirstName et LastName ont été ajoutées, comme indiqué dans l’extrait de sortie de la migration suivant :

    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. Examinez la base de données pour analyser l’effet de la UpdateUsermigration EF CoreAspNetUsers sur le schéma de la table.

    Dans le volet SQL Server, développez le nœud Colonnes dans la table dbo.AspNetUsers.

    Capture d’écran du schéma de la table AspNetUsers.

    Les propriétés FirstName et LastName de la classe RazorPagesPizzaUser correspondent aux colonnes FirstName et LastName de l’image précédente. Un type de données de nvarchar(100) a été assigné à chacune des deux colonnes en raison des attributs [MaxLength(100)]. La contrainte non null a été ajoutée parce que FirstName et LastName dans la classe sont des chaînes non nullables. Les lignes existantes montrent des chaînes vides dans les nouvelles colonnes.

Personnaliser le formulaire d’inscription d’utilisateur

Vous avez ajouté de nouvelles colonnes pour FirstName et LastName. Vous devez maintenant modifier l’interface utilisateur pour afficher les champs correspondants sur le formulaire d’inscription.

  1. Dans Areas/Identity/Pages/Account/Register.cshtml, ajoutez le balisage en surbrillance suivant :

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

    Avec le balisage précédent, les zones de texte Prénom et Nom sont ajoutées au formulaire d’inscription d’utilisateur.

  2. Dans Areas/Identity/Pages/Account/Register.cshtml.cs, ajoutez une prise en charge pour les zones de texte des noms.

    1. Ajoutez les propriétés FirstName et LastName dans la classe imbriquée 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; }
      

      Les attributs [Display] définissent le texte de l’étiquette à associer aux zones de texte.

    2. Modifiez la méthode OnPostAsync pour définir les propriétés FirstName et LastName sur l’objet RazorPagesPizza. Ajoutez les lignes en surbrillance suivantes :

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

      Le changement précédent définit les propriétés FirstName et LastName sur l’entrée utilisateur du formulaire d’inscription.

Personnaliser l’en-tête de site

Mettez à jour Pages/Shared/_LoginPartial.cshtml pour afficher le nom et le prénom collectés pendant l’inscription de l’utilisateur. Les lignes en surbrillance dans l’extrait de code suivant sont nécessaires :

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

Personnaliser le formulaire de gestion des profils

Vous avez ajouté les nouveaux champs au formulaire d’inscription utilisateur, mais vous devez également les ajouter au formulaire de gestion des profils afin que les utilisateurs existants puissent les modifier.

  1. Dans Areas/Identity/Pages/Account/Manage/Index.cshtml, ajoutez le balisage en surbrillance suivant. Enregistrez vos modifications.

    <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. Dans Areas/Identity/Pages/Account/Manage/Index.cshtml.cs, apportez les changements suivants pour prendre en charge les zones de texte des noms.

    1. Ajoutez les propriétés FirstName et LastName dans la classe imbriquée 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; }
      
          [Phone]
          [Display(Name = "Phone number")]
          public string PhoneNumber { get; set; }
      }
      
    2. Incorporez les changements en surbrillance dans la méthode 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
          };
      }
      

      Le code précédent prend en charge la récupération du prénom et du nom pour les afficher dans les zones de texte correspondantes du formulaire de gestion des profils.

    3. Incorporez les changements en surbrillance dans la méthode OnPostAsync. Enregistrez vos changements.

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

      Le code précédent prend en charge la mise à jour du prénom et du nom dans la table AspNetUsers de la base de données.

Configurer l’expéditeur de l’e-mail de confirmation

Pour envoyer l’e-mail de confirmation, vous devez créer une implémentation de IEmailSender et l’inscrire dans le système d’injection de dépendances. Pour simplifier les choses, votre implémentation n’envoie pas d’e-mail à un serveur SMTP en fait. Elle écrit juste le contenu de l’e-mail dans la console.

  1. Étant donné que vous allez afficher l’e-mail en texte brut dans la console, vous devez modifier le message généré pour exclure le texte codé en HTML. Dans Areas/Identity/Pages/Account/Register.cshtml.cs, recherchez le code suivant :

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

    Remplacez-le par :

    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
        $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
    
  2. Dans le volet Explorateur, cliquez avec le bouton droit sur le dossier Services et créez un fichier appelé EmailSender.cs. Ouvrez le fichier et ajoutez le code suivant :

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

    Le code précédent crée une implémentation de IEmailSender qui écrit le contenu du message dans la console. Dans une vraie implémentation, SendEmailAsync se connecterait à un service de messagerie externe ou effectuerait une quelqu’autre action pour envoyer un e-mail.

  3. Dans Program.cs, ajoutez les lignes mises en surbrillance :

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

    Le précédent inscrit EmailSender comme IEmailSender dans le système d’injection de dépendances.

Tester les changements du formulaire d’inscription

C’est tout ! Nous allons tester les changements du formulaire d’inscription et de l’e-mail de confirmation.

  1. Vérifiez que vous avez enregistré tous vos changements.

  2. Dans le volet du terminal, générez le projet et exécutez l’application avec dotnet run.

  3. Dans votre navigateur, accédez à l’application. Sélectionnez Se déconnecter si vous êtes toujours connecté.

  4. Sélectionnez Inscrire et utilisez le formulaire mis à jour pour inscrire un nouvel utilisateur.

    Notes

    Les contraintes de validation sur les champs Prénom et Nom reflètent les annotations de données sur les propriétés FirstName et LastName de InputModel.

  5. Après l’inscription, vous êtes redirigé vers l’écran de confirmation d’inscription. Dans le volet du terminal, faites défiler vers le haut pour rechercher la sortie de la console qui ressemble à ce qui suit :

    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>
    

    Accédez à l’URL avec Ctrl+Clic. L’écran de confirmation s’affiche.

    Notes

    Si vous utilisez GitHub Codespaces, vous devrez peut-être ajouter -7192 à la première partie de l’URL transférée. Par exemple : scaling-potato-5gr4j4-7192.preview.app.github.dev.

  6. Sélectionnez Connexion et connectez-vous avec le nouvel utilisateur. L’en-tête de l’application contient maintenant Bonjour [Prénom] [Nom] !.

  7. Dans le volet SQL Server de VS Code, cliquez avec le bouton droit sur la base de données RazorPagesPizza, puis sélectionnez Nouvelle requête. Sous l’onglet qui s’affiche, entrez la requête suivante et appuyez sur Ctrl+Maj+E pour l’exécuter.

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

    Un onglet avec des résultats similaires à ce qui suit s’affiche :

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

    Le premier utilisateur s’est inscrit avant d’ajouter FirstName et LastName au schéma. Ainsi, l’enregistrement de la table AspNetUsers associé ne contient pas de données dans ces colonnes.

Tester les changements apportés au formulaire de gestion des profils

Vous devez également tester les changements que vous avez apportés au formulaire de gestion des profils.

  1. Dans l’application web, connectez-vous avec le premier utilisateur que vous avez créé.

  2. Sélectionnez le lien Hello, ! pour accéder au formulaire de gestion des profils.

    Notes

    Le lien ne s’affiche pas correctement parce que la ligne de la table AspNetUsers pour cet utilisateur ne contient pas de valeurs pour FirstName et LastName.

  3. Entrez des valeurs valides pour Prénom et Nom. Sélectionnez Enregistrer.

    L’en-tête de l’application est mis à jour avec Bonjour [Prénom] [Nom] !.

  4. Appuyez sur Ctrl+C dans le volet du terminal de VS Code pour arrêter l’application.

Résumé

Dans cette unité, vous avez personnalisé Identity pour stocker des informations utilisateur personnalisées. Vous avez également personnalisé l’e-mail de confirmation. Dans la prochaine unité, vous allez apprendre à implémenter l’authentification multifacteur dans Identity.