DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Specification Pattern P2

๐Ÿ“Œ Step 2.1: Create the Generic Repository Interface

๐Ÿ“‚ Core/Interfaces/IGenericRepository.cs

using Core.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Core.Interfaces
{
    public interface IGenericRepository<T> where T : BaseEntity
    {
        Task<T?> GetByIdAsync(int id);
        Task<IReadOnlyList<T>> ListAllAsync();
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        Task<bool> SaveAllAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

โœ” Works for any entity (Product, Category, etc.).

โœ” Ensures all entities have an Id via BaseEntity.


๐Ÿ“Œ Step 2.2: Create the Generic Repository Implementation

๐Ÿ“‚ Infrastructure/Data/GenericRepository.cs

using Core.Entities;
using Core.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Infrastructure.Data
{
    public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
    {
        private readonly StoreContext _context;

        public GenericRepository(StoreContext context)
        {
            _context = context;
        }

        public async Task<T?> GetByIdAsync(int id)
        {
            return await _context.Set<T>().FindAsync(id);
        }

        public async Task<IReadOnlyList<T>> ListAllAsync()
        {
            return await _context.Set<T>().ToListAsync();
        }

        public void Add(T entity)
        {
            _context.Set<T>().Add(entity);
        }

        public void Update(T entity)
        {
            _context.Set<T>().Attach(entity);
            _context.Entry(entity).State = EntityState.Modified;
        }

        public void Remove(T entity)
        {
            _context.Set<T>().Remove(entity);
        }

        public async Task<bool> SaveAllAsync()
        {
            return await _context.SaveChangesAsync() > 0;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

โœ” Works for all entities (Product, Category, etc.).

โœ” Uses _context.Set<T>() instead of _context.Products.


๐Ÿ“Œ Step 2.3: Remove the ProductRepository

๐Ÿ“Œ Delete ProductRepository.cs (No longer needed).


๐Ÿ“Œ Step 2.4: Implement the ProductsController

๐Ÿ“‚ API/Controllers/ProductsController.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using Core.Entities;
using Core.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
    [ApiController]
    [Route("api/products")]
    public class ProductsController : ControllerBase
    {
        private readonly IGenericRepository<Product> _productRepository;

        public ProductsController(IGenericRepository<Product> productRepository)
        {
            _productRepository = productRepository;
        }

        [HttpGet]
        public async Task<ActionResult<List<Product>>> GetProducts()
        {
            var products = await _productRepository.ListAllAsync();
            return Ok(products);
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<Product>> GetProductById(int id)
        {
            var product = await _productRepository.GetByIdAsync(id);
            if (product == null) return NotFound();
            return Ok(product);
        }

        [HttpPost]
        public async Task<ActionResult> CreateProduct(Product product)
        {
            _productRepository.Add(product);
            await _productRepository.SaveAllAsync();
            return CreatedAtAction(nameof(GetProductById), new { id = product.Id }, product);
        }

        [HttpPut("{id}")]
        public async Task<ActionResult> UpdateProduct(int id, Product product)
        {
            var existingProduct = await _productRepository.GetByIdAsync(id);
            if (existingProduct == null) return NotFound();

            _productRepository.Update(product);
            await _productRepository.SaveAllAsync();
            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<ActionResult> 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

๐Ÿ”น What Changed?

โœ” Uses IGenericRepository<Product> instead of IProductRepository.

โœ” Uses traditional controllers with [ApiController] instead of Minimal API.

โœ” No business logic inside controllers (keeps them clean and focused).


๐Ÿ“Œ Step 2.5: Configure Dependency Injection in Program.cs

๐Ÿ“‚ API/Program.cs

using Core.Interfaces;
using Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Configure Database Connection
builder.Services.AddDbContext<StoreContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Register Generic Repository
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));

// Add Controllers
builder.Services.AddControllers();

var app = builder.Build();

// Middleware
app.UseAuthorization();
app.MapControllers();
app.Run();
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”น What Changed?

โœ” Uses builder.Services.AddControllers() for controllers.

โœ” Registers Generic Repository with Dependency Injection:

builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
Enter fullscreen mode Exit fullscreen mode

โœ” Uses .MapControllers() instead of Minimal API routes.


๐Ÿ“Œ Step 2.6: Testing the API

โœ… Get All Products

GET /api/products
Enter fullscreen mode Exit fullscreen mode

โœ” Returns all products.

โœ… Get Product By ID

GET /api/products/1
Enter fullscreen mode Exit fullscreen mode

โœ” Returns product with ID = 1.

โœ… Create a Product

POST /api/products
Content-Type: application/json

{
    "name": "Laptop",
    "brand": "Dell",
    "type": "Electronics",
    "price": 1500
}
Enter fullscreen mode Exit fullscreen mode

โœ” Creates a new product.

โœ… Update a Product

PUT /api/products/1
Content-Type: application/json

{
    "id": 1,
    "name": "Gaming Laptop",
    "brand": "Dell",
    "type": "Electronics",
    "price": 2000
}
Enter fullscreen mode Exit fullscreen mode

โœ” Updates product details.

โœ… Delete a Product

DELETE /api/products/1
Enter fullscreen mode Exit fullscreen mode

โœ” Deletes product with ID = 1.


๐Ÿš€ Step 2 Complete!

โœ… Replaced the Product Repository with a Generic Repository in .NET 8.

โœ… Used Traditional Controllers with [ApiController].

โœ… Kept the API clean, modular, and scalable.


๐Ÿ“Œ Step 3: Apply the Specification Pattern

Now that we have a Generic Repository, we will implement the Specification Pattern to add filtering, sorting, and pagination dynamically.

Would you like me to proceed with Step 3 in detail? ๐Ÿ˜Š๐Ÿš€

Top comments (0)