Escolha uma Página
(Last Updated On: 10/01/2021)

Como Criar, Ler, Atualizar e Apagar Usuários com o ASP.NET Core Identity

por | dez 22, 2020 | Desenvolvimento de Aplicações | 0 Comentários

Neste tutorial, realizarei operações CRUD para criar, ler, atualizar e excluir usuários na identidade (Identity) do ASP.NET Core. Antes de continuar, você deve instalar e configurar a identidade do ASP.NET Core em seu projeto – se ainda não instalou.

Instale o Visual Studio e o .NET Core

Instale o seguinte:
  • .NET Core 5.0 SDK ou mais recente;
  • Visual Studio 2019 versão 16.8 ou posterior com ASP.NET e carga de trabalho de desenvolvimento web… Instale, pelo menos, os seguintes “workloads”:
    • ASP.NET and web development
    • .NET Core cross-platform development

Crie um aplicativo WEB

No Visual Studio, selecione File > New > Project e escolha o template conforme a figura abaixo:
  • Dê um nome para o seu projeto teste como “MVCMovie” (é importante lembrar que ao dar esse nome, se você copiar código daqui, vai ter de trocar o namespace de todas páginas de controle (.cs) para o nome do seu projeto.
  • Escolha umm local no seu disco para os programas fonte;
  • Escolha a opção de deixar a solução e o projeto no mesmo diretório.
  • Escolha a aplicacão e clique em Change, para escolher o tipo de autenticação
  • Selecione a opção Individual User Accounts e de OK.
  • Escolha a aplicacão e clique em Change, para escolher o tipo de autenticação
  • Selecione a opção Individual User Accounts e de OK.
  • Clique em CREATE. Pronto. O projeto está pronto.

O Visual Studio usou um modelo padrão para o projeto MVC que você acabou de criar, já com identidade (Identity instalada).

No lado direito há as opções de registro e login. Você já possui um aplicativo de trabalho inserindo com o nome de projeto, que permite selecionar algumas opções.

Este é um projeto inicial simples e é um bom lugar para começar. No Visual Studio, clique em F5 para executar o aplicativo no modo de depuração ou Ctrl-F5 no modo de não depuração.

 

Criar novos usuários na identidade (Identity)

Para criar usuários na identidade do ASP.NET Core, você precisará criar uma classe de modelo. Comece tudo criando uma classe chamada AppUser.cs dentro da pasta Models (Modelos). Adicione propriedades públicas a ele, que são, no meu caso:
  • FirstName, LastName, Email (com override, pois já existe em Identity), PhoneNumber (com override, pois já existe em Identity) e Password, do tipo string. Adicione também atributos de validação de modelo [Required] a eles
  • FullName (campo calculado FirstName + LastName
  • Interesse, tipo inteiro, não obrigatório (por isso usei int? – a interrogação torna o campo não obrigatório)

Criando o controlador de todas as operações

Para fazer todas operações de leitura, autalização, criação, deleção – teremos de ter um controlador geral – que vamos incluir na pasta de Controllers (controladores). Vamos dar o nome a esse arquivo controlador de AdminController.cs.

Note que em Create o novo usuário tem variáveis que não existem no arquivo original de Identidade: FirstName, LastName, Interesse e Password. Esses campos serão adicionados à tabela de usuários original do ASP.NET.

O Password = user.Password guardará a password em string, sem afetar a guarda da senha codificada pelo aspnet. Para mim isso é útil, mesmo não sendo seguro. Você pode não incluir essa linha se quiser.

Para o arquivo de pessoas receber os novos campos, entre em Tools, Nuget Package Manager, Package Manager Console e digite:

add-migration Usuarios

update-database

Codigo AdminController.cs. na pasta Controllers
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PoupaTempoDigital.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;


namespace PoupaTempoDigital.Controllers
{
    public class AdminController : Controller
    {
        private readonly UserManager<AppUser> _userManager;
        private readonly ILogger<AdminController> _logger;
        private readonly RoleManager<AppRole> _roleManager;
        private IPasswordHasher<AppUser> _passwordHasher;
        public AdminController(
            UserManager<AppUser> userManager
            , ILogger<AdminController> logger
            , RoleManager<AppRole> roleManager
            , IPasswordHasher<AppUser> passwordHash
            )
        {
            _userManager = userManager;
            _logger = logger;
            _roleManager = roleManager;
            _passwordHasher = passwordHash;
        }
        public IActionResult Index()
        {
            List<AppUser> novaListaUsuarios = new List<AppUser>();
            var todosUsuarios = (from user in _userManager.Users
                                 select new AppUser
                                 {
                                     Id = user.Id,
                                     FirstName = user.FirstName,
                                     LastName = user.LastName,
                                     PhoneNumber = user.PhoneNumber,
                                     Email = user.Email

                                 });

            ViewBag.DataSource = todosUsuarios;
            return View();

        }

        // GET: Pessoas/Delete/5
        public async Task<IActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var usuario = await _userManager.Users.FirstOrDefaultAsync(m => m.Id == id);
            //ViewBag.DataSource =  _userManager.Users;
            if (usuario == null)
            {
                return NotFound();
            }

            return View(usuario);
        }


        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            AppUser user = await _userManager.FindByIdAsync(id.ToString());
            if (user != null)
            {
                IdentityResult result = await _userManager.DeleteAsync(user);
                if (result.Succeeded)
                { return RedirectToAction(nameof(Index)); }
                else
                { Errors(result); }
            }
            else
            { 
               ModelState.AddModelError("", "Usuário não Encontado"); 
            }
            //return View("Index", _userManager.Users);
            //ViewBag.DataSource = _userManager.Users;
            return RedirectToAction(nameof(Index));
        }

        public async Task<IActionResult> Update(int Id)
        {
            AppUser user = await _userManager.FindByIdAsync(Id.ToString());
            if (user != null)
                return View(user);
            else
                return RedirectToAction("Index");
        }

        [HttpPost]
        public async Task<IActionResult> Update(int Id, string email, string password, string FirstName, string LastName, string PhoneNumber)
        {
            AppUser user = await _userManager.FindByIdAsync(Id.ToString());
            if (user != null)
            {
                if (!string.IsNullOrEmpty(email))
                    user.Email = email;
                else
                    ModelState.AddModelError("", "O E-mail não pode ser vazio");

                if (!string.IsNullOrEmpty(FirstName))
                    user.FirstName = FirstName;
                else
                    ModelState.AddModelError("", "O Nome não pode ser vazio");

                if (!string.IsNullOrEmpty(LastName))
                    user.LastName = LastName;
                else
                    ModelState.AddModelError("", "O Sobrenome não pode ser vazio");

                if (!string.IsNullOrEmpty(PhoneNumber))
                    user.PhoneNumber = PhoneNumber;
                else
                    ModelState.AddModelError("", "O telefone do WhatsApp não pode ser vazio");


                if (!string.IsNullOrEmpty(password))
                    user.PasswordHash = _passwordHasher.HashPassword(user, password);
                else
                    ModelState.AddModelError("", "A senha não pode ser vazia");

                if (!string.IsNullOrEmpty(email) && !string.IsNullOrEmpty(password))
                {
                    IdentityResult result = await _userManager.UpdateAsync(user);
                    if (result.Succeeded)
                        return RedirectToAction("Index");
                    else
                        Errors(result);
                }
            }
            else
                ModelState.AddModelError("", "Usuário não encontrado");
            return View(user);
        }

        private void Errors(IdentityResult result)
        {
            foreach (IdentityError error in result.Errors)
                ModelState.AddModelError("", error.Description);
        }


        public ViewResult Create() => View();

        [HttpPost]
        public async Task<IActionResult> Create(AppUser user)
        {
            if (ModelState.IsValid)
            {
                var usuario = await _userManager.FindByEmailAsync(user.Email);
                if (usuario != null) //existe usuario
                {
                    ModelState.AddModelError("Email", "Este e-mail já está cadastrado no PoupaTempo. Entre no sistema usando o Acesso, no menu");
                    return View("Create", user);
                }


                AppUser appUser = new AppUser
                {
                    FirstName = user.FirstName,
                    LastName = user.LastName,
                    PhoneNumber = user.PhoneNumber,
                    Email = user.Email,
                    Interesse = 4, //todo definido um valor fixo arbitrário, deveria ser  user.Interesse,
                    Password = user.Password
                };
                appUser.UserName = appUser.Email;
                appUser.EmailConfirmed = true;
                //appUser.SecurityStamp = Guid.NewGuid().ToString();
                IdentityResult result = await _userManager.CreateAsync(appUser, user.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("O usuário criou uma nova conta com senha.");
                    //cria papéis se ainda não existem
                    //admin
                    if (!await _roleManager.RoleExistsAsync(Papeis.AdminEndUser))
                    {
                        await _roleManager.CreateAsync(new AppRole(Papeis.AdminEndUser));
                    }
                    //cliente
                    if (!await _roleManager.RoleExistsAsync(Papeis.ClienteEndUser))
                    {
                        await _roleManager.CreateAsync(new AppRole(Papeis.ClienteEndUser));
                    }
                    //colaborador
                    if (!await _roleManager.RoleExistsAsync(Papeis.ColaboradorEndUser))
                    {
                        await _roleManager.CreateAsync(new AppRole(Papeis.ColaboradorEndUser));
                    }
                    //cria papel padrao para cada novo inscrito = Cliente
                    await _userManager.AddToRoleAsync(appUser, Papeis.ClienteEndUser);
                }
                else
                {
                    foreach (IdentityError error in result.Errors)
                        ModelState.AddModelError("Erro:", error.Description);
                    return View("Create", user);
                }
                return RedirectToAction("Index");
            }
            return View("Index");
        }
    }
}

