Usando la identidad en la aplicación
Los elementos de la lista de tareas pendientes todavía se comparten entre todos los usuarios, porque las entidades de tareas pendientes almacenadas no están vinculadas a un usuario en particular. Ahora que el atributo [Authorize]
asegura que debe iniciar sesión para ver la vista de tareas, puede filtrar la consulta de la base de datos según quién haya iniciado sesión.
Primero, inyecte un UserManager<ApplicationUser>
en el TodoController
:
Controllers/TodoController.cs
[Authorize]
public class TodoController : Controller
{
private readonly ITodoItemService _todoItemService;
private readonly UserManager<ApplicationUser> _userManager;
public TodoController(ITodoItemService todoItemService,
UserManager<ApplicationUser> userManager)
{
_todoItemService = todoItemService;
_userManager = userManager;
}
// ...
}
Deberá agregar una nueva declaración using
en la parte superior:
using Microsoft.AspNetCore.Identity;
La clase UserManager
es parte de ASP.NET Core Identity. Puedes usarlo para obtener al usuario actual en la acción Index
:
public async Task<IActionResult> Index()
{
var currentUser = await _userManager.GetUserAsync(User);
if (currentUser == null) return Challenge();
var items = await _todoItemService
.GetIncompleteItemsAsync(currentUser);
var model = new TodoViewModel()
{
Items = items
};
return View(model);
}
El nuevo código en la parte superior del método de acción utiliza el UserManager
para buscar al usuario actual en la propiedad Usuario
disponible en la acción:
var currentUser = await _userManager.GetUserAsync(User);
Si hay un usuario que ha iniciado sesión, la propiedad User
contiene un objeto ligero con algo (pero no toda) la información del usuario. El UserManager
usa esto para buscar los detalles completos del usuario en la base de datos a través del método GetUserAsync()
.
El valor de currentUser
nunca debe ser nulo, porque el atributo [Authorize]
está presente en el controlador. Sin embargo, es una buena idea hacer un control de cordura, por si acaso. Puede usar el método Challenge()
para forzar al usuario a iniciar sesión nuevamente si falta su información:
if (currentUser == null) return Challenge();
Como ahora estás pasando un parámetro ApplicationUser
a GetIncompleteItemsAsync()
, deberás actualizar la interfaz ITodoItemService
:
Services/ITodoItemService.cs
public interface ITodoItemService
{
Task<TodoItem[]> GetIncompleteItemsAsync(
ApplicationUser user);
// ...
}
Ya que cambió la interfaz ITodoItemService
, también necesita actualizar la firma del método GetIncompleteItemsAsync()
en el TodoItemService
:
Services/TodoItemService
public async Task<TodoItem[]> GetIncompleteItemsAsync(
ApplicationUser user)
El siguiente paso es actualizar la consulta de la base de datos y agregar un filtro para mostrar solo los elementos creados por el usuario actual. Antes de que pueda hacer eso, debe agregar una nueva propiedad a la base de datos.
Actualizar la base de datos
Deberá agregar una nueva propiedad al modelo de entidad TodoItem
para que cada elemento pueda "recordar" al usuario que lo posee:
Models/TodoItem.cs
public string UserId { get; set; }
Dado que ha actualizado el modelo de entidad utilizado por el contexto de la base de datos, también debe migrar la base de datos. Crea una nueva migración usando dotnet ef
en el terminal:
dotnet ef migrations add AddItemUserId
Esto crea una nueva migración llamada AddItemUserId
que agregará una nueva columna a la tabla Items
, reflejando el cambio realizado en el modelo TodoItem
.
Utilice dotnet ef
de nuevo para aplicarlo a la base de datos:
dotnet ef database update
Actualizar la clase de servicio
Con la base de datos y el contexto de la base de datos actualizados, ahora puede actualizar el método GetIncompleteItemsAsync()
en el TodoItemService
y agregar otra cláusula a la declaración Where
:
Services/TodoItemService.cs
public async Task<TodoItem[]> GetIncompleteItemsAsync(
ApplicationUser user)
{
return await _context.Items
.Where(x => x.IsDone == false && x.UserId == user.Id)
.ToArrayAsync();
}
Si ejecuta la aplicación y se registra o inicia sesión, verá una lista de tareas vacía una vez más. Desafortunadamente, cualquier tarea que intentes agregar desaparece en el éter, porque aún no has actualizado la acción AddItem
para que el usuario la tenga en cuenta.
Actualizar las acciones AddItem y MarkDone
Deberá usar el UserManager
para obtener el usuario actual en los métodos de acción AddItem
y MarkDone
, tal como lo hizo en Index
.
Aquí están los dos métodos actualizados:
Controllers/TodoController.cs
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddItem(TodoItem newItem)
{
if (!ModelState.IsValid)
{
return RedirectToAction("Index");
}
var currentUser = await _userManager.GetUserAsync(User);
if (currentUser == null) return Challenge();
var successful = await _todoItemService
.AddItemAsync(newItem, currentUser);
if (!successful)
{
return BadRequest("Could not add item.");
}
return RedirectToAction("Index");
}
[ValidateAntiForgeryToken]
public async Task<IActionResult> MarkDone(Guid id)
{
if (id == Guid.Empty)
{
return RedirectToAction("Index");
}
var currentUser = await _userManager.GetUserAsync(User);
if (currentUser == null) return Challenge();
var successful = await _todoItemService
.MarkDoneAsync(id, currentUser);
if (!successful)
{
return BadRequest("Could not mark item as done.");
}
return RedirectToAction("Index");
}
Ambos métodos de servicio ahora deben aceptar un parámetro ApplicationUser
. Actualice la definición de la interfaz en ITodoItemService
:
Task<bool> AddItemAsync(TodoItem newItem, ApplicationUser user);
Task<bool> MarkDoneAsync(Guid id, ApplicationUser user);
Y por último, actualice la implementación del método del servicio en TodoItemService
. En el método AddItemAsync
, establece la propiedad UserId
cuando construyas un nuevo TodoItem
:
public async Task<bool> AddItemAsync(
TodoItem newItem, ApplicationUser user)
{
newItem.Id = Guid.NewGuid();
newItem.IsDone = false;
newItem.DueAt = DateTimeOffset.Now.AddDays(3);
newItem.UserId = user.Id;
// ...
}
La cláusula Where
en el método MarkDoneAsync
también debe verificar la ID del usuario, por lo que un usuario deshonesto no puede completar las tareas de otra persona adivinando sus ID:
public async Task<bool> MarkDoneAsync(
Guid id, ApplicationUser user)
{
var item = await _context.Items
.Where(x => x.Id == id && x.UserId == user.Id)
.SingleOrDefaultAsync();
// ...
}
¡Todo listo! Intenta usar la aplicación con dos cuentas de usuario diferentes. Las tareas pendientes se mantienen privadas para cada cuenta.