ASP .NET Core -  Criando uma Web API com Dapper - II


 Hoje continuar a criação de uma Web API ASP .NET Core usando o Dapper.

Continuando a primeira parte do artigo vamos implementar o padrão repository e a seguir criar a API.

Implementando o padrão Repository

Na pasta Repositories do projeto crie a interface ITarefaRepository :

using API.Data;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace API.Repositories
{
    public interface ITarefaRepository
    {
        Task<List<Tarefa>> GetTarefasAsync();
        Task<Tarefa> GetTarefaByIdAsync(int id);
        Task<TarefaContainer> GetTarefasEContadorAsync();
        Task<int> SaveAsync(Tarefa novaTarefa);
        Task<int> UpdateTarefaStatusAsync(Tarefa atualizaTarefa);
        Task<int> DeleteAsync(int id);
    }
}

Esta interface define o contrato para expor os serviços da API. Usamos a classe Task de forma que a implementação será feita de forma assíncrona.

A seguir na mesma pasta crie a classe concreta TarefaRepository que implementa esta interface :

using API.Data;
using Dapper;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace API.Repositories
{
    public class TarefaRepository : ITarefaRepository
    {
        private DbSession _db;
        public TarefaRepository(DbSession dbSession)
        {
            _db = dbSession;
        }
        public async Task<List<Tarefa>> GetTarefasAsync()
        {
            using (var conn = _db.Connection)
            {
                string query = "SELECT * FROM Tarefas";
                List<Tarefa> tarefas = (await conn.QueryAsync<Tarefa>(sql: query)).ToList();
                return tarefas;
            }
        }
        public async Task<Tarefa> GetTarefaByIdAsync(int id)
        {
            using (var conn = _db.Connection)
            {
                string query = "SELECT * FROM Tarefas WHERE Id = @id";
                Tarefa tarefa = await conn.QueryFirstOrDefaultAsync<Tarefa>
                    (sql: query, param: new { id });
                return tarefa;
            }
        }
        public async Task<TarefaContainer> GetTarefasEContadorAsync()
        {
            using (var conn = _db.Connection)
            {
                string query =
                    @"SELECT COUNT(*) FROM Tarefas
    	          SELECT * FROM Tarefas";
                var reader = await conn.QueryMultipleAsync(sql: query);
                return new TarefaContainer
                {
                    Contador = (await reader.ReadAsync<int>()).FirstOrDefault(),
                    Tarefas = (await reader.ReadAsync<Tarefa>()).ToList()
                };
            }
        }
        public async Task<int> SaveAsync(Tarefa novaTarefa)
        {
            using (var conn = _db.Connection)
            {
                string command = @"NSERT INTO Tarefas(Descricao, IsCompleta)
    		VALUES(@Descricao, @IsCompleta)";
                var result = await conn.ExecuteAsync(sql: command, param: novaTarefa);
                return result;
            }
        }
        public async Task<int> UpdateTarefaStatusAsync(Tarefa atualizaTarefa)
        {
            using (var conn = _db.Connection)
            {
                string command = @"
    		     UPDATE Tarefas SET IsCompleta = @IsCompleta WHERE Id = @Id";
                var result = await conn.ExecuteAsync(sql: command, param: atualizaTarefa);
                return result;
            }
        }
        public async Task<int> DeleteAsync(int id)
        {
            using (var conn = _db.Connection)
            {
                string command = @"DELETE FROM Tarefas WHERE Id = @id";
                var resultado = await conn.ExecuteAsync(sql: command, param: new { id });
                return resultado;
            }
        }
    }
}

Na implementação da interface estamos injetando uma instância da classe DbSession e  a seguir definimos métodos assíncronos usando async/await e estamos usando uma instrução using onde usamos o contexto da conexão que
ao final vai liberar a conexão desativando o objeto Connection.

Nota: A instrução using permite usar a conexão obtida da instancia de DbSession e ao final vai fechar e liberar o objeto connection.

Na implementação dos métodos usamos os seguintes recursos:

1- GetTarefasAsync

Aqui usamos o método QueryAsync<T> para executar a consulta que a considera como um parâmetro de entrada e retorna os dados mapeando para o tipo T.

2- GetTarefaByIdAsync

Usamos o método de extensão 'QueryFirstOrDefaultAsync' que retorna o primeiro registro de acordo com o filtro que passamos na consulta.  Para este método de extensão, estamos passando valores de filtro como objetos anônimos usando a palavra-chave 'new'.

