Adicionar, descarregar e eliminar dados personalizados de utilizador para Identity num projeto ASP.NET Core

Por Rick Anderson

Este artigo mostra como:

  • Adicionar dados personalizados de utilizador a uma aplicação web ASP.NET Core.
  • Marque o modelo de dados de usuário personalizado com o PersonalDataAttribute atributo para que ele fique automaticamente disponível para download e exclusão. Tornar os dados capazes de serem baixados e excluídos ajuda a atender aos requisitos do GDPR .

O exemplo do projeto é criado a partir de uma aplicação web Razor Pages, mas as instruções são semelhantes para uma aplicação web ASP.NET Core MVC.

Prerequisites

.NET 6 SDK

Ver ou transferir o código de exemplo (como transferir)

Criar um Razor aplicativo Web

Crie um novo projeto para uma Razor aplicação web.

  1. No Visual Studio, selecione Arquivo>Novo>Projeto.

  2. Introduza um nome para o projeto. Se quiser que o nome corresponda ao espaço de nomes do código de exemplo de download , introduza WebApp1.

  3. Selecione ASP.NET Core Aplicação Web e depois selecione OK.

  4. Na lista suspensa de Tipos de Autenticação , selecione Contas Individuais.

  5. Selecione Aplicação Web e depois selecione OK.

  6. Crie e execute o projeto.

Execute o Identity gerador de código

Cria e executa o Identity andaimador.

  1. Em Visual Studio Explorador de Soluções, clique com o botão direito no projeto e selecione Adicionar>Novo Item Estruturado.

  2. No painel esquerdo do diálogo Adicionar Andaime , selecione Identity>Adicionar.

  3. No diálogo de Adicionar Identity , configure as seguintes opções:

    1. Selecione o ficheiro de layout existente, ~/Pages/Shared/_Layout.cshtml.

    2. Selecione os seguintes ficheiros da página para substituir:

      • Account/Register
      • Account/Manage/Index
    3. Selecione o ícone de mais (+) e crie uma nova classe de contexto de dados. Aceite o tipo (por exemplo, WebApp1.Models.WebApp1Context se o projeto se chamar WebApp1).

    4. Selecione o ícone de mais (+) e crie uma nova classe de Utilizador. Aceite o tipo (por exemplo, WebApp1User, se o projeto tiver o nome WebApp1) e selecione Adicionar.

  4. Para completar a operação, selecione Adicionar.

Crie uma migração e verifique a aplicação

Depois de preparares o Identity gerador, cria uma migração e verifica a tua aplicação. Para os passos seguintes, consulte as instruções detalhadas em Migrations, UseAuthentication e layout.

  1. Crie uma migração e atualize o banco de dados.

  2. Adicione o UseAuthentication método ao ficheiro Program.cs .

  3. Adiciona o <partial name="_LoginPartial" /> partial ao ficheiro de layout. Para mais informações, consulte Alterações ao layout.

  4. Consulta a aplicação:

    1. Registe um utilizador.

    2. Selecione o novo nome de usuário (ao lado do link Logout ). Talvez seja necessário expandir a janela ou selecionar o ícone da barra de navegação para mostrar o nome de usuário e outros links.

    3. Selecione a guia Dados Pessoais .

    4. Selecione Download e examine o ficheiroPersonalData.json .

    5. Selecione Apagar e confirme que pode eliminar o utilizador iniciado.

Adicionar dados personalizados de utilizador à Identity base de dados

Precisas de atualizar a IdentityUser classe derivada com propriedades personalizadas. Se nomeaste o projeto WebApp1, o ficheiro correspondente a atualizar é Areas/Identity/Data/WebApp1User.cs. Atualize o arquivo com o seguinte código:

using Microsoft.AspNetCore.Identity;

namespace WebApp1.Areas.Identity.Data;

public class WebApp1User : IdentityUser
{
    [PersonalData]
    public string? Name { get; set; }
    [PersonalData]
    public DateTime DOB { get; set; }
}

As propriedades com o atributo PersonalDataAttribute são:

  • Eliminado quando a Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtmlRazor página chama UserManager.Delete.
  • Incluído nos dados descarregados pela Página Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtmlRazor.

