ASP .NET Core - Implementando a Onion Architecture - IV
Neste artigo vamos criar uma aplicação ASP .NET Core WEBAPI fazendo uma implementação básica da arquitetura em Cebola ou Onion Architecture. |
Continuando a terceira parte do artigo vamos iniciar a implementação da camada Infrastructure no projeto eStore.Persistence.
Implementação da camada Infrastructure : projeto eStore.Persistence
A camada Infrastructure é a camada mais externa da arquitetura cebola que lida com as necessidades de infraestrutura e fornece a implementação de suas interfaces de repositório. É aqui que conectamos a lógica de acesso a dados ou a lógica de registro ou a lógica de chamadas de serviço. Apenas a camada de infraestrutura sabe sobre o banco de dados e a tecnologia de acesso a dados (Entity framework ou Ado.net) e outras camadas não sabem nada sobre de onde vêm os dados e como estão sendo armazenados.
Nesta camada temos os seguintes projetos:
Vamos iniciar incluindo as referências aos seguintes pacotes do EF Core neste projeto:
Para incluir os pacotes use o menu Tools->..-> Manage Nuget Packages for Solution e na guia Browse selecione e instale os pacotes ou abra a janela Package Manager Console e digite o comando: install-package <nome-pacote>
A versão atual de todos os pacotes usados é a versão 5.0.4.
A seguir vamos criar 3 pastas no projeto Persistence :
Definindo o contexto e configurando as entidades
Abra a pasta Context e crie nesta pasta a classe ApplicationDbContext que herda de DbContext.
Nesta classe vamos definir as opções do Contexto usando DbContextOptions e passando as opções para a classe base. As definições são feitas quando do registro do serviço do contexto no arquivo Startup no método ConfigureServices onde definimos o provedor do banco de dados e a string de conexão.
Nesta classe definimos o mapeamento entre as entidade do domínio e o banco de dados usando a classe DbSet<T>.
No método OnModelCreating vamos usar o método ApplyConfigurationsFromAssembly para aplicar algumas configurações que iremos fazer nas entidades Product e Category definidas neste assembly.
Essas configurações devem ser criadas em um arquivo separado e implementar a interface IEntityTypeConfiguration<T>.
Nota: O método OnModelCreating é chamado quando seu contexto é criado pela primeira vez para construir o modelo e seus mapeamentos na memória;
1- ApplicationDbContext
using eStore.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace eStore.Persistence.Context
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
}
}
|
A seguir vamos criar a pasta EntityConfiguration no projeto e nesta classe vamos criar as classes onde vamos definir as configurações usando a Fluent API para as entidades do contexto.
Vamos começar com a entidade Category criando a classe CategoryConfiguration que implementa a interface IEntityTypeConfiguration<Category>
2- CategoryConfiguration
using eStore.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace eStore.Persistence.EntityConfiguration
{
public class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.HasKey(t => t.Id);
builder.Property(p => p.Name).HasMaxLength(100).IsRequired();
builder.HasData(
new Category(1, "Material Escolar"),
new Category(2, "Eletrônicos"),
new Category(3, "Acessórios")
);
}
}
}
|
Na implementação do método Configure usamos uma instância de EntityTypeBuilder<T> para definir as configurações que o EF Core vai usar quando aplicar o Migrations e também usamos a propriedade HasData que será usada para popular a tabela Category no banco de dados com os dados definidos.
Vamos repetir esse procedimento criando a classe ProductConfiguration:
using eStore.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace eStore.Persistence.EntityConfiguration
{
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasKey(t => t.Id);
builder.Property(p => p.Name).HasMaxLength(100).IsRequired();
builder.Property(p => p.Description).HasMaxLength(200).IsRequired();
builder.Property(p => p.Price).HasPrecision(10, 2);
builder.HasOne(e => e.Category).WithMany(e => e.Products).HasForeignKey(e => e.CategoryId);
}
}
}
|
Aqui neste código estamos definindo as configurações para algumas propriedades da entidade Product que serão usadas pelo EF Core na aplicação do Migrations e geração da tabela Product no banco de dados.
Usamos também o
método HasOne da Fluent API do EF Core para
configurar um lado de um relacionamento um-para- muitos
ou uma extremidade de um relacionamento um para um.
Para configurar totalmente um relacionamento válido, é necessário seguir o
padrão Has/With e emparelhar o uso de
HasOne com o método WithMany
onde especificamos a chave estrangeira como sendo
CategoryId.
Implementando os repositórios para Category e Product
Os repositórios destinam-se a criar uma camada de abstração entre a camada de acesso a dados e a camada de lógica de negócios de um aplicativo. A implementação desses padrões pode ajudar a isolar o aplicativo de alterações no armazenamento de dados e pode facilitar o teste de unidade automatizado ou TDD (desenvolvimento orientado por testes).
Neste projeto vamos implementar as classes concretas do repositório para cada tipo de entidade. Para a entidade Category e para a entidade Product, visto que já definimos as respectivas interfaces na camada Domain. Assim ao criar uma instância do repositório, vamos usar a interface para passar uma referência a qualquer objeto que implemente a interface do repositório.
A principal justificativa que eu vou dar para não implementar um repositório genérico é a seguinte:
"Um repositório é parte de um domínio que esta sendo modelado e um domínio não é genérico, Nem todas as entidades podem ser excluídas, nem todas as entidades podem ser adicionadas, nem todas as entidades têm um repositório. As consultas variam muito; a API do repositório torna-se tão única quanto a própria entidade."
Abra a pasta Repositories do projeto e nesta pasta crie a classe CategoryRepository que vai implementar a interface ICategoryRepository criada na camada Domain.
1- CategoryRepository
using eStore.Domain.Entities;
using eStore.Domain.Interfaces;
using eStore.Persistence.Context;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace eStore.Persistence.Repositories
{
public class CategoryRepository : ICategoryRepository
{
private ApplicationDbContext _categoryContext;
public CategoryRepository(ApplicationDbContext context)
{
_categoryContext = context;
}
public async Task<IEnumerable<Category>> GetCategories()
{
return await _categoryContext.Categories.ToListAsync();
}
public async Task<Category> GetById(int? id)
{
return await _categoryContext.Categories.FindAsync(id);
}
public async Task<Category> Create(Category product)
{
_categoryContext.Add(product);
var result = await _categoryContext.SaveChangesAsync();
return product;
}
public async Task<Category> Update(Category product)
{
_categoryContext.Update(product);
await _categoryContext.SaveChangesAsync();
return product;
}
public async Task<Category> Remove(Category product)
{
_categoryContext.Remove(product);
await _categoryContext.SaveChangesAsync();
return product;
}
}
|
No construtor da classe CategoryRepository injetamos uma instância do nosso contexto - ApplicationDbContext - e assim poderemos acessar os dados da nossa aplicação via contexto e realizar as consultas e persistência dos dados.
Implementamos a seguir o contrato definido na interface para os métodos que permite acessar, incluir, alterar e excluir informações. Fizemos isso criando métodos assíncronos usando Task/async e await.
Agora vamos realizar a implementação do repositório para produtos criando a classe ProductRepository:
2- ProductRepository
using eStore.Domain.Entities;
using eStore.Domain.Interfaces;
using eStore.Persistence.Context;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace eStore.Persistence.Repositories
{
public class ProductRepository : IProductRepository
{
private ApplicationDbContext _productContext;
public ProductRepository(ApplicationDbContext context)
{
_productContext = context;
}
public async Task<IEnumerable<Product>> GetProducts()
{
return await _productContext.Products.ToListAsync();
}
public async Task<Product> GetById(int? id)
{
return await _productContext.Products.FindAsync(id);
}
public async Task<Product> Create(Product product)
{
_productContext.Add(product);
var result = await _productContext.SaveChangesAsync();
return product;
}
public async Task<Product> Update(Product product)
{
_productContext.Update(product);
await _productContext.SaveChangesAsync();
return product;
}
public async Task<Product> Remove(Product product)
{
_productContext.Remove(product);
await _productContext.SaveChangesAsync();
return product;
}
}
}
|
Neste código injetamos no construtor a instância do nosso contexto e implementamos os métodos definidos na interface IProductRepository.
Precisamos agora registrar os serviços e os repositórios criados e vamos fazer isso na próxima parte do artigo no projeto Shared.
"Não estejais
inquietos por coisa alguma; antes as vossas petições sejam em tudo conhecidas
diante de Deus pela oração e súplica, com ação de graças."
Filipenses 4:6
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