Exercício – Personalizar Identidade

Concluído

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.

  1. 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 de DbContext com o nome RazorPagesPizzaAuth.
    • 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 e Account.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

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:

  1. Adicione as propriedades FirstName e LastName:

    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 de string.Empty caracteres não anuláveis.

  2. 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 e LastName.

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.

  1. Certifique-se de que todas as suas alterações são salvas.

  2. 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 tabela AspNetUsers. Especificamente, foram adicionadas as colunas FirstName e LastName, 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'';
    
  3. Examine o banco de dados para analisar o efeito da migração do UpdateUser EF Core no esquema da AspNetUsers tabela.

    No painel SQL Server, expanda o nó Colunas no dbo. Tabela AspNetUsers.

    Captura de tela do esquema da tabela AspNetUsers.

    As FirstName propriedades e LastName na RazorPagesPizzaUser classe correspondem às FirstName colunas e LastName na imagem anterior. Um tipo de dados de nvarchar(100) foi atribuído a cada uma das duas colunas devido aos atributos [MaxLength(100)]. A restrição não-nula foi adicionada porque FirstName e LastName 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.

  1. 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.

  2. Em Areas/Identity/Pages/Account/Register.cshtml.cs, adicione suporte para caixas de texto de nome.

    1. Adicione as propriedades FirstName e LastName à classe aninhada InputModel:

      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.

    2. Modifique o método OnPostAsync para definir as propriedades FirstName e LastName no objeto RazorPagesPizza. 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 e LastName 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.

  1. 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>
    
  2. Em Areas/Identity/Pages/Account/Manage/Index.cshtml.cs, faça as seguintes alterações para suportar as caixas de texto de nome.

    1. Adicione as propriedades FirstName e LastName à classe aninhada InputModel:

      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; }
      }
      
    2. 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.

    3. 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.

  1. 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}");
    
  2. 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.

  3. 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 um IEmailSender 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.

  1. Certifique-se de que guardou todas as alterações.

  2. No painel de terminal, crie o projeto e execute o aplicativo com dotnet runo .

  3. No seu browser, navegue para a aplicação. Selecione Sair se estiver conectado.

  4. 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 e LastName de InputModel.

  5. 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.

  6. Selecione Login e entre com o novo usuário. Agora, o cabeçalho da aplicação contém Hello, [Nome próprio] [Apelido]!.

  7. 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 e LastName ao esquema. Portanto, o registro de tabela associado AspNetUsers 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.

  1. Na aplicação Web, inicie sessão com o primeiro utilizador que criou.

  2. 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 para FirstName e LastName.

  3. 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]!.

  4. 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.