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"
    

    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.
      • Account.Manage.Index est la page de gestion des profils. Cette page est modifiée plus loin dans cette unité.
      • Account.Register est la page d’enregistrement de l’utilisateur(-trice). Cette page est également modifiée dans cette unité.
      • Account.Manage.EnableAuthenticator et Account.ConfirmEmail sont générés automatiquement, mais ne sont pas modifiés dans cette unité.

    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 :

    • 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

Étendre IdentityUser

Vous avez reçu une nouvelle exigence pour stocker les noms de vos utilisateurs. Étant donné que la classe IdentityUser par défaut ne contient pas de propriétés pour les prénoms et les noms, vous devez étendre la classe RazorPagesPizzaUser.

Dans Areas/Identity/Data/RazorPagesPizzaUser.cs, effectuez les changements suivants :

1. Add the `FirstName` and `LastName` properties:

    [!code-csharp[](../code/areas/identity/data/razorpagespizzauser.cs?highlight=3-5,7-9)]

    The properties in the preceding snippet represent additional columns to be created in the underlying `AspNetUsers` table. Both properties are required and are therefore annotated with the `[Required]` attribute. Additionally, the `[MaxLength]` attribute indicates that a maximum length of 100 characters is allowed. The underlying table column's data type is defined accordingly. A default value of `string.Empty` is assigned since nullable context is enabled in this project and the properties are non-nullable strings.

1. Add the following `using` statement to the top of the file.

    ```csharp
    using System.ComponentModel.DataAnnotations;
    ```

    The preceding code resolves the data annotation attributes applied to the `FirstName` and `LastName` properties.

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

    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>

UserManager.GetUserAsync(User) retourne un objet RazorPagesPizzaUser pouvant accepter la valeur Null. L’opérateur de ?. conditionnel null est utilisé pour accéder aux propriétés FirstName et LastName uniquement si l’objet RazorPagesPizzaUser n’est pas null.

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" 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. 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; }
      
          /// <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. 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

La première fois que vous avez testé l’application, vous avez inscrit un utilisateur, puis cliqué sur un lien pour simuler la confirmation de l’adresse e-mail de l’utilisateur(-trice). Pour envoyer un véritable courriel de confirmation, vous devez créer une implémentation de IEmailSender et l’enregistrer dans le système d’injection de dépendances. Pour simplifier les choses, votre implémentation dans cette unité n’enverra pas d’e-mail à un serveur SMTP en fait. Elle écrira 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 RazorPagesPizza\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.