Note que ao adicionar um usuário:

  • testo para ver se esse e-mail já não cadastrado anteriormente;
  • associo a ele uma “role”, ou seja, um papel. No meu caso:
  • testo se os 3 papeis que uso (cliente, colaborador e administrador) já existem no arquivo de roles. Se não existem, são criados;
  • associo o usuário adicionado uma role padrão, no meu caso, cliente.

O arquivo de roles (papeis, que você pode mudar)  fica na pasta models, em papeis.cs

Criando as Views (visualizações)

View de criação

Arquivo Create.cshtml. Pasta Views-&amp;amp;amp;amp;amp;gt;Admin
@model PoupaTempoDigital.Models.AppUser

@{
    ViewData["Title"] = "Cria Usuário";
}

<h1 class="bg-info text-white">&nbspCria Novo Usuário</h1>
<a asp-action="Index" class="btn btn-secondary">Retornar</a>
<div asp-validation-summary="All" class="text-danger"></div>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div class="form-group">
                <label asp-for="FirstName"></label>
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="LastName"></label>
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PhoneNumber"></label>
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="Email"></label>
                <input asp-for="Email" autocomplete="off" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password"></label>
                <input asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" autocomplete="off"  class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Create</button>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View de Atualização

Arquivo Update.cshtml. Pasta Views-&amp;amp;amp;amp;amp;gt;Admin
@model PoupaTempoDigital.Models.AppUser