Atualize o ficheiro de página 'Account/Manage/Index.cshtml'

  1. No ficheiro Areas/Identity/Pages/Account/Manage/Index.cshtml.cs , atualize a definição da InputModel classe com o seguinte código destacado:

    public class IndexModel : PageModel
    {
        private readonly UserManager<WebApp1User> _userManager;
        private readonly SignInManager<WebApp1User> _signInManager;
    
        public IndexModel(
            UserManager<WebApp1User> userManager,
            SignInManager<WebApp1User> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }
    
        /// <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>
        public string Username { get; set; }
    
        // Remaining API warnings ommited.
    
        [TempData]
        public string StatusMessage { get; set; }
    
        [BindProperty]
        public InputModel Input { get; set; }
    
        public class InputModel
        {
            [Required]
            [DataType(DataType.Text)]
            [Display(Name = "Full name")]
            public string Name { get; set; }
    
            [Required]
            [Display(Name = "Birth Date")]
            [DataType(DataType.Date)]
            public DateTime DOB { get; set; }
    
            [Phone]
            [Display(Name = "Phone number")]
            public string PhoneNumber { get; set; }
        }
    
        private async Task LoadAsync(WebApp1User user)
        {
            var userName = await _userManager.GetUserNameAsync(user);
            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
    
            Username = userName;
    
            Input = new InputModel
            {
                Name = user.Name,
                DOB = user.DOB,
                PhoneNumber = phoneNumber
            };
        }
    
        public async Task<IActionResult> OnGetAsync()
        {
            var user = await _userManager.GetUserAsync(User);
            if (user == null)
            {
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
            }
    
            await LoadAsync(user);
            return Page();
        }
    
        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();
            }
    
            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();
                }
            }
    
            if (Input.Name != user.Name)
            {
                user.Name = Input.Name;
            }
    
            if (Input.DOB != user.DOB)
            {
                user.DOB = Input.DOB;
            }
    
            await _userManager.UpdateAsync(user);
            await _signInManager.RefreshSignInAsync(user);
            StatusMessage = "Your profile has been updated";
            return RedirectToPage();
        }
    }
    
  2. No mesmo ficheiro, atualize as secções indicadas form-floating conforme mostrado na marcação destacada a seguir:

    @page
    @model IndexModel
    @{
        ViewData["Title"] = "Profile";
        ViewData["ActivePage"] = ManageNavPages.Index;
    }
    
    <h3>@ViewData["Title"]</h3>
    <partial name="_StatusMessage" for="StatusMessage" />
    <div class="row">
        <div class="col-md-6">
            <form id="profile-form" method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="form-floating">
                    <input asp-for="Username" class="form-control" disabled />
                    <label asp-for="Username" class="form-label"></label>
                </div>
                <div class="form-floating">
                    <input asp-for="Input.Name" class="form-control" />
                    <label asp-for="Input.Name" class="form-label"></label>
                </div>
                <div class="form-floating">
                    <input asp-for="Input.DOB" class="form-control" />
                    <label asp-for="Input.DOB" class="form-label"></label>
                </div>
                <div class="form-floating">
                    <input asp-for="Input.PhoneNumber" class="form-control" />
                    <label asp-for="Input.PhoneNumber" class="form-label"></label>
                    <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
                </div>
                <button id="update-profile-button" type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
            </form>
        </div>
    </div>
    
    @section Scripts {
        <partial name="_ValidationScriptsPartial" />
    }
    

