Exercice - Personnaliser Identity
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.
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 deDbContext
existante appeléeRazorPagesPizzaAuth
. - 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 deIdentityUser
appeléeRazorPagesPizzaUser
. - 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 classeRazorPagesPizzaAuth
référence maintenant le type d’utilisateur nouvellement créé deRazorPagesPizzaUser
: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.
- L’option
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();
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 typeIdentityUser
par défaut, l’utilisateurRazorPagesPizzaUser
est maintenant référencé. La directive@using
a été ajoutée pour résoudre les référencesRazorPagesPizzaUser
.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
.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 :
Ajoutez les propriétés
FirstName
etLastName
: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éfautstring.Empty
est affectée, car le contexte nullable est activé dans ce projet et les propriétés sont des chaînes non nullables.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
etLastName
.
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.
Vérifiez que tous vos changements sont enregistrés.
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 tableAspNetUsers
. Plus précisément, les colonnesFirstName
etLastName
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'';
Examinez la base de données pour analyser l’effet de la
UpdateUser
migration 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.
Les propriétés
FirstName
etLastName
de la classeRazorPagesPizzaUser
correspondent aux colonnesFirstName
etLastName
de l’image précédente. Un type de données denvarchar(100)
a été assigné à chacune des deux colonnes en raison des attributs[MaxLength(100)]
. La contrainte non null a été ajoutée parce queFirstName
etLastName
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.
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.
Dans Areas/Identity/Pages/Account/Register.cshtml.cs, ajoutez une prise en charge pour les zones de texte des noms.
Ajoutez les propriétés
FirstName
etLastName
dans la classe imbriquéeInputModel
: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.Modifiez la méthode
OnPostAsync
pour définir les propriétésFirstName
etLastName
sur l’objetRazorPagesPizza
. 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
etLastName
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.
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>
Dans Areas/Identity/Pages/Account/Manage/Index.cshtml.cs, apportez les changements suivants pour prendre en charge les zones de texte des noms.
Ajoutez les propriétés
FirstName
etLastName
dans la classe imbriquéeInputModel
: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; } }
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.
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.
É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}");
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.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
commeIEmailSender
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.
Vérifiez que vous avez enregistré tous vos changements.
Dans le volet du terminal, générez le projet et exécutez l’application avec
dotnet run
.Dans votre navigateur, accédez à l’application. Sélectionnez Se déconnecter si vous êtes toujours connecté.
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
etLastName
deInputModel
.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
.Sélectionnez Connexion et connectez-vous avec le nouvel utilisateur. L’en-tête de l’application contient maintenant Bonjour [Prénom] [Nom] !.
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
etLastName
au schéma. Ainsi, l’enregistrement de la tableAspNetUsers
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.
Dans l’application web, connectez-vous avec le premier utilisateur que vous avez créé.
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 pourFirstName
etLastName
.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] !.
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.