Introduction
Design patterns are proven solutions to common problems in software design. In ASP.NET MVC applications, implementing these patterns can help improve code maintainability, scalability, and testability. This article explores key design patterns used in ASP.NET MVC projects and provides practical examples of their implementation.
1. Repository Pattern
The repository pattern abstracts data access logic, providing a clean separation between the business logic and the data layer.
Benefits
- Promotes loose coupling between the application and data storage.
- Simplifies unit testing by allowing mocking of the data layer.
Implementation
// IRepository Interface
public interface IRepository<T> where T : class
{
IEnumerable<T> GetAll();
T GetById(int id);
void Insert(T entity);
void Update(T entity);
void Delete(int id);
}
// Repository Implementation
public class Repository<T> : IRepository<T> where T : class
{
private readonly ApplicationDbContext _context;
private DbSet<T> entities;
public Repository(ApplicationDbContext context)
{
_context = context;
entities = context.Set<T>();
}
public IEnumerable<T> GetAll() => entities.ToList();
public T GetById(int id) => entities.Find(id);
public void Insert(T entity) => entities.Add(entity);
public void Update(T entity) => _context.Entry(entity).State = EntityState.Modified;
public void Delete(int id) => entities.Remove(GetById(id));
}
2. Unit of Work Pattern
The unit of work pattern helps manage transactions by coordinating changes across multiple repositories in a single transaction.
Benefits
- Ensures consistency across repositories.
- Reduces redundant calls to the database.
Implementation
public class UnitOfWork : IDisposable
{
private readonly ApplicationDbContext _context;
private IRepository<Product> _productRepository;
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
}
public IRepository<Product> ProductRepository
{
get
{
return _productRepository ??= new Repository<Product>(_context);
}
}
public void Save()
{
_context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
3. Dependency Injection (DI)
Dependency injection (DI) injects dependencies into controllers or classes instead of creating them directly.
Benefits
- Reduces tight coupling.
- Simplifies testing by allowing dependency substitution.
Implementation
Configure DI in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IRepository<Product>, Repository<Product>>();
services.AddScoped<UnitOfWork>();
services.AddControllersWithViews();
}
Inject Dependencies in Controller
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
}
Products Controller
using System.Linq;
using System.Web.Mvc;
using MVCDesignPatternsApp.Models;
using MVCDesignPatternsApp.Repositories;
public class ProductsController : Controller
{
private readonly UnitOfWork _unitOfWork;
public ProductsController(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
// GET: Products
public ActionResult Index()
{
var products = _unitOfWork.ProductRepository.GetAll().ToList();
return View(products);
}
// GET: Products/Details/5
public ActionResult Details(int id)
{
var product = _unitOfWork.ProductRepository.GetById(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
// GET: Products/Create
public ActionResult Create()
{
return View();
}
// POST: Products/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
_unitOfWork.ProductRepository.Insert(product);
_unitOfWork.Save();
return RedirectToAction("Index");
}
return View(product);
}
// GET: Products/Edit/5
public ActionResult Edit(int id)
{
var product = _unitOfWork.ProductRepository.GetById(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
// POST: Products/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
_unitOfWork.ProductRepository.Update(product);
_unitOfWork.Save();
return RedirectToAction("Index");
}
return View(product);
}
// GET: Products/Delete/5
public ActionResult Delete(int id)
{
var product = _unitOfWork.ProductRepository.GetById(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
// POST: Products/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
_unitOfWork.ProductRepository.Delete(id);
_unitOfWork.Save();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_unitOfWork.Dispose();
}
base.Dispose(disposing);
}
}

Join the conversation! Your thoughts help the community grow.