Atualize o ficheiro de página 'Account/Register.cshtml'

  1. No ficheiro Areas/Identity/Pages/Account/Register.cshtml.cs , atualize a definição da InputModel classe com o seguinte código destacado:

        public class RegisterModel : PageModel
        {
            private readonly SignInManager<WebApp1User> _signInManager;
            private readonly UserManager<WebApp1User> _userManager;
            private readonly IUserStore<WebApp1User> _userStore;
            private readonly IUserEmailStore<WebApp1User> _emailStore;
            private readonly ILogger<RegisterModel> _logger;
            private readonly IEmailSender _emailSender;
    
            public RegisterModel(
                UserManager<WebApp1User> userManager,
                IUserStore<WebApp1User> userStore,
                SignInManager<WebApp1User> signInManager,
                ILogger<RegisterModel> logger,
                IEmailSender emailSender)
            {
                _userManager = userManager;
                _userStore = userStore;
                _emailStore = GetEmailStore();
                _signInManager = signInManager;
                _logger = logger;
                _emailSender = emailSender;
            }
    
            /// <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>
            [BindProperty]
            public InputModel Input { get; set; }
    
            // Remaining API warnings ommited.
            public string ReturnUrl { get; set; }
    
            public IList<AuthenticationScheme> ExternalLogins { get; set; }
    
            public class InputModel
            {
                [Required]
                [DataType(DataType.Text)]
                [Display(Name = "Full name")]
                public string Name { get; set; }
    
                [Required]
                [Display(Name = "Birth Date")]
                [DataType(DataType.Date)]
                public DateTime DOB { 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; }
            }
    
    
            public async Task OnGetAsync(string returnUrl = null)
            {
                ReturnUrl = returnUrl;
                ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            }
    
            public async Task<IActionResult> OnPostAsync(string returnUrl = null)
            {
                returnUrl ??= Url.Content("~/");
                ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
                if (ModelState.IsValid)
                {
                    var user = CreateUser();
    
                    user.Name = Input.Name;
                    user.DOB = Input.DOB;
    
                    await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
                    await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
                    var result = await _userManager.CreateAsync(user, Input.Password);
    
                    if (result.Succeeded)
                    {
                        _logger.LogInformation("User created a new account with password.");
    
                        var userId = await _userManager.GetUserIdAsync(user);
                        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                        code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                        var callbackUrl = Url.Page(
                            "/Account/ConfirmEmail",
                            pageHandler: null,
                            values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                            protocol: Request.Scheme);
    
                        await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                            $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
    
                        if (_userManager.Options.SignIn.RequireConfirmedAccount)
                        {
                            return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
                        }
                        else
                        {
                            await _signInManager.SignInAsync(user, isPersistent: false);
                            return LocalRedirect(returnUrl);
                        }
                    }
                    foreach (var error in result.Errors)
                    {
                        ModelState.AddModelError(string.Empty, error.Description);
                    }
                }
    
                // If we got this far, something failed, redisplay form
                return Page();
            }
    
            private WebApp1User CreateUser()
            {
                try
                {
                    return Activator.CreateInstance<WebApp1User>();
                }
                catch
                {
                    throw new InvalidOperationException($"Can't create an instance of '{nameof(WebApp1User)}'. " +
                        $"Ensure that '{nameof(WebApp1User)}' is not an abstract class and has a parameterless constructor, or alternatively " +
                        $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
                }
            }
    
            private IUserEmailStore<WebApp1User> GetEmailStore()
            {
                if (!_userManager.SupportsUserEmail)
                {
                    throw new NotSupportedException("The default UI requires a user store with email support.");
                }
                return (IUserEmailStore<WebApp1User>)_userStore;
            }
        }
    }
    
  2. No mesmo ficheiro, atualize as secções indicadas form-floating conforme mostrado na marcação destacada a seguir:

    @page
    @model RegisterModel
    @{
        ViewData["Title"] = "Register";
    }
    
    <h1>@ViewData["Title"]</h1>
    
    <div class="row">
        <div class="col-md-4">
            <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.Name" class="form-control" />
                    <label asp-for="Input.Name"></label>
                    <span asp-validation-for="Input.Name" class="text-danger"></span>
                </div>
                <div class="form-floating">
                    <input asp-for="Input.DOB" class="form-control" />
                    <label asp-for="Input.DOB"></label>
                    <span asp-validation-for="Input.DOB" 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>
                <div class="form-floating">
                    <input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" />
                    <label asp-for="Input.Password"></label>
                    <span asp-validation-for="Input.Password" class="text-danger"></span>
                </div>
                <div class="form-floating">
                    <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" />
                    <label asp-for="Input.ConfirmPassword"></label>
                    <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
                </div>
                <button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
            </form>
        </div>
        <div class="col-md-6 col-md-offset-2">
            <section>
                <h3>Use another service to register.</h3>
                <hr />
                @{
                    if ((Model.ExternalLogins?.Count ?? 0) == 0)
                    {
                        <div>
                            <p>
                                There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
                                about setting up this ASP.NET application to support logging in via external services</a>.
                            </p>
                        </div>
                    }
                    else
                    {
                        <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
                            <div>
                                <p>
                                    @foreach (var provider in Model.ExternalLogins!)
                                    {
                                        <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
                                    }
                                </p>
                            </div>
                        </form>
                    }
                }
            </section>
        </div>
    </div>
    
    @section Scripts {
        <partial name="_ValidationScriptsPartial" />
    }
    
  3. Construa o projeto.

Atualizar o layout

Adicione links para iniciar sessão e encerrar sessão para todas as páginas da aplicação web. Para instruções detalhadas, consulte Alterações ao layout.

