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

DevCycle image

Fast, Flexible Releases with OpenFeature Built-in

Ship faster on the first feature management platform with OpenFeature built-in to all of our open source SDKs.

Start shipping

Top comments (0)

πŸ‘‹ Kindness is contagious

Dive into this thoughtful piece, beloved in the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A sincere "thank you" can brighten someone's dayβ€”leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay