ASP .NET Core - Criando um Repositório Assíncrono Genérico
Neste artigo veremos como criar um repositório assíncrono em uma aplicação ASP .NET Core de forma a recordar os conceitos da programação assíncrona. . |
A programação assíncrona é uma técnica de programação paralela, que permite que o processo de trabalho seja executado separadamente do thread principal do aplicativo. Assim que o trabalho for concluído, ele informa ao tópico principal sobre o resultado, se foi bem-sucedido ou não.
Ao usar a programação assíncrona, podemos evitar gargalos de desempenho e melhorar a capacidade de resposta do nosso aplicativo.
Na programação
assíncrona não enviamos as requisições para
o servidor e a bloqueamos enquanto aguardamos as respostas (o tempo que for
necessário). Agora, quando enviamos uma solicitação ao servidor, o pool de
threads delega uma thread a essa solicitação.
Eventualmente, essa thread termina seu trabalho e retorna ao pool de threads,
liberando-se para a próxima solicitação. Em algum momento, os dados serão
obtidos do banco de dados e o resultado precisa ser enviado ao solicitante.
Nesse momento, o pool de threads fornece outra thread para lidar com esse
trabalho, e, uma vez que o trabalho for concluído, a thread usada volta
para o pool de threads.
Abaixo temos uma figura representando o fluxo de trabalho assíncrono :
As palavras async, await e os
tipos de retorno
As palavras-chave async e await desempenham um papel crucial na programação assíncrona. Ao usar essas palavras-chave, podemos escrever facilmente métodos assíncronos sem muito esforço.
Use o modificador async para especificar que um método, uma expressão ou um método anônimo é assíncrono. Se você usar esse modificador em um método ou expressão, ele será referido como um método assíncrono. Exemplo:
public
async Task<int> MetodoAsync() |
Todo o método onde for usado a palavra async deve conter a palavra await. A palavra-chave await oferece uma maneira sem bloqueio de iniciar uma tarefa e, em seguida, continuar a execução quando essa tarefa for concluída. Exemplo:
Se o método que a palavra-chave async modifica não contiver uma expressão ou instrução await, ele será executado de forma síncrona. Um aviso do compilador o alertará sobre quaisquer métodos assíncronos que não contenham instruções await, pois essa situação poderá indicar um erro.
Mas o que mais
caracteriza os métodos assíncronos?
Devemos considerar o seguinte :
A assinatura do método deve incluir o modificador async;
O método deve ter um tipo de retorno da Task<TResult>, Task ou void;
As declarações de método devem incluir pelo menos uma única expressão await - isso diz ao compilador que o método precisa ser suspenso enquanto a operação aguardada estiver ocupada.
Por último, o nome do método deve terminar com o sufixo "async" (mesmo que isso seja mais convencional do que o necessário).
Como exemplo suponha que desejamos criar um método assíncrono para retornar todos os produtos. Para isso temos que usar a palavra-chave async e definir o tipo de retorno do método.
async Task<IEnumerable<Produto>> GetTodosProdutosAsync() |
Ao usar a palavra-chave async, estamos habilitando o uso da palavra-chave await e modificando a forma como os resultados do método são tratados (de síncrono para assíncrono).
A seguir veremos como criar um repositório assíncrono genérico em um projeto ASP .NET Core Web API.
Recursos usados:
Criando o projeto ASP.NET Core Web API na pasta API
Abra o VS 2022, clique em New Project e selecione o template ASP .NET Core Web API e clique em Next;0
Informe o nome ProdutosApi e clique em Next;
A seguir selecione o Framework, Authentication Type e demais configurações conforme mostrada na figura:
Obs: Note que vamos usar Controllers e não vamos usar as minimal APIs.
Clique em Create.
Com o projeto criado vamos criar as pastas Models e Repositoreis no projeto e incluir os seguintes pacotes:
Após isso teremos a seguinte estrutura no projeto :
Na pasta Models vamos criar as classes Produto e Categoria que representam o nosso modelo de domínio:
1- Categoria
public class Categoria { public int CategriaId { get; set; } public string? Nome { get; set; } public string? Descricao { get; set; } public ICollection<Produto> Produtos { get; set; } } |
2- Produto
public class Produto { public int ID { get; set; } public string? Nome { get; set; } public string? Descricao { get; set; } public decimal Preco { get; set; } public string? ImagemUrl { get; set; } public DateTime DataCompra { get; set; } public int Estoque { get; set; } public int CategoriaId { get; set; } public Categoria Categoria { get; set; } } |
Defini as propriedades de navegação nas entidades para que o EF Core possa inferir o relacionamento entre Categoria e Produto.
A seguir vamos criar nesta pasta a classe de contexto AppDbContext que herda de DbContext:
using Microsoft.EntityFrameworkCore; namespace ProdutosApi.Models; public class AppDbContext : DbContext |
No arquivo appsettings.json vamos definir a string de conexão:
{ "ConnectionStrings": { "DefaultConnection": "Data Source=<sua_instancia>;Initial Catalog=CadastroDB;Integrated Security=True" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } |
No arquivo Program vamos registrar o serviço do contexto
var conexaoDB
= builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<AppDbContext>(options => { options.UseSqlServer(conexaoDB); }); |
Criando os Repositórios e as implementações
Agora na pasta Repositories vamos criar a interface IRepositoryBase() :
using System.Linq.Expressions; namespace ProdutosApi.Repositories; public interface IRepositoryBasec<T> where T : class |
Neste código estamos usamos o namespace using System.Linq.Expressions que contém classes e enumerações que permitem representar expressões de código no nível da linguagem como objetos na forma de árvores de expressões;
Note que as assinaturas dos métodos FindAll e FindByCondition retornam IQueryable que nos permite anexar chamadas assíncronas a eles e também podemos aplicar expressões lambdas para filtrar e classificar os dados;
O método
FindByCondidtion() retorna os dados que atendem o
critério informado em tempo de execução via expressão lambada. Estamos usando o
delegate Func, e aplicando o predicate
expression para verificar se o dado atende o
critério (retorna true ou false);
Os métodos Create, Update e Delete não modificam
nenhum dado, eles apenas rastreiam as alterações em uma entidade e aguardam a
execução do método SaveChanges do EF Core.
Observe que na definição da interface do repositório temos uma restrição que diz que o tipo T deve ser uma classe. Existem vários tipos de restrições que podem ser utilizadas para limitar os tipos permitidos. Abaixo temos algumas delas:
Where T: struct, indica que o argumento deve ser um tipo de valor.
Where T: class, indica que T deve ser um tipo de referência.
Where T: new(), obriga que o tipo T tenha um construtor público sem parâmetros;
Where T: nomeClasse, indica que o argumento deve herdar ou ser desse tipo.
Where T: nomeInterface, o argumento deve implementar a interface indicada.
Where T1: T2, T1 indica que o argumento T1 deve ser igual ou herdar o tipo, também argumento do método, T2.
A seguir vamos criar as interfaces para os repositórios de Categoria e Produto.
1- ICategoriaRepository
using ProdutosApi.Models; namespace ProdutosApi.Repositories; public interface ICategoriaRepository : IRepositoryBase<Categoria> |
2- IProdutoRepository
using ProdutosApi.Models; namespace ProdutosApi.Repositories; public interface IProdutoRepository : IRepositoryBase<Produto> |
A seguir vamos criar uma interface para agrupar esses dois repositórios:
3- IRepositoryWrapper
namespace ProdutosApi.Repositories;
public interface IRepositoryWrapper |
A seguir vamos fazer as implementações destas interfaces. Para isso vamos criar uma pasta Implementations dentro da pasta Repositories.
A seguir dentro da pasta Implementations vamos criar cada implementação :
1- BaseRepository
using Microsoft.EntityFrameworkCore; using ProdutosApi.Models; using System.Linq.Expressions; namespace ProdutosApi.Repositories.Implementations; public abstract class BaseRepository<T> : IRepositoryBase<T> where T : class public IQueryable<T> FindAll() public void Create(T entity) public void Update(T entity) public void Delete(T entity) |
2- CategoriaRepository
using Microsoft.EntityFrameworkCore; using ProdutosApi.Models; namespace ProdutosApi.Repositories.Implementations; public class CategoriaRepository : BaseRepository<Categoria>, ICategoriaRepository public async Task<IEnumerable<Categoria>> GetAllCategoriasAsync() |
3- ProdutoRepository
using Microsoft.EntityFrameworkCore; using ProdutosApi.Models; namespace ProdutosApi.Repositories.Implementations; public class ProdutoRepository : BaseRepository<Produto>, IProdutoRepository public async Task<IEnumerable<Produto>> GetAllProdutosAsync()
|
4- RepositoryWrapper
using ProdutosApi.Models;
namespace ProdutosApi.Repositories.Implementations public IProdutoRepository ProdutoRepo public RepositoryWrapper(AppDbContext repositoryContext) public async Task SaveAsync() |
Agora podemos implementar os Controllers. Eu vou mostrar a implementação do controlador ProdutosController e vou deixar a seu cargo implementar o controlador para Categorias.
1- ProdutosController
using Microsoft.AspNetCore.Mvc; using ProdutosApi.Models; using ProdutosApi.Repositories;
namespace ProdutosApi.Controllers public ProdutosController(ILogger<ProdutosController> logger,
[HttpGet] [HttpGet("{id}", Name = "ProdutoById")] if (produto == null) [HttpPost] if (!ModelState.IsValid) _repoContext.ProdutoRepo.CreateProduto(produto); return CreatedAtRoute("ProdutoById", new { id = produto.ID }, produto); [HttpPut("{id}")] if (!ModelState.IsValid) var produtoEntity = await _repoContext.ProdutoRepo.GetProdutoByIdAsync(id); _repoContext.ProdutoRepo.UpdateProduto(produtoEntity); return NoContent(); [HttpDelete("{id}")] _repoContext.ProdutoRepo.DeleteProduto(produto); return NoContent(); |
Para que tudo funcione corretamente temos que registrar os serviços no arquivo Program:
using Microsoft.EntityFrameworkCore; using ProdutosApi.Models; using ProdutosApi.Repositories; using ProdutosApi.Repositories.Implementations;
var builder = WebApplication.CreateBuilder(args); var conexaoDB = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddScoped<IRepositoryWrapper, RepositoryWrapper>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); |
Executando o projeto teremos o resultado a seguir:
Pegue o projeto completo aqui : ProdutosApi_RepoAsync.zip
E estamos conversados...
"Portanto, lembrai-vos de que vós noutro tempo éreis
gentios na carne, e chamados incircuncisão pelos que na carne se chamam
circuncisão feita pela mão dos homens;
Que naquele tempo estáveis sem Cristo, separados da comunidade de Israel, e
estranhos às alianças da promessa, não tendo esperança, e sem Deus no
mundo."
Efésios 2:11,12
Referências:
ASP .NET Core - Implementando a segurança com
ASP.NET Core MVC - Criando um Dashboard .
C# - Gerando QRCode - Macoratti
ASP .NET - Gerando QRCode com a API do Google
ASP .NET Core 2.1 - Como customizar o Identity
Usando o ASP .NET Core Identity - Macoratti
ASP .NET Core - Apresentando o IdentityServer4
ASP .NET Core 3.1 - Usando Identity de cabo a rabo