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

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
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
@model PoupaTempoDigital.Models.AppUser
@{
ViewData["Title"] = "Cria Usuário";
}
<h1 class="bg-info text-white"> Cria 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
@model PoupaTempoDigital.Models.AppUser
@{
ViewData["Title"] = "Atualiza Usuário";
}
<h1 class="bg-info text-white"> Atualiza 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.
@model IEnumerable<PoupaTempoDigital.Models.AppUser>
@{
ViewData["Title"] = "Listagem de Usuários";
}
<h1 class="bg-info text-white"> Todos 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>
 
@*<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”.
@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.