Ejercicio: personalización de Identity
De forma predeterminada, Identity representa un usuario con una clase IdentityUser. Una manera de extender los datos que se capturan en el momento del registro es crear una clase derivada de IdentityUser. En esta unidad, se crea una clase derivada llamada ContosoPetsUser. ContosoPetsUser contendrá las propiedades para almacenar el nombre de pila y los apellidos del usuario.

También se requieren cambios en la interfaz de usuario para recopilar información adicional de perfil de usuario. En los pasos siguientes se explica el proceso de recopilación del nombre de pila y los apellidos del usuario registrado.
Personalización de los datos de la cuenta de usuario
Agregue los archivos de registro del usuario que se van a modificar en el proyecto:
dotnet aspnet-codegenerator identity \ --dbContext ContosoPetsAuth \ --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register" \ --userClass ContosoPetsUser \ --forceEn el comando anterior:
- La opción
--dbContextproporciona a la herramienta conocimientos de la clase derivada deDbContextexistente llamadaContosoPetsAuth. - La opción
--filesespecifica una lista delimitada por signos de punto y coma de archivos únicos que se van a agregar al área de Identity. - La opción
--userClassda como resultado la creación de una clase derivada deIdentityUserllamadaContosoPetsUser. - La opción
--forcehace que se sobrescriban los archivos existentes en el área Identity.
Sugerencia
Ejecute el siguiente comando desde la raíz del proyecto para ver los valores válidos de la opción
--files:dotnet aspnet-codegenerator identity --listFilesLos archivos siguientes se agregan al directorio Areas/Identity:
- Data/
- ContosoPetsUser.cs
- Pages/
- _ViewImports.cshtml
- Account/
- _ViewImports.cshtml
- Register.cshtml
- Register.cshtml.cs
- Manage/
- _ManageNav.cshtml
- _ViewImports.cshtml
- EnableAuthenticator.cshtml
- EnableAuthenticator.cshtml.cs
- Index.cshtml
- Index.cshtml.cs
- ManageNavPages.cs
Además, el archivo Data/ContosoPetsAuth.cs, que existía antes de ejecutar el comando anterior, se ha sobrescrito porque se ha usado la opción
--force. La declaración de claseContosoPetsAuthahora hace referencia al tipo de usuario recién creadoContosoPetsUser:public class ContosoPetsAuth : IdentityDbContext<ContosoPetsUser>A la página de Razor EnableAuthenticator se le ha aplicado scaffolding, aunque no se modificará en el módulo hasta más adelante.
- La opción
En el método
Configurede Areas/Identity/IdentityHostingStartup.cs, la llamada aAddDefaultIdentitydebe tener constancia del nuevo tipo de usuario de Identity. Incorpore el cambio resaltado siguiente y guarde el archivo.services.AddDefaultIdentity<ContosoPetsUser>() .AddDefaultUI() .AddEntityFrameworkStores<ContosoPetsAuth>();Actualice Pages/Shared/_LoginPartial.cshtml para incorporar los cambios resaltados siguientes. Guarde los cambios.
@using Microsoft.AspNetCore.Identity @using ContosoPets.Ui.Areas.Identity.Data @inject SignInManager<ContosoPetsUser> SignInManager @inject UserManager<ContosoPetsUser> UserManager <ul class="navbar-nav">Los cambios anteriores actualizan el tipo de usuario pasado a
SignInManager<T>yUserManager<T>en las directivas@inject. En lugar del tipo predeterminadoIdentityUser, ahora se hace referencia al usuarioContosoPetsUser. Se ha agregado la directiva@usingpara resolver las referencias deContosoPetsUser.Pages/Shared/_LoginPartial.cshtml se encuentra físicamente fuera del área Identity. Por lo tanto, la herramienta de scaffolding no ha actualizado automáticamente el archivo. Los cambios apropiados se han realizado manualmente.
Sugerencia
Como alternativa a la edición manual del archivo _LoginPartial.cshtml, este se puede eliminar antes de ejecutar la herramienta de scaffolding. El archivo _LoginPartial.cshtml se volverá a crear con referencias a la clase nueva
ContosoPetsUser.Actualice Areas/Identity/Data/ContosoPetsUser.cs para admitir el almacenamiento y la recuperación de los datos adicionales de perfil de usuario. Realice los cambios siguientes:
Agregue las propiedades
FirstNameyLastName:public class ContosoPetsUser : IdentityUser { [Required] [MaxLength(100)] public string FirstName { get; set; } [Required] [MaxLength(100)] public string LastName { get; set; } }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]. El atributo[Required]también da como resultado una restricción distinta de NULL en la columna de la tabla de base de datos subyacente. 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.Agregue la instrucción
usingsiguiente en la parte superior del archivo. Guarde los cambios.using System.ComponentModel.DataAnnotations;El código anterior resuelve los atributos de anotación de datos aplicados a las propiedades
FirstNameyLastName.
Actualización de la base de datos
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 updateLa migración de EF Core
UpdateUserha aplicado un script de cambios de DDL al esquema de la tablaAspNetUsers. En concreto, se han agregado las columnasFirstNameyLastName, tal y como se muestra en el fragmento siguiente de resultado de migración:info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (1,005ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] ALTER TABLE "AspNetUsers" ADD "FirstName" character varying(100) NOT NULL DEFAULT ''; info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (517ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] ALTER TABLE "AspNetUsers" ADD "LastName" character varying(100) NOT NULL DEFAULT '';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'';Complete los pasos siguientes para analizar el impacto de la migración de EF Core
UpdateUseren el esquema de la tablaAspNetUsers. Obtendrá información sobre el impacto que puede extender el modelo de datos de Identity en el almacén de datos subyacente.
Ejecute el comando siguiente para ver el esquema de la tabla:
db -c '\d "AspNetUsers"'Se muestra el resultado siguiente:
Table "public.AspNetUsers" Column | Type | Collation | Nullable | Default ----------------------+--------------------------+-----------+----------+----------------------- Id | text | | not null | UserName | character varying(256) | | | NormalizedUserName | character varying(256) | | | Email | character varying(256) | | | NormalizedEmail | character varying(256) | | | EmailConfirmed | boolean | | not null | PasswordHash | text | | | SecurityStamp | text | | | ConcurrencyStamp | text | | | PhoneNumber | text | | | PhoneNumberConfirmed | boolean | | not null | TwoFactorEnabled | boolean | | not null | LockoutEnd | timestamp with time zone | | | LockoutEnabled | boolean | | not null | AccessFailedCount | integer | | not null | FirstName | character varying(100) | | not null | ''::character varying LastName | character varying(100) | | not null | ''::character varyingLas propiedades
FirstNameyLastNamede la claseContosoPetsUsercorresponden a las columnasFirstNameyLastNamedel resultado anterior. Se ha asignado un tipo de datoscharacter varying(100)a cada una de las dos columnas debido a los atributos[MaxLength(100)]. Se ha agregado la restricción distinta a NULL debido a los atributos[Required].Desplácese hacia abajo en el shell de comandos hasta que se muestre la información de índice siguiente:
Indexes: "PK_AspNetUsers" PRIMARY KEY, btree ("Id") "UserNameIndex" UNIQUE, btree ("NormalizedUserName") "EmailIndex" btree ("NormalizedEmail")El índice
PK_AspNetUsersmuestra que la columnaIdes el identificador único de una cuenta de usuario.Presione la tecla q para salir del visor de texto en el shell de comandos.
Ejecute el comando siguiente para ver el esquema de la tabla:
db -Q "SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH AS MAX_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='AspNetUsers'" -Y 20Se muestra el resultado siguiente:
COLUMN_NAME IS_NULLABLE DATA_TYPE MAX_LENGTH -------------------- ----------- -------------------- ----------- Id NO nvarchar 450 UserName YES nvarchar 256 NormalizedUserName YES nvarchar 256 Email YES nvarchar 256 NormalizedEmail YES nvarchar 256 EmailConfirmed NO bit NULL PasswordHash YES nvarchar -1 SecurityStamp YES nvarchar -1 ConcurrencyStamp YES nvarchar -1 PhoneNumber YES nvarchar -1 PhoneNumberConfirmed NO bit NULL TwoFactorEnabled NO bit NULL LockoutEnd YES datetimeoffset NULL LockoutEnabled NO bit NULL AccessFailedCount NO int NULL FirstName NO nvarchar 100 LastName NO nvarchar 100Las propiedades
FirstNameyLastNamede la claseContosoPetsUsercorresponden a las columnasFirstNameyLastNamedel resultado 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 distinta a NULL debido a los atributos[Required]. Las filas existentes muestran cadenas vacías en las nuevas columnas.Ejecute el comando siguiente para ver la clave principal de la tabla:
db -i $setupWorkingDirectory/list-aspnetusers-pk.sql -Y 15El siguiente resultado muestra que la columna
Ides el identificador único de una cuenta de usuario:Table Column Primary key --------------- --------------- --------------- AspNetUsers Id PK_AspNetUsers
Personalización del formulario de registro de usuarios
En Areas/Identity/Pages/Account/Register.cshtml, agregue el marcado resaltado siguiente:
<form asp-route-returnUrl="@Model.ReturnUrl" method="post"> <h4>Create a new account.</h4> <hr /> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="Input.FirstName"></label> <input asp-for="Input.FirstName" class="form-control" /> <span asp-validation-for="Input.FirstName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Input.LastName"></label> <input asp-for="Input.LastName" class="form-control" /> <span asp-validation-for="Input.LastName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Input.Email"></label> <input asp-for="Input.Email" class="form-control" /> <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
FirstNameyLastNamea 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; } [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { 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
OnPostAsyncpara establecer las propiedadesFirstNameyLastNameen el objetoContosoPetsUser. Realice los cambios resaltados siguientes:public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); if (ModelState.IsValid) { var user = new ContosoPetsUser { FirstName = Input.FirstName, LastName = Input.LastName, UserName = Input.Email, Email = Input.Email, }; var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) {El cambio anterior establece las propiedades
FirstNameyLastNameen 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:
@using Microsoft.AspNetCore.Identity
@using ContosoPets.Ui.Areas.Identity.Data
@inject SignInManager<ContosoPetsUser> SignInManager
@inject UserManager<ContosoPetsUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
ContosoPetsUser 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>
<li class="nav-item">
<form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new { area = "" })">
<button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
Personalización del formulario de administración de perfiles
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="All" class="text-danger"></div> <div class="form-group"> <label asp-for="Input.FirstName"></label> <input asp-for="Input.FirstName" class="form-control" /> <span asp-validation-for="Input.FirstName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Input.LastName"></label> <input asp-for="Input.LastName" class="form-control" /> <span asp-validation-for="Input.LastName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Username"></label> <input asp-for="Username" class="form-control" disabled /> </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
FirstNameyLastNamea 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; } [Phone] [Display(Name = "Phone number")] public string PhoneNumber { get; set; } }Incorpore los cambios resaltados en el método
LoadAsync:private async Task LoadAsync(ContosoPetsUser 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) { var userId = await _userManager.GetUserIdAsync(user); throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'."); } } 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
AspNetUsersde la base de datos.
Compilación, implementación y prueba
Ejecute el siguiente comando para compilar la aplicación:
dotnet build --no-restoreLa opción
--no-restorese incluye porque no se han agregado paquetes NuGet desde la última compilación. El proceso de compilación omite la restauración de paquetes NuGet y se realiza correctamente sin ninguna advertencia. Si se produce un error en la compilación, compruebe la salida con el fin de obtener información para solucionar problemas.Implemente la aplicación en Azure App Service ejecutando el comando siguiente:
az webapp upEn el explorador, vaya a la aplicación. Seleccione Cerrar sesión si todavía tiene la sesión iniciada.
Sugerencia
Si necesita la dirección URL de la aplicación, puede mostrarla con el comando siguiente:
echo $webAppUrlSeleccione 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
FirstNameyLastNamedeInputModel.Después del registro, se le redirigirá a la página principal. El encabezado de la aplicación ahora contiene Hola, [Nombre de pila] [Apellidos].
Ejecute el comando siguiente para confirmar que el nombre de pila y los apellidos están almacenados en la base de datos:
db -c 'SELECT "UserName", "Email", "FirstName", "LastName" FROM "AspNetUsers"'Se muestra una variación del resultado siguiente:
UserName | Email | FirstName | LastName ---------------------------+---------------------------+-----------+---------- kai.klein@contoso.com | kai.klein@contoso.com | | jana.heinrich@contoso.com | jana.heinrich@contoso.com | Jana | Heinrich (2 rows)db -Q "SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers" -Y 25Se muestra una variación del resultado siguiente:
UserName Email FirstName LastName ------------------------- ------------------------- ------------------------- ------------------------- kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana HeinrichEl primer usuario registrado antes de agregar
FirstNameyLastNameal esquema. Por consiguiente, el registro de la tablaAspNetUsersasociada no tiene datos en esas columnas.
Prueba de los cambios en el formulario de administración de perfiles
En la aplicación web, inicie sesión con el primer usuario que se ha creado.
Haga clic en el enlace Hola para navegar al formulario de administración de perfiles.
Nota
El vínculo no se muestra correctamente porque la fila de la tabla
AspNetUserspara este usuario no contiene valores paraFirstNameyLastName.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].
¿Necesita ayuda? Consulte nuestra guía de solución de problemas o notifique un problema para enviar comentarios específicos.