@{
    ViewData["Title"] = "Atualiza Usuário";
}

<h1 class="bg-info text-white">&nbspAtualiza Usuário</h1>
<a asp-action="Index" class="btn btn-secondary">Back</a>
<div asp-validation-summary="All" class="text-danger"></div>

<hr />
<div class="row">
    <div class="col-md-4">


        <form asp-action="Update" method="post">
            <div class="form-group">
                <label asp-for="Id"></label>
                <input asp-for="Id" class="form-control" disabled />
                <span asp-validation-for="Id" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="FirstName"></label>
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="LastName"></label>
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="PhoneNumber"></label>
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="Email"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <input name="password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Save</button>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Listagem dos usuários

Para a listagem eu usos pacotes da Syncfusion, que me facilitam muito a paginação, o sort e a pesquisa em cada campo mostrado (veja meu artigo Syncfusion Core: acelerando o seu desenvolvimento em Core MVC com componentes).

Nessa página de listagem eu inclui dois botoes, um de atualização e outro para apagar o usuário. Seria bobagem gerar uma segunda listagem só para escolher o usuário a ser “apagado”.

Esses dois botões são construidos dentro do “template” logo abaixo do grid, entre <script> e </script>.

De qualquer forma deixei comentado o “código tradicional” para este tipo de view, caso não queira usar o Syncfusion. Não tem paginação, nem sort, nem nada, como de hábito.

