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)