3- GetTarefasEContadorAsync

O Dapper tem a capacidade de retornar os conjuntos de resultados de várias tabelas. Aqui criamos um novo modelo para capturar mais de um resultado na classe TarefaContainer.

Criamos duas consultas, uma retorna a contagem das tarefas e a outra retorna o total das tarefas. A seguir usamos o método 'QueryMultipleAsync' que executa a consulta e retorna vários conjuntos de resultados.

Depois lemos o primeiro conjunto de resultados, usando ReadAsync<int> para obter o total das tarefas e depois lemos o segundo conjunto de resultados, ou seja, coleção de tarefas usando ReadAsync<Tarefa>.

4 - SaveAsync

Este método vai incluir uma nova tarefa e criamos a consulta SQL usando INSERT INTO e estamos usando parâmetros dinâmicos : '@Descricao'  e '@IsCompleta'.  O método de extensão 'ExecuteAsync' executa o comando fornecido.

Este método recebe como parâmetro 'param' uma 'novaTarefa' (instância de tarefa), onde cada nome das propriedades definidas em 'novaTarefa' deve corresponder ao nome dos parâmetro dinâmicos.

O tipo de retorno de 'ExecuteAsync' é um valor inteiro para representar o número de linhas afetadas na execução do comando, portanto, se retornar zero, não haverá efeito do comando no banco de dados.

A lógica do método UpdateTarefaStatusAsync é a mesma usada em SaveAsync , e,  a lógica do método DeleteAsync é a mesma que a usada em GetTarefaByIdAsync.

Para concluir precisamos agora registrar tanto a classe DbSession como o Repository como um serviço na classe Startup:

 public void ConfigureServices(IServiceCollection services)
 {
            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
            });
            services.AddScoped<DbSession>();
            services.AddTransient<ITarefaRepository, TarefaRepository>();
 }

E desta forma podemos agora criar o controlador TarefasController na pasta Controllers:

using API.Data;
using API.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TarefasController : ControllerBase
    {
        private readonly ITarefaRepository _tarefaRepo;
        public TarefasController(ITarefaRepository tarefaRepo)
        {
            _tarefaRepo = tarefaRepo;
        }
        [HttpGet]
        [Route("tarefas")]
        public async Task<IActionResult> GetTarefasAsync()
        {
            var result = await _tarefaRepo.GetTarefasAsync();
            return Ok(result);
        }
        [HttpGet]
        [Route("tarefa")]
        public async Task<IActionResult> GetTodoItemByIdAsync(int id)
        {
            var tarefa = await _tarefaRepo.GetTarefaByIdAsync(id);
            return Ok(tarefa);
        }
        [HttpGet]
        [Route("tarefascontador")]
        public async Task<IActionResult> GetTodosAndCountAsync()
        {
            var resultado = await _tarefaRepo.GetTarefasEContadorAsync();
            return Ok(resultado);
        }
        [HttpPost]
        [Route("criartarefa")]
        public async Task<IActionResult> SaveAsync(Tarefa novaTarefa)
        {
            var result = await _tarefaRepo.SaveAsync(novaTarefa);
            return Ok(result);
        }
        [HttpPost]
        [Route("atualizastatus")]
        public async Task<IActionResult> UpdateTodoStatusAsync(Tarefa atualizaTarefa)
        {
            var result = await _tarefaRepo.UpdateTarefaStatusAsync(atualizaTarefa);
            return Ok(result);
        }
        [HttpDelete]
        [Route("deletatarefa")]
        public async Task<IActionResult> DeleteAsync(int id)
        {
            var resultado = await _tarefaRepo.DeleteAsync(id);
            return Ok(resultado);
        }
    }
}

No controlador injetamos uma instância do repositório ITarefaRepository e com essa instância estamos realizando o CRUD básico na tabela Tarefas.

Ao executar o projeto teremos a apresentação dos endpoints da nossa API exibidos na interface do Swagger:

Basta testar cada um deles e confirmar a implementação feita conforme foi mostrado neste vídeo no youtube: https://www.youtube.com/watch?v=85w8m-EdRs8&t=390s

Pegue o projeto aqui :   DapperAPI.zip (sem as referências)

"Voz do que clama no deserto: Preparai o caminho do Senhor; endireitai no ermo vereda a nosso Deus.
Todo o vale será exaltado, e todo o monte e todo o outeiro será abatido; e o que é torcido se endireitará, e o que é áspero se aplainará."
Isaías 40:3,4

Referências:


José Carlos Macoratti