No topo da página acrescentei um botão para a criação de novos usuários.

Arquivo Index.cshtml. Pasta Views-&amp;amp;amp;amp;amp;gt;Admin
@model IEnumerable<PoupaTempoDigital.Models.AppUser>

@{
    ViewData["Title"] = "Listagem de Usuários";
}

<h1 class="bg-info text-white">&nbspTodos os Usuários</h1>
<a asp-action="Create" class="btn btn-secondary">Cria Novo Usuário</a>
<p></p>
<ejs-grid id="Grid" locale="pt-BR" dataSource="@ViewBag.DataSource" allowPaging="true" allowSorting="true" allowFiltering="true" allowGrouping="true" gridLines="Both">

    <e-grid-pagesettings pageSize="15"></e-grid-pagesettings>

    <e-grid-columns>
        <e-grid-column field="Id" headerText="Id" isPrimaryKey="true" isIdentity="true" width="50"></e-grid-column>
        <e-grid-column field="FullName" headerText="Nome" textAlign="Left" width="120"></e-grid-column>
        <e-grid-column field="PhoneNumber" headerText="WhatsApp" width="100"></e-grid-column>
        <e-grid-column field="Email" headerText="E-mail" width="150"></e-grid-column>

        <e-grid-column field="" headerText="Ação" width="130" textAlign="Center" template="#template"></e-grid-column>

    </e-grid-columns>
</ejs-grid>

<script id="template" type="text/x-template">

    <div>
        @*<a rel='nofollow' href="/Pessoas/Edit/${Id}">${NomeCompleto}</a>*@
    <a rel='nofollow'  class="btn btn-sm btn-primary text-white" href="/Admin/Update/${Id}">
                    Atualiza
                </a>
  &nbsp
        @*<a rel='nofollow' href="/Pessoas/Edit/${Id}">${NomeCompleto}</a>*@
    <a rel='nofollow'  class="btn btn-sm btn-warning text-white" href="/Admin/Delete/${Id}">
                    Apaga
                </a>
    </div>
</script>

@*<table class="table table-sm table-bordered">
    <tr>
        <th>ID</th>
        <th>Nome</th>
        <th>Fone</th>
        <th>E-mail</th>
        <th>Update</th>
    </tr>
    @foreach (AppUser user in Model)
    {
        <tr>
            <td>@user.Id</td>
            <td>@user.FullName</td>
            <td>@user.PhoneNumber</td>
            <td>@user.Email</td>
            <td>
                <a class="btn btn-sm btn-primary" asp-action="Update" asp-route-id="@user.Id">
                    Atualiza
                </a>
            </td>
        </tr>
    }
</table>*@

Tela para Apagar Usuário

Não tem tela para apagar e sim uma tela de confirmação, caso acidentalmente seja clicado o botão “Apagar”.

Arquivo Delete.cshtml. Pasta Views-&amp;amp;amp;amp;amp;gt;Admin
@model PoupaTempoDigital.Models.AppUser

@{
    ViewData["Title"] = "Apagando Usuário";
}

<h1>Apaga Usuário</h1>

<h3>Tem certeza que quer apagar este usuário?</h3>
<div>
    <h4>Usuário</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.FullName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.FullName)
        </dd>
    </dl>

    <form asp-action="Delete">
        <input type="hidden" asp-for="Id" />
        <input type="submit" value="Apagar" class="btn btn-danger" /> |
        <a asp-action="Index">Retorno à Lista</a>
    </form>
</div>

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários aqui no final da página.