Adicionar uma migração para os dados personalizados do usuário

Adicione dados para um utilizador personalizado à base de dados.

Execute os seguintes comandos na Consola Visual Studio Gestor de Pacotes:

Add-Migration CustomUserData
Update-Database

Testa a aplicação com dados personalizados do utilizador (criar, ver, descarregar, apagar)

Teste a sua aplicação web adicionando um novo utilizador com dados personalizados:

  1. Registe um novo utilizador.

  2. Exiba os dados personalizados de usuário na página /Identity/Account/Manage.

  3. Descarregue e veja os dados pessoais do utilizador a partir da /Identity/Account/Manage/PersonalData página.

  4. Selecione Apagar e confirme que pode apagar os dados personalizados do utilizador.

.NET SDK Core 3.0

Criar um Razor aplicativo Web

  • No menu Visual Studio File, selecione Novo>Project. Nomeie o projeto WebApp1 se desejar que ele corresponda ao namespace do código de exemplo de download .
  • Selecione ASP.NET Core Aplicação Web>OK
  • Na secção de Autenticação , selecione Contas de Utilizador Individuais.
  • Selecione Aplicação Web>OK
  • Crie e execute o projeto.

Execute o Identity gerador de código

  • A partir de Explorador de Soluções, clique com o botão direito no projeto >Add>Novo Item Estruturado.
  • No painel esquerdo da caixa de diálogo Adicionar andaime , selecione Identity>Adicionar.
  • Na caixa de diálogo Adicionar Identity, as seguintes opções:
    • Selecione o arquivo de layout existente ~/Pages/Shared/_Layout.cshtml
    • Selecione os seguintes arquivos para substituir:
      • Account/Register
      • Account/Manage/Index
    • Selecione o + botão para criar uma nova classe de contexto de dados. Aceite o tipo (WebApp1.Models.WebApp1Context se o projeto for chamado WebApp1).
    • Selecione o + botão para criar uma nova classe User. Aceite o tipo (WebApp1User se o projeto for chamado WebApp1) >Adicionar.
  • Selecione Adicionar.

Siga as instruções em Migrações, UseAuthentication e layout para executar as seguintes etapas:

  • Crie uma migração e atualize o banco de dados.
  • Adicionar UseAuthentication a Startup.Configure.
  • Adicione <partial name="_LoginPartial" /> ao arquivo de layout.
  • Teste o aplicativo:
    • Registar um utilizador
    • Selecione o novo nome de usuário (ao lado do link Logout ). Talvez seja necessário expandir a janela ou selecionar o ícone da barra de navegação para mostrar o nome de usuário e outros links.
    • Selecione a guia Dados Pessoais .
    • Seleciona o botão Download e examina o PersonalData.json ficheiro.
    • Teste o botão Excluir , que exclui o usuário conectado.

Adicionar dados de usuário personalizados ao banco de Identity dados

Atualize a classe derivada IdentityUser com propriedades personalizadas. Se você nomeou o projeto WebApp1, o arquivo é chamado Areas/Identity/Data/WebApp1User.cs.

Atualize o arquivo com o seguinte código:

using System;
using Microsoft.AspNetCore.Identity;

namespace WebApp1.Areas.Identity.Data
{
    public class WebApp1User : IdentityUser
    {
        [PersonalData]
        public string Name { get; set; }
        [PersonalData]
        public DateTime DOB { get; set; }
    }
}

As propriedades com o atributo PersonalData são:

  • Eliminado quando a Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtmlRazor página chama UserManager.Delete.
  • Incluído nos dados descarregados pela Página Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtmlRazor.

Atualizar a página Account/Manage/Index.cshtml

Atualize o InputModel no Areas/Identity/Pages/Account/Manage/Index.cshtml.cs com o seguinte código realçado:

public partial class IndexModel : PageModel
{
    private readonly UserManager<WebApp1User> _userManager;
    private readonly SignInManager<WebApp1User> _signInManager;

    public IndexModel(
        UserManager<WebApp1User> userManager,
        SignInManager<WebApp1User> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    public string Username { get; set; }

    [TempData]
    public string StatusMessage { get; set; }

    [BindProperty]
    public InputModel Input { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Full name")]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Birth Date")]
        [DataType(DataType.Date)]
        public DateTime DOB { get; set; }

