Ejercicio: personalización de Identity
En la unidad anterior, ha aprendido cómo funciona la personalización en Identity de ASP.NET Core. En esta unidad, se amplía el modelo de datos de Identity y se realizan los cambios correspondientes de la interfaz de usuario.
Personalización de los datos de la cuenta de usuario
En esta sección, va a crear y personalizar los archivos de la interfaz de usuario de Identity que se usarán en lugar de la biblioteca de clases de Razor predeterminada.
Agregue los archivos de registro del usuario que se van a modificar en el proyecto:
dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail"
En el comando anterior:
- La opción
--dbContext
proporciona a la herramienta conocimientos de la clase derivada deDbContext
existente llamadaRazorPagesPizzaAuth
. - La opción
--files
especifica una lista delimitada por signos de punto y coma de archivos únicos que se van a agregar al área de Identity.Account.Manage.Index
es la página de administración de perfiles. Esta página se modifica más adelante en esta unidad.Account.Register
es la página de registro de usuarios. Esta página también se modifica en esta unidad.- Se aplica scaffolding a
Account.Manage.EnableAuthenticator
yAccount.ConfirmEmail
, pero no se modifican en esta unidad.
Sugerencia
Ejecute el comando siguiente desde la raíz del proyecto para ver los valores válidos de la opción
--files
:dotnet aspnet-codegenerator identity --listFiles
Los archivos siguientes se agregan al directorio 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
- La opción
Extienda IdentityUser
Se le da un nuevo requisito para almacenar los nombres de los usuarios. Dado que la clase IdentityUser
predeterminada no contiene propiedades para los nombres y apellidos, debe extender la clase RazorPagesPizzaUser
.
Realice los siguientes cambios en Areas/Identity/Data/RazorPagesPizzaUser.cs:
Agregue las propiedades
FirstName
yLastName
: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; }
Las propiedades del fragmento anterior representan columnas adicionales que se van a crear en la tabla subyacente
AspNetUsers
. Ambas propiedades son necesarias y, por tanto, se anotan con el atributo[Required]
. Además, el atributo[MaxLength]
indica que se permite una longitud máxima de 100 caracteres. El tipo de datos de la columna de la tabla subyacente se define en consecuencia. Se asigna un valor predeterminado destring.Empty
porque el contexto que admite un valor NULL está habilitado en este proyecto y las propiedades son cadenas que no aceptan valores NULL.Agregue la instrucción
using
siguiente en la parte superior del archivo.using System.ComponentModel.DataAnnotations;
El código anterior resuelve los atributos de anotación de datos aplicados a las propiedades
FirstName
yLastName
.
Actualización de la base de datos
Ahora que se realizaron los cambios del modelo, se deben realizar cambios complementarios en la base de datos.
Asegúrese de haber guardado todos los cambios.
Cree y aplique una migración de EF Core para actualizar el almacén de datos subyacente:
dotnet ef migrations add UpdateUser dotnet ef database update
La migración de EF Core
UpdateUser
ha aplicado un script de cambios de DDL al esquema de la tablaAspNetUsers
. En concreto, se han agregado las columnasFirstName
yLastName
, tal y como se muestra en el fragmento siguiente de resultado de migración: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'';
Examine la base de datos para analizar el efecto de la migración de EF Core
UpdateUser
en el esquema de la tablaAspNetUsers
.En el panel SQL Server, expanda el nodo Columnas de la tabla dbo.AspNetUsers.
Las propiedades
FirstName
yLastName
de la claseRazorPagesPizzaUser
se corresponden a las columnasFirstName
yLastName
de la imagen anterior. Se ha asignado un tipo de datosnvarchar(100)
a cada una de las dos columnas debido a los atributos[MaxLength(100)]
. Se ha agregado la restricción no NULL porque en la clase,FirstName
yLastName
son cadenas que no aceptan valores NULL. Las filas existentes muestran cadenas vacías en las nuevas columnas.
Personalización del formulario de registro de usuarios
Ha agregado nuevas columnas para FirstName
y LastName
. Ahora debe editar la interfaz de usuario para mostrar los campos coincidentes en el formulario de registro.
En Areas/Identity/Pages/Account/Register.cshtml, agregue el marcado resaltado siguiente:
<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 el marcado anterior, se agregan los cuadros de texto Nombre de pila y Apellidos al formulario de registro del usuario.
En Areas/Identity/Pages/Account/Register.cshtml.cs, agregue compatibilidad para los cuadros de texto de nombre.
Agregue las propiedades
FirstName
yLastName
a la clase anidadaInputModel
: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; }
Los atributos
[Display]
definen el texto de la etiqueta que se va a asociar con los cuadros de texto.Modifique el método
OnPostAsync
para establecer las propiedadesFirstName
yLastName
en el objetoRazorPagesPizza
. Agregue las líneas resaltadas siguientes: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);
El cambio anterior establece las propiedades
FirstName
yLastName
en la entrada del usuario del formulario de registro.
Personalización del encabezado del sitio
Actualice Pages/Shared/_LoginPartial.cshtml para mostrar el nombre de pila y los apellidos recopilados durante el registro del usuario. Se necesitan las líneas resaltadas en el fragmento de código siguiente:
<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)
devuelve un objeto RazorPagesPizzaUser
que admite un valor NULL. El operador condicional null ?.
se usa para acceder a las propiedades FirstName
y LastName
solo si el objeto RazorPagesPizzaUser
no es NULL.
Personalización del formulario de administración de perfiles
Ha agregado los campos nuevos al formulario de registro de usuario, pero también debe agregarlos al formulario de administración de perfiles para que los usuarios existentes puedan editarlos.
En Areas/Identity/Pages/Account/Manage/Index.cshtml, agregue el marcado resaltado siguiente. Guarde los cambios.
<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>
En Areas/Identity/Pages/Account/Manage/Index.cshtml.cs, realice los cambios siguientes para admitir los cuadros de texto de nombre.
Agregue las propiedades
FirstName
yLastName
a la clase anidadaInputModel
: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; } }
Incorpore los cambios resaltados en el método
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 }; }
El código anterior admite la recuperación del nombre de pila y los apellidos que se muestran en los cuadros de texto correspondientes del formulario de administración de perfiles.
Incorpore los cambios resaltados en el método
OnPostAsync
. Guarde los cambios.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(); }
El código anterior admite la actualización del nombre de pila y los apellidos de la tabla
AspNetUsers
de la base de datos.
Configuración del remitente del correo electrónico de confirmación
La primera vez que probó la aplicación, registró un usuario y, después, hizo clic en un vínculo para simular la confirmación de la dirección de correo electrónico del usuario. Para enviar el correo electrónico de confirmación real, debe crear una implementación de IEmailSender y registrarla en el sistema de inserción de dependencias. Para simplificar las cosas, la implementación de esta unidad no envía realmente correo electrónico a un servidor de Protocolo simple de transferencia de correo (SMTP). Solo escribe el contenido del correo electrónico en la consola.
Como va a ver el correo electrónico en texto sin formato en la consola, debe cambiar el mensaje generado para excluir el texto codificado en HTML. En Areas/Identity/Pages/Account/Register.cshtml.cs, busque el código siguiente:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
Cámbielo por esto:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
En el panel Explorador, haga clic con el botón derecho en la carpeta RazorPagesPizza\Services y cree un archivo denominado EmailSender.cs. Abra el archivo y agregue el código siguiente:
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; } }
En el código anterior se crea una implementación de IEmailSender que escribe el contenido del mensaje en la consola. En una implementación real,
SendEmailAsync
se conectaría a un servicio de correo externo o a alguna otra acción para enviar correo electrónico.En Program.cs, agregue las líneas resaltadas:
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();
Lo anterior registra
EmailSender
comoIEmailSender
en el sistema de inserción de dependencias.
Prueba de los cambios en el formulario de registro
Eso es todo. Ahora se probarán los cambios en el formulario de registro y el correo electrónico de confirmación.
Asegúrese de guardar todos los cambios.
En el panel de terminal, compile el proyecto y ejecute la aplicación con
dotnet run
.En el explorador, vaya a la aplicación. Seleccione Cerrar sesión si tiene la sesión iniciada.
Seleccione Registrar y use el formulario actualizado para registrar un nuevo usuario.
Nota:
Las restricciones de validación en los campos Nombre de pila y Apellidos reflejan las anotaciones de datos en las propiedades
FirstName
yLastName
deInputModel
.Después de registrarse, se le redirigirá a la pantalla Registrar confirmación. En el panel de terminal, desplácese hacia arriba para buscar la salida de la consola similar a la siguiente:
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>
Presione Ctrl+clic para ir a la dirección URL. Se muestra la pantalla de confirmación.
Nota
Si usa GitHub Codespaces, puede que tenga que agregar
-7192
a la primera parte de la dirección URL reenviada. Por ejemplo,scaling-potato-5gr4j4-7192.preview.app.github.dev
.Seleccione Iniciar sesión e inicie sesión con el nuevo usuario. El encabezado de la aplicación ahora contiene Hola, [Nombre de pila] [Apellidos].
En el panel SQL Server de VS Code, haga clic con el botón derecho en la base de datos RazorPagesPizza y seleccione Nueva consulta. En la pestaña que aparece, escriba la siguiente consulta y presione Ctrl+Mayús+E para ejecutarla.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
Aparece una pestaña con resultados similares a los siguientes:
UserName Email Nombre LastName kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich Primer usuario registrado antes de agregar
FirstName
yLastName
al esquema. Por lo tanto, el registro de la tablaAspNetUsers
asociada no tiene datos en esas columnas.
Prueba de los cambios en el formulario de administración de perfiles
También debe probar los cambios realizados en el formulario de administración de perfiles.
En la aplicación web, inicie sesión con el primer usuario que ha creado.
Seleccione el vínculo Hola para ir al formulario de administración de perfiles.
Nota:
El vínculo no se muestra correctamente porque la fila de la tabla
AspNetUsers
para este usuario no contiene valores paraFirstName
yLastName
.Escriba valores válidos para los campos Nombre de pila y Apellidos. Seleccione Guardar.
El encabezado de la aplicación se actualiza a Hola, [Nombre de pila] [Apellidos].
Para detener la aplicación, presione Ctrl+C en el panel de terminal de VS Code.
Resumen
En esta unidad, ha personalizado Identity para almacenar información de usuario personalizada. También ha personalizado el correo electrónico de confirmación. En la siguiente unidad, aprenderá a implementar la autenticación multifactor en Identity.