DEV Community

mohamed Tayel
mohamed Tayel

Posted on • Edited on

Specification Pattern P1

πŸš€ Step 1: Case Study Without Generic Repository & Specification Pattern

πŸ’‘ Goal: Implement a simple Product API using a separate repository for products.


πŸ“‚ Project Structure (Without Generic Repository & Specification Pattern)

πŸ“‚ ProductApp β”œβ”€β”€ πŸ“‚ Core # Domain Layer β”‚ β”œβ”€β”€ πŸ“‚ Entities β”‚ β”‚ β”œβ”€β”€ Product.cs β”‚ β”œβ”€β”€ πŸ“‚ Interfaces β”‚ β”‚ β”œβ”€β”€ IProductRepository.cs β”‚ β”œβ”€β”€ πŸ“‚ Infrastructure # Data Access Layer β”‚ β”œβ”€β”€ πŸ“‚ Data β”‚ β”‚ β”œβ”€β”€ ProductRepository.cs β”‚ β”‚ β”œβ”€β”€ StoreContext.cs β”‚ β”œβ”€β”€ πŸ“‚ API # Presentation Layer β”‚ β”œβ”€β”€ πŸ“‚ Controllers β”‚ β”‚ β”œβ”€β”€ ProductsController.cs β”‚ β”œβ”€β”€ πŸ“‚ DTOs β”‚ β”‚ β”œβ”€β”€ ProductDto.cs 
Enter fullscreen mode Exit fullscreen mode

1️⃣ Install Required NuGet Packages

Before proceeding, install the necessary NuGet packages for Entity Framework Core and SQL Server support:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.Tools 
Enter fullscreen mode Exit fullscreen mode

2️⃣ Create the Product Entity

πŸ“‚ Core/Entities/Product.cs

namespace Core.Entities { public class Product { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Brand { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public decimal Price { get; set; } } } 
Enter fullscreen mode Exit fullscreen mode

3️⃣ Create the ProductDto for API Response

πŸ“‚ API/DTOs/ProductDto.cs

namespace API.DTOs { public class ProductDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Brand { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public decimal Price { get; set; } } } 
Enter fullscreen mode Exit fullscreen mode

4️⃣ Create the IProductRepository Interface

πŸ“‚ Core/Interfaces/IProductRepository.cs

using System.Collections.Generic; using System.Threading.Tasks; using Core.Entities; namespace Core.Interfaces { public interface IProductRepository { Task<Product?> GetByIdAsync(int id); Task<List<Product>> ListAllAsync(); void Add(Product product); void Update(Product product); void Remove(Product product); Task<bool> SaveAllAsync(); } } 
Enter fullscreen mode Exit fullscreen mode

5️⃣ Implement the ProductRepository

πŸ“‚ Infrastructure/Data/ProductRepository.cs

using System.Collections.Generic; using System.Threading.Tasks; using Core.Entities; using Core.Interfaces; using Microsoft.EntityFrameworkCore; namespace Infrastructure.Data { public class ProductRepository : IProductRepository { private readonly StoreContext _context; public ProductRepository(StoreContext context) { _context = context; } public async Task<Product?> GetByIdAsync(int id) { return await _context.Products.FindAsync(id); } public async Task<List<Product>> ListAllAsync() { return await _context.Products.ToListAsync(); } public void Add(Product product) { _context.Products.Add(product); } public void Update(Product product) { _context.Products.Update(product); } public void Remove(Product product) { _context.Products.Remove(product); } public async Task<bool> SaveAllAsync() { return await _context.SaveChangesAsync() > 0; } } } 
Enter fullscreen mode Exit fullscreen mode

6️⃣ Create the StoreContext (DbContext)

πŸ“‚ Infrastructure/Data/StoreContext.cs

using Core.Entities; using Microsoft.EntityFrameworkCore; namespace Infrastructure.Data { public class StoreContext : DbContext { public StoreContext(DbContextOptions<StoreContext> options) : base(options) { } public DbSet<Product> Products { get; set; } } } 
Enter fullscreen mode Exit fullscreen mode

7️⃣ Create the API Controller: ProductsController

πŸ“‚ API/Controllers/ProductsController.cs

using API.DTOs; using Core.Entities; using Core.Interfaces; using Microsoft.AspNetCore.Mvc; namespace API.Controllers { [ApiController] [Route("api/[controller]")] public class ProductsController : ControllerBase { private readonly IProductRepository _productRepository; public ProductsController(IProductRepository productRepository) { _productRepository = productRepository; } [HttpGet] public async Task<ActionResult<IEnumerable<ProductDto>>> GetProducts() { var products = await _productRepository.ListAllAsync(); return Ok(products.Select(p => new ProductDto { Id = p.Id, Name = p.Name, Brand = p.Brand, Type = p.Type, Price = p.Price })); } [HttpGet("{id}")] public async Task<ActionResult<ProductDto>> GetProduct(int id) { var product = await _productRepository.GetByIdAsync(id); if (product == null) return NotFound(); return Ok(new ProductDto { Id = product.Id, Name = product.Name, Brand = product.Brand, Type = product.Type, Price = product.Price }); } [HttpPost] public async Task<ActionResult<ProductDto>> CreateProduct(ProductDto productDto) { var product = new Product { Name = productDto.Name, Brand = productDto.Brand, Type = productDto.Type, Price = productDto.Price }; _productRepository.Add(product); await _productRepository.SaveAllAsync(); return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, productDto); } [HttpPut("{id}")] public async Task<IActionResult> UpdateProduct(int id, ProductDto productDto) { var product = await _productRepository.GetByIdAsync(id); if (product == null) return NotFound(); product.Name = productDto.Name; product.Brand = productDto.Brand; product.Type = productDto.Type; product.Price = productDto.Price; _productRepository.Update(product); await _productRepository.SaveAllAsync(); return NoContent(); } [HttpDelete("{id}")] public async Task<IActionResult> DeleteProduct(int id) { var product = await _productRepository.GetByIdAsync(id); if (product == null) return NotFound(); _productRepository.Remove(product); await _productRepository.SaveAllAsync(); return NoContent(); } } } 
Enter fullscreen mode Exit fullscreen mode

8️⃣ Register StoreContext in Program.cs

πŸ“‚ API/Program.cs

using Infrastructure.Data; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // Register StoreContext with Dependency Injection builder.Services.AddDbContext<StoreContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // Register Repository builder.Services.AddScoped<IProductRepository, ProductRepository>(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); 
Enter fullscreen mode Exit fullscreen mode

🎯 Final Steps: Running the API

  1. Run the following commands to apply migrations:
dotnet ef migrations add InitialCreate --project ../Infrastructure/Data.csproj --startup-project API/ProductAPI.csproj dotnet ef database update --project ../Infrastructure/Data.csproj --startup-project API/ProductAPI.csproj 
Enter fullscreen mode Exit fullscreen mode
  1. Start the API:
dotnet run 
Enter fullscreen mode Exit fullscreen mode
  1. Open Swagger UI at https://localhost:5001/swagger to test the endpoints.

πŸš€ Step 1 is Complete

βœ” We implemented a Product API using a traditional repository.

βœ” The API can Create, Read, Update, and Delete (CRUD) products.

Next Steps: Implement the Generic Repository to improve code reusability. πŸš€

Top comments (0)