        [Phone]
        [Display(Name = "Phone number")]
        public string PhoneNumber { get; set; }
    }

    private async Task LoadAsync(WebApp1User user)
    {
        var userName = await _userManager.GetUserNameAsync(user);
        var phoneNumber = await _userManager.GetPhoneNumberAsync(user);

        Username = userName;

        Input = new InputModel
        {
            Name = user.Name,
            DOB = user.DOB,
            PhoneNumber = phoneNumber
        };
    }

    public async Task<IActionResult> OnGetAsync()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound(
                $"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        await LoadAsync(user);
        return Page();
    }

    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();
        }

        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}'.");
            }
        }
        
        if (Input.Name != user.Name)
        {
            user.Name = Input.Name;
        }

        if (Input.DOB != user.DOB)
        {
            user.DOB = Input.DOB;
        }

        await _userManager.UpdateAsync(user);

        await _signInManager.RefreshSignInAsync(user);
        StatusMessage = "Your profile has been updated";
        return RedirectToPage();
    }
}

Atualize o Areas/Identity/Pages/Account/Manage/Index.cshtml com a seguinte marcação realçada:

@page
@model IndexModel
@{
    ViewData["Title"] = "Profile";
    ViewData["ActivePage"] = ManageNavPages.Index;
}

<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<div class="row">
    <div class="col-md-6">
        <form id="profile-form" method="post">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Username"></label>
                <input asp-for="Username" class="form-control" disabled />
            </div>
            <div class="form-group">
                <label asp-for="Input.Name"></label>
                <input asp-for="Input.Name" class="form-control" />
            </div>
            <div class="form-group">
                <label asp-for="Input.DOB"></label>
                <input asp-for="Input.DOB" class="form-control" />
            </div>
            <div class="form-group">
                <label asp-for="Input.PhoneNumber"></label>
                <input asp-for="Input.PhoneNumber" class="form-control" />
                <span asp-validation-for="Input.PhoneNumber" 
                    class="text-danger"></span>
            </div>
            <button id="update-profile-button" type="submit" 
                class="btn btn-primary">Save</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Atualizar a página Account/Register.cshtml

Atualize o InputModel no Areas/Identity/Pages/Account/Register.cshtml.cs com o seguinte código realçado:

[AllowAnonymous]
public class RegisterModel : PageModel
{
    private readonly SignInManager<WebApp1User> _signInManager;
    private readonly UserManager<WebApp1User> _userManager;
    private readonly ILogger<RegisterModel> _logger;
    private readonly IEmailSender _emailSender;

    public RegisterModel(
        UserManager<WebApp1User> userManager,
        SignInManager<WebApp1User> signInManager,
        ILogger<RegisterModel> logger,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
        _emailSender = emailSender;
    }

    [BindProperty]
    public InputModel Input { get; set; }

    public string ReturnUrl { get; set; }

    public IList<AuthenticationScheme> ExternalLogins { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Full name")]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Birth Date")]
        [DataType(DataType.Date)]
        public DateTime DOB { 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; }
    }

    public async Task OnGetAsync(string returnUrl = null)
    {
        ReturnUrl = returnUrl;
        ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl = returnUrl ?? Url.Content("~/");
        ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
        if (ModelState.IsValid)
        {
            var user = new WebApp1User {
                Name = Input.Name,
                DOB = Input.DOB,
                UserName = Input.Email, 
                Email = Input.Email 
            };
            var result = await _userManager.CreateAsync(user, Input.Password);
            if (result.Succeeded)
            {
                _logger.LogInformation("User created a new account with password.");

                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = user.Id, code = code },
                    protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, 
                    "Confirm your email",
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                if (_userManager.Options.SignIn.RequireConfirmedAccount)
                {
                    return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
                }
                else
                {
                    await _signInManager.SignInAsync(user, isPersistent: false);
                    return LocalRedirect(returnUrl);
                }
            }
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }

        // If we got this far, something failed, redisplay form
        return Page();
    }
}

Atualize o Areas/Identity/Pages/Account/Register.cshtml com a seguinte marcação realçada:

