Using identity in the application

Os itens da lista de tarefas a fazer ainda são compartilhados entre todos os usuários, porque as entidades de tarefas armazenadas não estão vinculadas a um usuário específico. Agora que o atributo [Authorize] garante que você deve estar logado para ver a view de tarefas, você pode filtrar a consulta do banco de dados com base em quem está logado.

Primeiro, injete a UserManager<ApplicationUser> no 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;
    }

    // ...
}

Você precisará adicionar um novo comando using no início do arquivo:

using Microsoft.AspNetCore.Identity;

A classe UserManager é parte do ASP.NET Core Identity. Você pode utilizá-la para recuperar o usuário logado na action 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);
}

O novo código no topo do action método usa o UserManager para procurar o usuário logado a partir da propriedade User disponível na action:

var currentUser = await _userManager.GetUserAsync(User);

Se houver um usuário logado, a propriedade User contém um objeto com algumas (mas não todas) informações do usuário. O UserManager usa isto para procurar os detalhes completos do usuário no banco de dados através do método GetUserAsync().

O valor de currentUser nunca deve ser nulo, porque o atributo [Authorize] está presente no controller. No entanto, é uma boa idéia fazer uma verificação a mais para garantir. Você pode usar o método Challenge() para forçar o usuário a efetuar login novamente se suas informações estiverem faltando.

if (currentUser == null) return Challenge();

Já que você está passando um parâmetro ApplicationUser paraGetIncompleteItemsAsync(), você precisará atualizar a interface ITodoItemService:

Services/ITodoItemService.cs

public interface ITodoItemService
{
    Task<TodoItem[]> GetIncompleteItemsAsync(
        ApplicationUser user);

    // ...
}

Como você mudou a interface ITodoItemService, você também precisa atualizar a assinatura do método GetIncompleteItemsAsync() no TodoItemService:

Services/TodoItemService

public async Task<TodoItem[]> GetIncompleteItemsAsync(
    ApplicationUser user)

O próximo passo é atualizar a consulta do banco de dados e adicionar um filtro para mostrar apenas os itens criados pelo usuário atual. Antes de fazer isso, você precisa adicionar uma nova propriedade ao banco de dados.

Atualizando o banco de dados

Você precisará adicionar uma nova propriedade ao entity model TodoItem para que cada tarefa possa "lembrar" o usuário que o possui:

Models/TodoItem.cs

public string UserId { get; set; }

Como você atualizou o entity model usado pelo context do banco de dados, também é necessário executar o migrate do banco de dados. Crie uma nova migration usando o comando dotnet ef no terminal:

dotnet ef migrations add AddItemUserId

Isso cria uma nova migration chamada AddItemUserId que adicionará uma nova coluna à tabela Items, espelhando a alteração que você fez no model TodoItem.

Use o comando dotnet ef novamente para aplicá-lo ao banco de dados:

dotnet ef database update

Atualizando a service class

Com o banco de dados e seu contexto atualizados, você pode atualizar o método GetIncompleteItemsAsync() no TodoItemService e adicionar outra cláusula à instrução 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();
}

Se você executar a aplicatção e se registrar ou efetuar login, verá uma lista de tarefas vazia novamente. Infelizmente, todos os itens que você tenta adicionar desaparecem porque você ainda não atualizou a action AddItem para tratar os usuários das tarefas ainda.

Atualizando as actions AddItem e MarkDone

Você precisará usar o UserManager para obter o usuário atual nas actions AddItem e MarkDone, assim como você fez em Index.

Aqui estão os dois métodos atualizados:

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 service methods agora devem aceitar um parâmetro ApplicationUser. Atualize a definição da interface em ITodoItemService:

Task<bool> AddItemAsync(TodoItem newItem, ApplicationUser user);

Task<bool> MarkDoneAsync(Guid id, ApplicationUser user);

E por último, atualize as implementações do service method no TodoItemService. No método AddItemAsync, defina a propriedade UserId ao construir um new 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;

    // ...
}

A cláusula Where no método MarkDoneAsync também precisa verificar o ID do usuário, de forma que um usuário não autorizado não possa completar as tarefas de outra pessoa adivinhando seus IDs:

public async Task<bool> MarkDoneAsync(
    Guid id, ApplicationUser user)
{
    var item = await _context.Items
        .Where(x => x.Id == id && x.UserId == user.Id)
        .SingleOrDefaultAsync();

    // ...
}

Pronto! Tente usar a aplicação com duas contas de usuário diferentes. As tarefas agora estão privadas para cada conta de usuário.

Last updated

Was this helpful?