Initial Project Structure:
- Projects:
- Shary.API
- Shary.Core
- Shary.Repository
- Shary.Service
Core (Entities):
- Create
BaseEntity,ProductCategory,ProductBrand,Product Productproperties:Name,Description,Price(decimal),PictureUrl- Navigation:
Category(withCategoryId),Brand(withBrandId)
Repository (Data Layer):
StoreContext : DbContextinDatafolder- Constructor takes
DbContextOptionsand passes tobase(DI) - Add
DbSetforProducts,ProductBrands,ProductCategories OnModelCreatingusesmodelBuilder.ApplyConfigurationsFromAssembly(...)
- Constructor takes
ConfigFolder inData:ProductConfigurations : IEntityTypeConfiguration<Product>- In
Configure:builder.Property(p => p.Name).IsRequired().HasMaxLength(100)builder.HasOne(p => p.Brand).WithMany().HasForeignKey(p => p.BrandId)- Configure all other properties and relationships similarly
Dependency Injection (in Shary.API Program):
builder.Services.AddDbContext<StoreContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));Migrations:
- Run:
add-migration "ProductModule" -O Data/Migrations - Update DB:
update-database
Apply Migrations at runtime:
IServiceScope scope = app.Services.CreateScope();
StoreContext _dbContext = scope.ServiceProvider.GetRequiredService<StoreContext>();
ILoggerFactory loggerFactory = scope.ServiceProvider.GetRequiredService<ILoggerFactory>();
try
{
await _dbContext.Database.MigrateAsync();
}
catch (Exception ex)
{
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "Error during migration");
}Data Seeding:
- Add
DataSeedingfolder - Add
StoreContextSeedclass for seeding data
Generic Repository:
- In
CoreAddRepositories.Contract:- Add
IGenericRepository<T>whereT : BaseEntity- Methods:
GetAsync,GetAllAsync
- Methods:
- Add
- In
Repository:- Add
GenericRepository<T>implementingIGenericRepository<T>- Inject
StoreContextin the constructor
- Inject
- Add
Controllers Setup:
- In
Shary.APIunderControllersfolder:- Create
BaseApiController : ControllerBasewith[Route("api/[controller]")]and[ApiController]. - Create
ProductsController : BaseApiController.- Inject
IGenericRepository<Product>via constructor. - Add endpoint
GetProductsreturningTask<ActionResult<IEnumerable<Product>>>. - Use
Include(through specifications) to include related entities.
- Inject
- Create
Register Services:
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));Specification Pattern:
- In
Shary.Core:- Create
Specificationsfolder. ISpecification<T>interface:Expression<Func<T, bool>> Criteria { get; }List<Expression<Func<T, object>>> Includes { get; }
BaseSpecifications<T>class implementingISpecification<T>:- Handle Criteria and Includes lists.
- Provide methods to add criteria and includes.
- Create
In Repository Layer:
- Create
SpecificationEvaluatorclass to applyCriteriaandIncludesto queries. - In
GenericRepositoryadd:Task<IEnumerable<T>> GetAllWithSpecAsync(ISpecification<T> spec);Task<T?> GetWithSpecAsync(ISpecification<T> spec);- Implement these methods using
SpecificationEvaluator.
Example Specification:
ProductWithBrandAndCategorySpecification : BaseSpecifications<Product>- Add includes for Brand and Category.
Update Endpoints:
- In
ProductsController, use specification-based methods forGetProducts()to includeBrandandCategory.