Exercício – Personalizar Identidade
Na unidade anterior, você aprendeu como a personalização funciona no ASP.NET Core Identity. Nesta unidade, você estende o modelo de dados de identidade e faz as alterações de interface do usuário correspondentes.
Personalizar a interface do usuário da conta de usuário
Nesta seção, você criará e personalizará os arquivos da interface do usuário de identidade para serem usados no lugar da Biblioteca de Classes Razor padrão.
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"
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).Account.Manage.Index
é a página de gerenciamento de perfil. Esta página é modificada posteriormente nesta unidade.Account.Register
é a página de registo do utilizador. Esta página também é modificada nesta unidade.Account.Manage.EnableAuthenticator
eAccount.ConfirmEmail
são andaimes mas não modificados nesta unidade.
Gorjeta
Execute o seguinte comando na raiz do projeto para exibir valores válidos para a
--files
opção:dotnet aspnet-codegenerator identity --listFiles
Os seguintes ficheiros são adicionados ao diretório 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
- A opção
Estender IdentityUser
Você recebe um novo requisito para armazenar os nomes dos usuários. Como a classe padrão IdentityUser
não contém propriedades para nomes e sobrenomes, você precisa estender a RazorPagesPizzaUser
classe.
Faça as seguintes alterações em Areas/Identity/Data/RazorPagesPizzaUser.cs:
Adicione as propriedades
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; }
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 padrão de é atribuído uma vez que o contexto nulo está habilitado neste projeto e as propriedades são cadeias destring.Empty
caracteres 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 de modelo são feitas, as alterações de acompanhamento devem ser feitas no banco de dados.
Certifique-se de que todas as suas alterações são salvas.
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 o banco de dados para analisar o efeito da migração do
UpdateUser
EF Core no esquema daAspNetUsers
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 de caracteres não anuláveis. As linhas existentes mostram cadeias vazias nas novas colunas.
Personalizar o formulário de registo do utilizador
Você adicionou novas colunas para FirstName
e LastName
. Agora você precisa editar a interface do usuário para exibir campos correspondentes no formulário de registro.
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" 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>
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>
UserManager.GetUserAsync(User)
Retorna um objeto anulável RazorPagesPizzaUser
. O operador null-conditional ?.
é usado para acessar as FirstName
propriedades e LastName
somente se o RazorPagesPizzaUser
objeto não for nulo.
Personalizar o formulário de gestão de perfil
Você adicionou os novos campos ao formulário de registro de usuário, mas também deve adicioná-los ao formulário de gerenciamento de perfil para que os usuários existentes possam editá-los.
Em Areas/Identity/Pages/Account/Manage/Index.cshtml, adicione a seguinte markup realçada. Guardar as suas alterações.
<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>
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; } /// <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 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
. Guardar as suas 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 do e-mail de confirmação
Na primeira vez que você testou o aplicativo, você registrou um usuário e, em seguida, clicou em um link para simular a confirmação do endereço de e-mail do usuário. Para enviar um e-mail de confirmação real , você precisa criar uma implementação e registrá-la no sistema de injeção de IEmailSender dependência. Para simplificar, sua implementação nesta unidade não envia e-mails para um servidor SMTP (Simple Mail Transfer Protocol). Ele apenas grava o conteúdo do e-mail no console.
Como você vai visualizar o e-mail em texto sem formatação no console, altere a mensagem gerada para excluir texto codificado em 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 Explorer, clique com o botão direito do mouse na pasta RazorPagesPizza\Services e crie um novo arquivo chamado EmailSender.cs. Abra o ficheiro e adicione-o ao 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 de IEmailSender que grava o conteúdo da mensagem no console. Em uma implementação do mundo real,
SendEmailAsync
se conectaria a um serviço de correio externo ou alguma outra ação para enviar e-mail.No 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 precedente
EmailSender
regista-se como umIEmailSender
no sistema de injeção de dependência.
Testar as alterações ao formulário de registo
Isso é tudo! Vamos testar as alterações no formulário de inscrição e no e-mail de confirmação.
Certifique-se de que guardou todas as alterações.
No painel de terminal, crie o projeto e execute o aplicativo com
dotnet run
o .No seu browser, navegue para a aplicação. Selecione Sair se estiver conectado.
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 registrar, você será redirecionado para a tela de confirmação de registro. No painel do terminal, role para cima para encontrar a saída do console semelhante à 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 até o URL com +o clique Ctrl. A tela de confirmação é exibida.
Nota
Se você estiver usando o GitHub Codespaces, talvez seja necessário adicionar
-7192
à primeira parte da URL encaminhada. Por exemplo,scaling-potato-5gr4j4-7192.preview.app.github.dev
.Selecione Login e entre com o novo usuário. 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 mouse no banco de dados RazorPagesPizza e selecione Nova consulta. Na guia exibida, digite a seguinte consulta e pressione 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 Joana Heinrich O primeiro usuário registrado antes de adicionar
FirstName
eLastName
ao esquema. Portanto, o registro de tabela associadoAspNetUsers
não tem dados nessas colunas.
Testar as alterações ao formulário de gestão de perfil
Você também deve testar as alterações feitas no formulário de gerenciamento de perfil.
Na aplicação Web, inicie sessão com o primeiro utilizador que criou.
Selecione o link Olá, ! para navegar até o formulário de gerenciamento de perfil.
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 Guardar.
O cabeçalho da aplicação é atualizado para Hello, [Nome próprio] [Apelido]!.
Para parar o aplicativo, pressione Ctrl+C no painel de terminal no VS Code.
Resumo
Nesta unidade, você personalizou a Identidade para armazenar informações personalizadas do usuário. Você também personalizou o e-mail de confirmação. Na próxima unidade, você aprenderá sobre como implementar a autenticação multifator no Identity.