@page
@model RegisterModel
@{
    ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>

<div class="row">
    <div class="col-md-4">
        <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.Name"></label>
                <input asp-for="Input.Name" class="form-control" />
                <span asp-validation-for="Input.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.DOB"></label>
                <input asp-for="Input.DOB" class="form-control" />
                <span asp-validation-for="Input.DOB" 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>
            <div class="form-group">
                <label asp-for="Input.Password"></label>
                <input asp-for="Input.Password" class="form-control" />
                <span asp-validation-for="Input.Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.ConfirmPassword"></label>
                <input asp-for="Input.ConfirmPassword" class="form-control" />
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Register</button>
        </form>
    </div>
    <div class="col-md-6 col-md-offset-2">
        <section>
            <h4>Use another service to register.</h4>
            <hr />
            @{
                if ((Model.ExternalLogins?.Count ?? 0) == 0)
                {
                    <div>
                        <p>
                            There are no external authentication services configured. See 
                             <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                            for details on setting up this ASP.NET application to support 
                            logging in via external services.
                        </p>
                    </div>
                }
                else
                {
                    <form id="external-account" asp-page="./ExternalLogin" 
                        asp-route-returnUrl="@Model.ReturnUrl" method="post" 
                        class="form-horizontal">
                        <div>
                            <p>
                                @foreach (var provider in Model.ExternalLogins)
                                {
                                    <button type="submit" class="btn btn-primary" name="provider" 
                                        value="@provider.Name" 
                                        title="Log in using your @provider.DisplayName account">
                                            @provider.DisplayName</button>
                                }
                            </p>
                        </div>
                    </form>
                }
            }
        </section>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Construa o projeto.

Adicionar uma migração para os dados personalizados do usuário

No Visual Studio Gestor de Pacotes Console:

Add-Migration CustomUserData
Update-Database

Testar criar, visualizar, baixar, excluir dados personalizados do usuário

Teste o aplicativo:

  • Registe um novo utilizador.
  • Exiba os dados personalizados de usuário na página /Identity/Account/Manage.
  • Transfira e visualize os dados pessoais dos utilizadores na página /Identity/Account/Manage/PersonalData.

Adicionar declarações ao Identity usando IUserClaimsPrincipalFactory<ApplicationUser>

Note

Esta seção não é uma extensão do tutorial anterior. Para aplicar os passos seguintes à aplicação construída com base no tutorial, veja this GitHub issue.

Reivindicações adicionais podem ser adicionadas a ASP.NET Core Identity usando a interface IUserClaimsPrincipalFactory<T>. Essa classe pode ser adicionada ao aplicativo no Startup.ConfigureServices método. Adicione a implementação personalizada da classe da seguinte maneira:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, 
        AdditionalUserClaimsPrincipalFactory>();

O código de demonstração usa a ApplicationUser classe. Essa classe adiciona uma IsAdmin propriedade que é usada para adicionar a declaração adicional.

public class ApplicationUser : IdentityUser
{
    public bool IsAdmin { get; set; }
}

O AdditionalUserClaimsPrincipalFactory implementa a UserClaimsPrincipalFactory interface. Uma nova declaração de função é adicionada ClaimsPrincipalao .

public class AdditionalUserClaimsPrincipalFactory 
        : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public AdditionalUserClaimsPrincipalFactory( 
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager, 
        IOptions<IdentityOptions> optionsAccessor) 
        : base(userManager, roleManager, optionsAccessor)
    {}

    public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        var principal = await base.CreateAsync(user);
        var identity = (ClaimsIdentity)principal.Identity;

        var claims = new List<Claim>();
        if (user.IsAdmin)
        {
            claims.Add(new Claim(JwtClaimTypes.Role, "admin"));
        }
        else
        {
            claims.Add(new Claim(JwtClaimTypes.Role, "user"));
        }

        identity.AddClaims(claims);
        return principal;
    }
}

A declaração adicional pode ser usada no aplicativo. Numa Razor Página, pode-se utilizar a instância IAuthorizationService para aceder ao valor da reivindicação.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

@if ((await AuthorizationService.AuthorizeAsync(User, "IsAdmin")).Succeeded)
{
    <ul class="mr-auto navbar-nav">
        <li class="nav-item">
            <a class="nav-link" asp-controller="Admin" asp-action="Index">ADMIN</a>
        </li>
    </ul>
}

.NET Core SDK 2.2 ou posterior

Criar um Razor aplicativo Web

  • No menu Visual Studio File, selecione Novo>Project. Nomeie o projeto WebApp1 se desejar que ele corresponda ao namespace do código de exemplo de download .
  • Selecione ASP.NET Core Aplicação Web>OK
  • Selecione ASP.NET Core 2.2 no menu suspenso
  • Na secção de Autenticação , selecione Contas de Utilizador Individuais.
  • Selecione Aplicação Web>OK
  • Crie e execute o projeto.

