Esercizio - Personalizzare Identity
Nell'unità precedente si è appreso come funziona la personalizzazione in ASP.NET Core Identity. In questa unità verrà esteso il modello di dati Identity e verranno apportate le modifiche corrispondenti all'interfaccia utente.
Personalizzare l’interfaccia utente dell'account utente
In questa sezione si creeranno e si personalizzeranno i file dell'interfaccia utente di Identity da usare in sostituzione della libreria di classi Razor predefinita.
Aggiungere al progetto i file di registrazione utente da modificare:
dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail"
Nel comando precedente:
- L'opzione
--dbContext
fornisce allo strumento informazioni sulla classe derivata daDbContext
esistente denominataRazorPagesPizzaAuth
. - L'opzione
--files
specifica un elenco delimitato da punto e virgola di file univoci da aggiungere all'area Identity.Account.Manage.Index
è la pagina di gestione del profilo. Questa pagina viene modificata più avanti in questa unità.Account.Register
è la pagina di registrazione dell'utente. Anche questa pagina viene modificata più avanti in questa unità.- Per
Account.Manage.EnableAuthenticator
eAccount.ConfirmEmail
è stato eseguito lo scaffolding ma non sono stati modificati in questa unità.
Suggerimento
Eseguire il comando seguente dalla radice del progetto per visualizzare i valori validi per l'opzione
--files
:dotnet aspnet-codegenerator identity --listFiles
I file seguenti vengono aggiunti alla directory 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
- L'opzione
Estendere IdentityUser
Viene assegnato un nuovo requisito per archiviare i nomi degli utenti. Poiché la classe IdentityUser
predefinita non contiene proprietà per nome e cognome, è necessario estendere la classe RazorPagesPizzaUser
.
Modificare Areas/Identity/Data/RazorPagesPizzaUser.cs nel modo seguente:
Aggiungere le proprietà
FirstName
eLastName
: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; }
Le proprietà nel frammento di codice precedente rappresentano colonne aggiuntive da creare nella tabella
AspNetUsers
sottostante. Entrambe le proprietà sono obbligatorie e pertanto annotate con l'attributo[Required]
. Inoltre, l'attributo[MaxLength]
indica che è consentita una lunghezza massima di 100 caratteri. Il tipo di dati della colonna della tabella sottostante viene definito di conseguenza. Viene assegnato un valore predefinito distring.Empty
dal momento che in questo progetto è abilitato il contesto che ammette i valori Null e le proprietà sono stringhe che non ammettono i valori Null.Aggiungere l'istruzione
using
seguente all'inizio di questo file.using System.ComponentModel.DataAnnotations;
Il codice precedente risolve gli attributi di annotazione dei dati applicati alle proprietà
FirstName
eLastName
.
Aggiornare il database
Ora che sono state apportate le modifiche al modello, è necessario apportare le modifiche opportune al database.
Assicurarsi che tutte le modifiche vengano salvate.
Creare e applicare una migrazione di EF Core per aggiornare l'archivio dati sottostante:
dotnet ef migrations add UpdateUser dotnet ef database update
La migrazione di EF Core
UpdateUser
ha applicato uno script delle modifiche DDL allo schema della tabellaAspNetUsers
. In particolare sono state aggiunte le colonneFirstName
eLastName
, come illustrato nell'estratto dell'output della migrazione riportato di seguito: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'';
Esaminare il database per analizzare l'effetto della migrazione di
UpdateUser
di EF Core sullo schema della tabellaAspNetUsers
.Nel riquadro SQL Server espandere il nodo Colonne nella tabella dbo.AspNetUsers.
Le proprietà
FirstName
eLastName
nella classeRazorPagesPizzaUser
corrispondono alle colonneFirstName
eLastName
nell'immagine precedente. Il tipo di datinvarchar(100)
è stato assegnato alle due colonne a causa degli attributi[MaxLength(100)]
. È stato aggiunto il vincolo non Null perchéFirstName
eLastName
nella classe sono stringhe che non ammettono i valori Null. Le righe esistenti mostrano stringhe vuote nelle nuove colonne.
Personalizzare il modulo di registrazione utente
Sono state aggiunte nuove colonne per FirstName
e LastName
. A questo punto, è necessario modificare l'interfaccia utente per visualizzare i campi corrispondenti nel modulo di registrazione.
In Areas/Identity/Pages/Account/Register.cshtml aggiungere il markup evidenziato seguente:
<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>
Con il markup precedente, al modulo di registrazione utente vengono aggiunte le caselle di testo First name (Nome) e Last name (Cognome).
In Areas/Identity/Pages/Account/Register.cshtml.cs aggiungere il supporto per le caselle di testo del nome.
Aggiungere le proprietà
FirstName
eLastName
alla classe annidataInputModel
: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; }
Gli attributi
[Display]
definiscono il testo dell'etichetta da associare alle caselle di testo.Modificare il metodo
OnPostAsync
in modo da impostare le proprietàFirstName
eLastName
sull'oggettoRazorPagesPizza
. Aggiungere le righe evidenziate seguenti: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);
La modifica precedente imposta le proprietà
FirstName
eLastName
sull'input dell'utente dal modulo di registrazione.
Personalizzare l'intestazione del sito
Aggiornare Pages/Shared/_LoginPartial.cshtml in modo da visualizzare il nome e il cognome raccolti durante la registrazione dell'utente. Sono necessarie le righe evidenziate nel frammento di codice seguente:
<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)
restituisce un oggetto RazorPagesPizzaUser
che ammette i valori Null. L'operatore ?.
condizionale Null viene usato per accedere alle proprietà FirstName
e LastName
solo se l'oggetto RazorPagesPizzaUser
non è Null.
Personalizzare il modulo di gestione dei profili
I nuovi campi sono stati aggiunti al modulo di registrazione utente, ma è necessario aggiungerli anche al modulo di gestione dei profili in modo che gli utenti esistenti possano modificarli.
In Areas/Identity/Pages/Account/Manage/Index.cshtml aggiungere il markup evidenziato seguente. Salva le modifiche.
<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>
In Areas/Identity/Pages/Account/Manage/Index.cshtml.cs apportare le modifiche seguenti per supportare le caselle di testo del nome.
Aggiungere le proprietà
FirstName
eLastName
alla classe annidataInputModel
: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; } }
Incorporare le modifiche evidenziate nel metodo
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 }; }
Il codice precedente supporta il recupero del nome e del cognome per visualizzarli nelle caselle di testo corrispondenti del modulo di gestione dei profili.
Incorporare le modifiche evidenziate nel metodo
OnPostAsync
. Salva le modifiche.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(); }
Il codice precedente supporta l'aggiornamento del nome e del cognome nella tabella
AspNetUsers
del database.
Configurare il mittente del messaggio di posta elettronica di conferma
La prima volta che è stata testata l'app, è stato registrato un utente e quindi è stato fatto clic su un collegamento per simulare la conferma dell'indirizzo di posta elettronica dell'utente. Per inviare il messaggio di posta elettronica di conferma effettivo, è necessario creare un'implementazione di IEmailSender e registrarla nel sistema di inserimento delle dipendenze. Per semplicità, in questa unità l'implementazione non invia effettivamente messaggi di posta elettronica a un server SMTP (Simple Mail Transfer Protocol). ma scriverà semplicemente il contenuto del messaggio di posta elettronica nella console.
Dal momento il messaggio di posta elettronica verrà visualizzato come testo normale nella console, è consigliabile modificare il messaggio generato in modo da escludere il testo con codifica HTML. In Areas/Identity/Pages/Account/Register.cshtml.cs individuare il codice seguente:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
Sostituirlo con:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
Nel riquadro Esplora risorse, fare clic con il pulsante destro del mouse sulla cartella RazorPagesPizza\Services e creare un nuovo file denominato EmailSender.cs. Aprire il file e aggiungere il codice seguente:
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; } }
Il codice precedente crea un'implementazione di IEmailSender che scrive il contenuto del messaggio nella console. In un'implementazione reale
SendEmailAsync
si connette a un servizio di posta elettronica esterno o a un'altra azione per inviare messaggi di posta elettronica.In Program.cs aggiungere le righe evidenziate:
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();
Quella precedente registra
EmailSender
comeIEmailSender
nel sistema di inserimento delle dipendenze.
Testare le modifiche apportate al modulo di registrazione
È tutto. A questo punto verranno testate le modifiche apportate al modulo di registrazione e al messaggio di posta elettronica di conferma.
Assicurarsi di avere salvato tutte le modifiche.
Nel riquadro del terminale compilare il progetto ed eseguire l'app con il comando
dotnet run
.Passare all'app nel browser. Selezionare Disconnetti se si è ancora connessi.
Selezionare Register (Registra) e usare il modulo aggiornato per registrare un nuovo utente.
Nota
I vincoli di convalida sui campi First name (Nome) e Last name (Cognome) riflettono le annotazioni dei dati nelle proprietà
FirstName
eLastName
diInputModel
.Dopo la registrazione, si viene reindirizzati alla schermata Conferma registrazione. Nel riquadro del terminale scorrere verso l'alto per individuare l'output della console simile al seguente:
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>
Per passare all'URL, premere CTRL+clic. Viene visualizzata la schermata di conferma.
Nota
Se si usa GitHub Codespaces, potrebbe essere necessario aggiungere
-7192
alla prima parte dell'URL inoltrato. Ad esempio:scaling-potato-5gr4j4-7192.preview.app.github.dev
.Selezionare Login (Accedi) e accedere con il nuovo utente. L'intestazione dell'app ora contiene Hello, [Nome] [Cognome]!.
Nel riquadro SQL Server in VS Code fare clic on il pulsante destro del mouse sul database RazorPagesPizza e selezionare Nuova query. Nella scheda visualizzata immettere la query seguente e premere CTRL+MAIUSC+E per eseguirla.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
Verrà visualizzata una scheda con risultati simili ai seguenti:
UserName E-mail FirstName LastName kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich Il primo utente registrato prima dell'aggiunta di
FirstName
eLastName
allo schema. Di conseguenza, il record della tabellaAspNetUsers
associato non contiene dati in quelle colonne.
Testare le modifiche apportate al modulo di gestione dei profili
È anche opportuno testare le modifiche apportate al modulo di gestione del profilo.
Nell'app Web accedere con l'account del primo utente creato.
Selezionare il collegamento Hello, ! per passare al modulo di gestione del profilo.
Nota
Il collegamento non viene visualizzato correttamente perché la riga della tabella
AspNetUsers
per questo utente non contiene valori perFirstName
eLastName
.Immettere valori validi per il nome e il cognome. Seleziona Salva.
L'intestazione dell'app viene aggiornata in Hello, [Nome] [Cognome]!.
Per arrestare l’app, premere CTRL+C nel riquadro del terminale in VS Code.
Riepilogo
In questa unità si è personalizzato Identity in modo da archiviare informazioni utente personalizzate. Si è anche personalizzato il messaggio di posta elettronica di conferma. Nell'unità successiva verrà illustrato come implementare l'autenticazione a più fattori in Identità.