Exercício – Personalizar Identidade
Na unidade anterior, aprendeu como funciona a personalização no ASP.NET Core Identity. Nesta unidade, vai expandir o modelo de dados identidade e efetuar as alterações de IU correspondentes.
Personalizar os dados da conta de utilizador
Nesta secção, vai criar e personalizar os ficheiros de IU de Identidade a utilizar em vez da Biblioteca de Classes do Razor predefinida.
Adicione os ficheiros de registo de utilizador a serem modificados no projeto:
dotnet aspnet-codegenerator identity --dbContext RazorPagesPizzaAuth --files "Account.Manage.EnableAuthenticator;Account.Manage.Index;Account.Register;Account.ConfirmEmail" --userClass RazorPagesPizzaUser --force
No comando anterior:
- A opção
--dbContext
dá à ferramenta conhecimentos da classe derivada deDbContext
com o nomeRazorPagesPizzaAuth
. - A opção
--files
especifica uma lista delimitada por pontos e vírgulas de ficheiros únicos a adicionar à área Identity (Identidade). - A opção
--userClass
resulta na criação de uma classe derivada deIdentityUser
com o nomeRazorPagesPizzaUser
. - A
--force
opção faz com que os ficheiros existentes na área Identidade sejam substituídos.
Dica
Execute o seguinte comando a partir da raiz do projeto para ver valores válidos para a opção
--files
:dotnet aspnet-codegenerator identity --listFiles
Os seguintes ficheiros são adicionados ao diretório 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
Além disso, o ficheiro Data/RazorPagesPizzaAuth.cs, que já existia antes de executar o comando anterior, foi substituído porque foi utilizada a opção
--force
. A declaração de classeRazorPagesPizzaAuth
referencia agora o tipo de utilizador recentemente criado deRazorPagesPizzaUser
:public class RazorPagesPizzaAuth : IdentityDbContext<RazorPagesPizzaUser>
As EnableAuthenticator páginas e ConfirmEmail do Razor foram estruturadas, embora só sejam modificadas mais tarde no módulo.
- A opção
No Program.cs, a chamada para
AddDefaultIdentity
tem de ser informada do novo tipo de utilizador Identidade. Incorpore as seguintes alterações realçadas. (Exemplo reformatado para legibilidade.)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();
Atualize Pages/Shared/_LoginPartial.cshtml para incorporar as seguintes alterações realçadas na parte superior. Guarde as alterações.
@using Microsoft.AspNetCore.Identity @using RazorPagesPizza.Areas.Identity.Data @inject SignInManager<RazorPagesPizzaUser> SignInManager @inject UserManager<RazorPagesPizzaUser> UserManager <ul class="navbar-nav">
As alterações anteriores atualizam o tipo de utilizador transmitido para
SignInManager<T>
eUserManager<T>
nas diretivas@inject
. Em vez do tipo predefinidoIdentityUser
, é agora referenciado o utilizadorRazorPagesPizzaUser
. A diretiva@using
foi adicionada para resolver as referênciasRazorPagesPizzaUser
.Pages/Shared/_LoginPartial.cshtml situa-se fisicamente fora da área Identity. Assim, o ficheiro não foi atualizado automaticamente pela ferramenta de estrutura. As alterações adequadas têm de ser feitas manualmente.
Dica
Como alternativa à edição manual do ficheiro _LoginPartial.cshtml, este pode ser eliminado antes de executar a ferramenta de estrutura. O _LoginPartial.cshtml ficheiro é recriado com referências à nova
RazorPagesPizzaUser
classe.Atualize Areas/Identity/Data/RazorPagesPizzaUser.cs para suportar o armazenamento e a obtenção de dados adicionais de perfis de utilizadores. Efetue as seguintes alterações:
Adicione as propriedades
FirstName
eLastName
:public class RazorPagesPizzaUser : IdentityUser { [Required] [MaxLength(100)] public string FirstName { get; set; } = string.Empty; [Required] [MaxLength(100)] public string LastName { get; set; } = string.Empty; }
As propriedades no fragmento anterior representam colunas adicionais a criar na tabela subjacente
AspNetUsers
. Ambas as propriedades são necessárias e, portanto, são anotadas com o atributo[Required]
. Além disso, o atributo[MaxLength]
indica que é permitido um comprimento máximo de 100 carateres. O tipo de dados da coluna da tabela subjacente é definido em conformidade. Um valor predefinido destring.Empty
é atribuído, uma vez que o contexto nulo está ativado neste projeto e as propriedades são cadeias não anuláveis.Adicione a seguinte instrução
using
na parte superior do ficheiro.using System.ComponentModel.DataAnnotations;
O código anterior resolve os atributos de anotação de dados aplicados às propriedades
FirstName
eLastName
.
Atualizar a base de dados
Agora que as alterações ao modelo foram efetuadas, as alterações que acompanham têm de ser efetuadas na base de dados.
Certifique-se de que todas as suas alterações são guardadas.
Crie e aplique uma migração EF Core para atualizar o arquivo de dados subjacente:
dotnet ef migrations add UpdateUser dotnet ef database update
A migração EF Core
UpdateUser
aplicou um script de alterações DDL ao esquema da tabelaAspNetUsers
. Especificamente, foram adicionadas as colunasFirstName
eLastName
, conforme visto no seguinte excerto de saída de migração: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 a base de dados para analisar o efeito da migração do
UpdateUser
EF Core noAspNetUsers
esquema da tabela.No painel SQL Server, expanda o nó Colunas no dbo. Tabela AspNetUsers.
As
FirstName
propriedades eLastName
naRazorPagesPizzaUser
classe correspondem àsFirstName
colunas eLastName
na imagem anterior. Um tipo de dados denvarchar(100)
foi atribuído a cada uma das duas colunas devido aos atributos[MaxLength(100)]
. A restrição não nula foi adicionada porqueFirstName
eLastName
na classe são cadeias não anuláveis. As linhas existentes mostram cadeias vazias nas novas colunas.
Personalizar o formulário de registo do utilizador
Adicionou novas colunas para FirstName
e LastName
. Agora, tem de editar a IU para apresentar campos correspondentes no formulário de registo.
Em Areas/Identity/Pages/Account/Register.cshtml, adicione a seguinte markup realçada:
<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>
Com a anterior markup, as caixas de texto Nome próprio e Apelido são adicionadas ao formulário de registo de utilizador.
Em Areas/Identity/Pages/Account/Register.cshtml.cs, adicione suporte para caixas de texto de nome.
Adicione as propriedades
FirstName
eLastName
à classe aninhadaInputModel
: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; }
Os atributos
[Display]
definem o texto da etiqueta como estando associado às caixas de texto.Modifique o método
OnPostAsync
para definir as propriedadesFirstName
eLastName
no objetoRazorPagesPizza
. Adicione as seguintes linhas realçadas: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);
A alteração anterior define as propriedades
FirstName
eLastName
para a entrada de utilizador do formulário de registo.
Personalizar o cabeçalho do site
Atualize Pages/Shared/_LoginPartial.cshtml para mostrar o nome próprio e apelido recolhidos durante o registo do utilizador. As linhas realçadas no fragmento seguinte são necessárias:
<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>
Personalizar o formulário de gestão de perfil
Adicionou os novos campos ao formulário de registo do utilizador, mas também deve adicioná-los ao formulário de gestão de perfis para que os utilizadores existentes os possam editar.
Em Areas/Identity/Pages/Account/Manage/Index.cshtml, adicione a seguinte markup realçada. Guarde as alterações.
<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>
Em Areas/Identity/Pages/Account/Manage/Index.cshtml.cs, faça as seguintes alterações para suportar as caixas de texto de nome.
Adicione as propriedades
FirstName
eLastName
à classe aninhadaInputModel
: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 as alterações realçadas no 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 }; }
O código anterior suporta a obtenção de nomes próprios e apelidos para apresentação nas caixas de texto correspondentes do formulário de gestão de perfil.
Incorpore as alterações realçadas no método
OnPostAsync
. Guarde as alterações.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(); }
O código anterior suporta a atualização dos primeiros nomes e apelidos na tabela
AspNetUsers
da base de dados.
Configurar o remetente de e-mail de confirmação
Para enviar o e-mail de confirmação, tem de criar uma implementação e IEmailSender registá-la no sistema de injeção de dependências. Para manter as coisas simples, a implementação não envia e-mails para um servidor SMTP. Apenas escreve o conteúdo do e-mail na consola do .
Uma vez que vai ver o e-mail em texto simples na consola, deve alterar a mensagem gerada para excluir texto codificado por HTML. Em Áreas/Identidade/Páginas/Conta/Register.cshtml.cs, localize o seguinte código:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
Altere-o para:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by visiting the following URL:\r\n\r\n{callbackUrl}");
No painel Explorador , clique com o botão direito do rato na pasta Serviços e crie um novo ficheiro com o nome EmailSender.cs. Abra o ficheiro e adicione o seguinte código:
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; } }
O código anterior cria uma implementação que IEmailSender escreve o conteúdo da mensagem na consola do . Numa implementação real,
SendEmailAsync
ligar-se-ia a um serviço de correio externo ou a qualquer outra ação para enviar e-mail.Em Program.cs, adicione as linhas realçadas:
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();
O registo anterior é
EmailSender
registado como umIEmailSender
no sistema de injeção de dependências.
Testar as alterações ao formulário de registo
É tudo! Vamos testar as alterações ao formulário de registo e ao e-mail de confirmação.
Certifique-se de que guardou todas as suas alterações.
No painel de terminal, crie o projeto e execute a aplicação com
dotnet run
.No seu browser, navegue para a aplicação. Selecione Logout (Terminar sessão) se ainda estiver ligado.
Selecione Register (Registar) e utilize o formulário atualizado para registar um novo utilizador.
Nota
As restrições de validação nos campos First name (Nome próprio) e Last name (Apelido) refletem as anotações de dados nas propriedades
FirstName
eLastName
deInputModel
.Depois de se registar, é redirecionado para o ecrã de confirmação Registar . No painel de terminal, desloque-se para cima para encontrar o resultado da consola semelhante ao seguinte:
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>
Navegue para o URL com Ctrl+clique. O ecrã de confirmação é apresentado.
Nota
Se estiver a utilizar o GitHub Codespaces, poderá ter de adicionar
-7192
à primeira parte do URL reencaminhado. Por exemplo,scaling-potato-5gr4j4-7192.preview.app.github.dev
.Selecione Iniciar sessão e inicie sessão com o novo utilizador. Agora, o cabeçalho da aplicação contém Hello, [Nome próprio] [Apelido]!.
No painel SQL Server no VS Code, clique com o botão direito do rato na base de dados RazorPagesPizza e selecione Nova consulta. No separador apresentado, introduza a seguinte consulta e prima Ctrl+Shift+E para executá-la.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
É apresentado um separador com resultados semelhantes aos seguintes:
Nome de Utilizador E-mail FirstName LastName kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich O primeiro utilizador registado antes de adicionar
FirstName
eLastName
ao esquema. Assim, o registo de tabela associadoAspNetUsers
não tem dados nessas colunas.
Testar as alterações ao formulário de gestão de perfil
Também deve testar as alterações efetuadas ao formulário de gestão de perfis.
Na aplicação Web, inicie sessão com o primeiro utilizador que criou.
Selecione a ligação Olá, ! para navegar para o formulário de gestão de perfis.
Nota
A ligação não é apresentada corretamente porque a linha da tabela
AspNetUsers
correspondente a este utilizador não contém valores paraFirstName
eLastName
.Introduza valores válidos para Nome próprio e Apelido. Selecione Save (Guardar).
O cabeçalho da aplicação é atualizado para Hello, [Nome próprio] [Apelido]!.
Prima Ctrl+C no painel de terminal no VS Code para parar a aplicação.
Resumo
Nesta unidade, personalizou a Identidade para armazenar informações de utilizador personalizadas. Também personalizou o e-mail de confirmação. Na próxima unidade, irá aprender a implementar a autenticação multifator na Identidade.