Execute o Identity gerador de código

  • A partir de Explorador de Soluções, clique com o botão direito no projeto >Add>Novo Item Estruturado.
  • No painel esquerdo da caixa de diálogo Adicionar andaime , selecione Identity>Adicionar.
  • Na caixa de diálogo Adicionar Identity, as seguintes opções:
    • Selecione o arquivo de layout existente ~/Pages/Shared/_Layout.cshtml
    • Selecione os seguintes arquivos para substituir:
      • Account/Register
      • Account/Manage/Index
    • Selecione o + botão para criar uma nova classe de contexto de dados. Aceite o tipo (WebApp1.Models.WebApp1Context se o projeto for chamado WebApp1).
    • Selecione o + botão para criar uma nova classe User. Aceite o tipo (WebApp1User se o projeto for chamado WebApp1) >Adicionar.
  • Selecione Adicionar.

Siga as instruções em Migrações, UseAuthentication e layout para executar as seguintes etapas:

  • Crie uma migração e atualize o banco de dados.
  • Adicionar UseAuthentication a Startup.Configure.
  • Adicione <partial name="_LoginPartial" /> ao arquivo de layout.
  • Teste o aplicativo:
    • Registar um utilizador
    • Selecione o novo nome de usuário (ao lado do link Logout ). Talvez seja necessário expandir a janela ou selecionar o ícone da barra de navegação para mostrar o nome de usuário e outros links.
    • Selecione a guia Dados Pessoais .
    • Seleciona o botão Download e examina o PersonalData.json ficheiro.
    • Teste o botão Excluir , que exclui o usuário conectado.

Adicionar dados de usuário personalizados ao banco de Identity dados

Atualize a classe derivada IdentityUser com propriedades personalizadas. Se você nomeou o projeto WebApp1, o arquivo é chamado Areas/Identity/Data/WebApp1User.cs. Atualize o arquivo com o seguinte código:

using Microsoft.AspNetCore.Identity;
using System;

namespace WebApp1.Areas.Identity.Data
{
    public class WebApp1User : IdentityUser
    {
        [PersonalData]
        public string Name { get; set; }
        [PersonalData]
        public DateTime DOB { get; set; }
    }
}

As propriedades com o atributo PersonalData são:

  • Eliminado quando a Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtmlRazor página chama UserManager.Delete.
  • Incluído nos dados descarregados pela Página Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtmlRazor.

Atualizar a página Account/Manage/Index.cshtml

Atualize o InputModel no Areas/Identity/Pages/Account/Manage/Index.cshtml.cs com o seguinte código realçado:

public partial class IndexModel : PageModel
{
    private readonly UserManager<WebApp1User> _userManager;
    private readonly SignInManager<WebApp1User> _signInManager;
    private readonly IEmailSender _emailSender;

    public IndexModel(
        UserManager<WebApp1User> userManager,
        SignInManager<WebApp1User> signInManager,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
    }

    public string Username { get; set; }
    public bool IsEmailConfirmed { get; set; }

    [TempData]
    public string StatusMessage { get; set; }

    [BindProperty]
    public InputModel Input { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Full name")]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Birth Date")]
        [DataType(DataType.Date)]
        public DateTime DOB { get; set; }

        [Required]
        [EmailAddress]
        public string Email { get; set; }

        [Phone]
        [Display(Name = "Phone number")]
        public string PhoneNumber { get; set; }
    }

    public async Task<IActionResult> OnGetAsync()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        var userName = await _userManager.GetUserNameAsync(user);
        var email = await _userManager.GetEmailAsync(user);
        var phoneNumber = await _userManager.GetPhoneNumberAsync(user);

        Username = userName;

        Input = new InputModel
        {
            Name = user.Name,
            DOB = user.DOB,
            Email = email,
            PhoneNumber = phoneNumber
        };

        IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);

        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }

        var email = await _userManager.GetEmailAsync(user);
        if (Input.Email != email)
        {
            var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
            if (!setEmailResult.Succeeded)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
            }
        }

        if (Input.Name != user.Name)
        {
            user.Name = Input.Name;
        }

        if (Input.DOB != user.DOB)
        {
            user.DOB = Input.DOB;
        }

        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 _userManager.UpdateAsync(user);

        await _signInManager.RefreshSignInAsync(user);
        StatusMessage = "Your profile has been updated";
        return RedirectToPage();
    }

    public async Task<IActionResult> OnPostSendVerificationEmailAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
        }


        var userId = await _userManager.GetUserIdAsync(user);
        var email = await _userManager.GetEmailAsync(user);
        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var callbackUrl = Url.Page(
            "/Account/ConfirmEmail",
            pageHandler: null,
            values: new { userId = userId, code = code },
            protocol: Request.Scheme);
        await _emailSender.SendEmailAsync(
            email,
            "Confirm your email",
            $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

        StatusMessage = "Verification email sent. Please check your email.";
        return RedirectToPage();
    }
}

