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. πŸš€

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more