Exercício – Personalizar o Identity
Na unidade anterior, você aprendeu como funciona a personalização na Identidade do ASP.NET Core. Nesta unidade, você estenderá o modelo de dados do Identity e fará as alterações correspondentes na interface do usuário.
Personalizar os dados da conta de usuário
Nesta seção, você vai criar e personalizar os arquivos de interface do usuário de identidade a serem usados em vez da Biblioteca de Classes Razor padrão.
Adicione ao projeto os arquivos de registro do usuário a serem modificados:
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
fornece à ferramenta o conhecimento da classe derivada deDbContext
existente chamadaRazorPagesPizzaAuth
. - A opção
--files
especifica uma lista delimitada por ponto e vírgula de arquivos exclusivos a serem adicionados à área Identity. - A opção
--userClass
resulta na criação de uma classe derivada deIdentityUser
chamadaRazorPagesPizzaUser
. - A opção
--force
faz com que os arquivos existentes na área Identity sejam substituídos.
Dica
Execute o seguinte comando na raiz do projeto a fim de exibir valores válidos para a opção
--files
:dotnet aspnet-codegenerator identity --listFiles
Os seguintes arquivos 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 arquivo Data/RazorPagesPizzaAuth.cs, que existia antes da execução do comando anterior, foi substituído porque a opção
--force
foi usada. A declaração de classeRazorPagesPizzaAuth
agora referencia o tipo de usuário recém-criadoRazorPagesPizzaUser
:public class RazorPagesPizzaAuth : IdentityDbContext<RazorPagesPizzaUser>
As Razor Pages EnableAuthenticator e ConfirmEmail foram gerada por scaffolding, mas só serão modificadas mais adiante no módulo.
- A opção
Em Program.cs, a chamada a
AddDefaultIdentity
precisa estar ciente do novo tipo de usuário do Identity. Incorpore as alterações realçadas a seguir. (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 alterações realçadas a seguir no topo. Salve 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 usuário passado para
SignInManager<T>
eUserManager<T>
e nas diretivas@inject
. Em vez do tipoIdentityUser
padrão, o usuárioRazorPagesPizzaUser
agora é referenciado. A diretiva@using
foi adicionada para resolver as referências deRazorPagesPizzaUser
.Pages/Shared/_LoginPartial.cshtml está fisicamente localizado fora da área Identity. Portanto, o arquivo não foi atualizado automaticamente pela ferramenta de scaffolding. Foi necessário fazer as alterações apropriadas manualmente.
Dica
Como alternativa à edição manual do arquivo _LoginPartial.cshtml, ele pode ser excluído antes da execução da ferramenta de scaffolding. O arquivo _LoginPartial.cshtml será recriado com referências à nova classe
RazorPagesPizzaUser
.Atualize Areas/Identity/Data/RazorPagesPizzaUser.cs para dar suporte ao armazenamento e à recuperação dos dados adicionais de perfil do usuário. Faça 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 snippet anterior representam colunas adicionais a serem criadas na tabela
AspNetUsers
subjacente. Ambas as propriedades são necessárias e, portanto, são anotadas com o atributo[Required]
. Além disso, o atributo[MaxLength]
indica que um tamanho máximo de 100 caracteres é permitido. O tipo de dados da coluna da tabela subjacente é definido de acordo. Um valorstring.Empty
padrão é atribuído, uma vez que o contexto anulável está habilitado neste projeto e as propriedades são cadeias de caracteres não anuláveis.Adicione a instrução
using
a seguir ao início do arquivo.using System.ComponentModel.DataAnnotations;
O código anterior resolve os atributos de anotação de dados aplicados às propriedades
FirstName
eLastName
.
Atualizar o banco de dados
Agora que as alterações de modelo foram feitas, as alterações que acompanham devem ser feitas no banco de dados.
Verifique se todas as alterações foram salvas.
Crie e aplique uma migração do EF Core para atualizar o armazenamento de dados subjacente:
dotnet ef migrations add UpdateUser dotnet ef database update
A migração do EF Core
UpdateUser
aplicou um script de alteração de DDL ao esquema da tabelaAspNetUsers
. Especificamente, as colunasFirstName
eLastName
foram adicionadas, conforme visto no seguinte trecho de resultado da 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 impacto da migração do
UpdateUser
EF Core no esquema da tabelaAspNetUsers
.No painel SQL Server, expanda o nó Colunas na tabela dbo.AspNetUsers.
As propriedades
FirstName
eLastName
na classeRazorPagesPizzaUser
correspondem às colunasFirstName
eLastName
na imagem anterior. Um tipo de dadosnvarchar(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 de caracteres vazias nas novas colunas.
Personalizar o formulário de registro de usuário
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 marcação 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 marcação anterior, as caixas de texto Nome e Sobrenome são adicionadas ao formulário de registro de usuário.
Em Areas/Identity/Pages/Account/Register.cshtml.cs, adicione suporte às 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 do rótulo a ser 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
como a entrada do usuário no formulário de registro.
Personalizar o cabeçalho do site
Atualize Pages/Shared/_LoginPartial.cshtml para exibir o nome e o sobrenome coletados durante o registro de usuário. As linhas realçadas no seguinte snippet 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 gerenciamento de perfil
Você adicionou os novos campos ao formulário de registro do usuário, mas também deve adicioná-los ao formulário de gerenciamento de perfil para que os usuários possam editá-los.
Em Areas/Identity/Pages/Account/Manage/Index.cshtml, adicione a marcação realçada a seguir. Salve suas 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 alterações a seguir para dar suporte às 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 dá suporte à recuperação do nome e do sobrenome para exibição nas caixas de texto correspondentes do formulário de gerenciamento de perfil.
Incorpore as alterações realçadas no método
OnPostAsync
. Salve 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 dá suporte à atualização do nome e do sobrenome na tabela
AspNetUsers
do banco de dados.
Configurar o remetente de email de confirmação
Para enviar o email de confirmação, você precisa criar uma implementação de IEmailSender e registrá-la no sistema injeção de dependência. Para simplificar, a implementação não enviará realmente um email para um servidor SMTP. Ela só gravará o conteúdo de email no console.
Como você exibirá o email em texto sem formatação no console, altere a mensagem gerada para excluir o texto codificado em HTML. Em Areas/Identity/Pages/Account/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>.");
Mude-a 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 Serviços e crie um novo arquivo chamado EmailSender.cs. Abra o arquivo 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 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 email externo ou a alguma outra ação para enviar email.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 anterior registra
EmailSender
como umIEmailSender
no sistema de injeção de dependência.
Testar as alterações ao formulário de registro
Isso é tudo. Vamos testar as alterações no formulário de registro e no email de confirmação.
Verifique se você salvou todas as alterações.
No painel do terminal, crie o projeto e execute o aplicativo com
dotnet run
.No navegador, navegue até o aplicativo. Selecione Logoff se ainda estiver conectado.
Selecione Registrar e use o formulário atualizado para registrar um novo usuário.
Observação
As restrições de validação nos campos Nome e Sobrenome refletem as anotações de dados nas propriedades
FirstName
eLastName
deInputModel
.Após o registro, 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é a URL com Ctrl+clique. A tela de confirmação é exibida.
Observação
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 Logon e entre com o novo usuário. O cabeçalho do aplicativo agora contém Olá, [nome] [sobrenome]!.
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, insira a consulta a seguir e pressione Ctrl+Shift+E para executá-la.
SELECT UserName, Email, FirstName, LastName FROM dbo.AspNetUsers
Uma guia com resultados semelhantes aos seguintes aparece:
UserName Email Nome LastName kai.klein@contoso.com kai.klein@contoso.com jana.heinrich@contoso.com jana.heinrich@contoso.com Jana Heinrich O primeiro usuário registrado antes de adicionar
FirstName
eLastName
ao esquema. Portanto, o registro da tabelaAspNetUsers
associado não tem dados nessas colunas.
Testar as alterações no formulário de gerenciamento de perfil
Você também deve testar as alterações feitas no formulário de gerenciamento de perfil.
No aplicativo Web, entre com o primeiro usuário criado.
Clique no link Olá, ! para navegar até o formulário de gerenciamento de perfil.
Observação
O link não é exibido corretamente porque a linha da tabela
AspNetUsers
desse usuário não contém valores paraFirstName
eLastName
.Insira valores válidos para Nome e Sobrenome. Selecione Salvar.
O cabeçalho do aplicativo é atualizado para Olá, [nome] [sobrenome]!.
Pressione Ctrl+C no painel do terminal no VS Code para interromper o aplicativo.
Resumo
Nesta unidade, você personalizou a Identidade para armazenar informações personalizadas do usuário. Você também personalizou o email de confirmação. Na próxima unidade, você vai aprender a implementar a autenticação multifator em Identidade.