Atualize o Areas/Identity/Pages/Account/Manage/Index.cshtml com a seguinte marcação realçada:

@page
@model IndexModel
@{
    ViewData["Title"] = "Profile";
    ViewData["ActivePage"] = ManageNavPages.Index;
}

<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
    <div class="col-md-6">
        <form id="profile-form" method="post">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Username"></label>
                <input asp-for="Username" class="form-control" disabled />
            </div>
            <div class="form-group">
                <label asp-for="Input.Email"></label>
                @if (Model.IsEmailConfirmed)
                {
                    <div class="input-group">
                        <input asp-for="Input.Email" class="form-control" />
                        <span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
                    </div>
                }
                else
                {
                    <input asp-for="Input.Email" class="form-control" />
                    <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
                }
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.Name"></label>
                <input asp-for="Input.Name" class="form-control" />
            </div>
            <div class="form-group">
                <label asp-for="Input.DOB"></label>
                <input asp-for="Input.DOB" class="form-control" />
            </div>
            <div class="form-group">
                <label asp-for="Input.PhoneNumber"></label>
                <input asp-for="Input.PhoneNumber" class="form-control" />
                <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
            </div>
            <button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Atualizar a página Account/Register.cshtml

Atualize o InputModel no Areas/Identity/Pages/Account/Register.cshtml.cs com o seguinte código realçado:

[AllowAnonymous]
public class RegisterModel : PageModel
{
    private readonly SignInManager<WebApp1User> _signInManager;
    private readonly UserManager<WebApp1User> _userManager;
    private readonly ILogger<RegisterModel> _logger;
    private readonly IEmailSender _emailSender;

    public RegisterModel(
        UserManager<WebApp1User> userManager,
        SignInManager<WebApp1User> signInManager,
        ILogger<RegisterModel> logger,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
        _emailSender = emailSender;
    }

    [BindProperty]
    public InputModel Input { get; set; }

    public string ReturnUrl { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Text)]
        [Display(Name = "Full name")]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Birth Date")]
        [DataType(DataType.Date)]
        public DateTime DOB { 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; }
    }

    public void OnGet(string returnUrl = null)
    {
        ReturnUrl = returnUrl;
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl = returnUrl ?? Url.Content("~/");
        if (ModelState.IsValid)
        {
            var user = new WebApp1User {
                Name = Input.Name,
                DOB = Input.DOB,
                UserName = Input.Email,
                Email = Input.Email
            };
            var result = await _userManager.CreateAsync(user, Input.Password);
            if (result.Succeeded)
            {
                _logger.LogInformation("User created a new account with password.");

                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { userId = user.Id, code = code },
                    protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }

        // If we got this far, something failed, redisplay form
        return Page();
    }
}

Atualize o Areas/Identity/Pages/Account/Register.cshtml com a seguinte marcação realçada:

@page
@model RegisterModel
@{
    ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>

<div class="row">
    <div class="col-md-4">
        <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.Name"></label>
                <input asp-for="Input.Name" class="form-control" />
                <span asp-validation-for="Input.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.DOB"></label>
                <input asp-for="Input.DOB" class="form-control" />
                <span asp-validation-for="Input.DOB" 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>
            <div class="form-group">
                <label asp-for="Input.Password"></label>
                <input asp-for="Input.Password" class="form-control" />
                <span asp-validation-for="Input.Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.ConfirmPassword"></label>
                <input asp-for="Input.ConfirmPassword" class="form-control" />
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Register</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Construa o projeto.

Adicionar uma migração para os dados personalizados do usuário

No Visual Studio Gestor de Pacotes Console:

Add-Migration CustomUserData
Update-Database

Testar criar, visualizar, baixar, excluir dados personalizados do usuário

Teste o aplicativo:

  • Registe um novo utilizador.
  • Exiba os dados personalizados de usuário na página /Identity/Account/Manage.
  • Transfira e visualize os dados pessoais dos utilizadores na página /Identity/Account/Manage/PersonalData.