Initial Project Structure:
- Projects:
- Shary.API
- Shary.Core
- Shary.Repository
- Shary.Service
Core (Entities):
- Create
BaseEntity
,ProductCategory
,ProductBrand
,Product
Product
properties:Name
,Description
,Price(decimal)
,PictureUrl
- Navigation:
Category
(withCategoryId
),Brand
(withBrandId
)
Repository (Data Layer):
StoreContext : DbContext
inData
folder- Constructor takes
DbContextOptions
and passes tobase
(DI) - Add
DbSet
forProducts
,ProductBrands
,ProductCategories
OnModelCreating
usesmodelBuilder.ApplyConfigurationsFromAssembly(...)
- Constructor takes
Config
Folder 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
DataSeeding
folder - Add
StoreContextSeed
class for seeding data
Generic Repository:
- In
Core
AddRepositories.Contract
:- Add
IGenericRepository<T>
whereT : BaseEntity
- Methods:
GetAsync
,GetAllAsync
- Methods:
- Add
- In
Repository
:- Add
GenericRepository<T>
implementingIGenericRepository<T>
- Inject
StoreContext
in the constructor
- Inject
- Add
Controllers Setup:
- In
Shary.API
underControllers
folder:- Create
BaseApiController : ControllerBase
with[Route("api/[controller]")]
and[ApiController]
. - Create
ProductsController : BaseApiController
.- Inject
IGenericRepository<Product>
via constructor. - Add endpoint
GetProducts
returningTask<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
Specifications
folder. 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
SpecificationEvaluator
class to applyCriteria
andIncludes
to queries. - In
GenericRepository
add: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 includeBrand
andCategory
.