10 C# Design Patterns Every ASP.NET Core Developer Should Know (With Simple Examples)
When developers first learn C#, they often focus on syntax, LINQ, async/await, and Entity Framework.
But as applications grow, writing code that is easy to maintain, extend, and test becomes far more important than simply making it work.
That’s where Design Patterns come in.
Think of design patterns as proven solutions to common software design problems. They aren’t frameworks or libraries — they’re reusable approaches that help you build cleaner applications.
The good news?
You’re probably already using several of these patterns every day in ASP.NET Core without realizing it.
Let’s explore 10 essential design patterns every .NET developer should know, with simple examples.
1. Repository Pattern
Problem
You don’t want your controllers talking directly to Entity Framework.
Instead, create a repository.
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
}Implementation
public class ProductRepository : IProductRepository
{
private readonly AppDbContext _context;public ProductRepository(AppDbContext context)
{
_context = context;
} public async Task<Product?> GetByIdAsync(int id)
{
return await _context.Products.FindAsync(id);
}
}
Controller
public class ProductsController
{
private readonly IProductRepository _repository; public ProductsController(IProductRepository repository)
{
_repository = repository;
}
}✅ Makes data access easier to test.
2. Dependency Injection Pattern
ASP.NET Core uses this everywhere.
Instead of creating dependencies yourself…
❌
var emailService = new EmailService();Inject them.
public class UserService
{
private readonly IEmailService _emailService; public UserService(IEmailService emailService)
{
_emailService = emailService;
}
}Registration
builder.Services.AddScoped<IEmailService, EmailService>();Benefits
- Loose coupling
- Easier testing
- Better maintainability
3. Factory Pattern
Instead of using new everywhere…
Create objects through a factory.
public interface INotification
{
void Send();
}public class EmailNotification : INotification
{
public void Send()
{
Console.WriteLine("Email sent");
}
}Factory
public static class NotificationFactory
{
public static INotification Create()
{
return new EmailNotification();
}
}Usage
var notification = NotificationFactory.Create();
notification.Send();Useful when object creation becomes complex.
4. Singleton Pattern
Only one instance exists.
Registration
builder.Services.AddSingleton<AppSettings>();Every request shares the same object.
Common examples
- Cache
- Configuration
- Logger
5. Strategy Pattern
Choose behavior at runtime.
Interface
public interface IPaymentStrategy
{
void Pay(decimal amount);
}Implementation
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine("Paid using card");
}
}Usage
paymentStrategy.Pay(100);Perfect for multiple payment methods.
6. Builder Pattern
Useful when creating complex objects.
Get Sachin Ghadi’s stories in your inbox
Join Medium for free to get updates from this writer.
Instead of
new Employee(...15 parameters...)Use
var employee = new EmployeeBuilder()
.WithName("John")
.WithSalary(50000)
.Build();Cleaner.
More readable.
7. Observer Pattern
One object notifies many others.
Example
When an order is placed
- Send Email
- Update Inventory
- Create Invoice
- Push Notification
Each subscriber reacts independently.
ASP.NET Core events often use this pattern.
8. Decorator Pattern
Add functionality without modifying existing code.
Original
public interface IMessageService
{
void Send();
}Decorator
public class LoggingDecorator : IMessageService
{
private readonly IMessageService _service;public LoggingDecorator(IMessageService service)
{
_service = service;
} public void Send()
{
Console.WriteLine("Logging...");
_service.Send();
}
}
Useful for
- Logging
- Caching
- Authorization
9. Mediator Pattern
Instead of services talking directly…
Everything communicates through a mediator.
public record CreateOrderCommand(string Product);Handler
public class CreateOrderHandler
{
public void Handle(CreateOrderCommand command)
{
Console.WriteLine("Order Created");
}
}Popular library
- MediatR
Very common in Clean Architecture.
10. CQRS (Command Query Responsibility Segregation)
Separate reads from writes.
Instead of
ProductServiceSplit into
CreateProductCommandGetProductQueryCommands
✔ Modify data
Queries
✔ Read data
Benefits
- Easier scaling
- Cleaner architecture
- Better separation of concerns
Which Patterns Does ASP.NET Core Already Use?
Final Thoughts
Design patterns aren’t about making your code more complicated — they’re about making it easier to understand, extend, and maintain.
As a .NET developer, you’ll encounter many of these patterns in real-world applications:
- Repository for data access
- Dependency Injection for loose coupling
- Strategy for flexible business logic
- Builder for complex object creation
- Decorator for logging and caching
- Mediator and CQRS in Clean Architecture
You don’t need to memorize every pattern. Focus on understanding when to use them and why they solve a particular problem. That’s what separates developers who can write code from those who can design software.
💬 Which design pattern do you use the most in your projects?
Let me know in the comments — I’d love to hear your experience!
#dotnet #csharp #aspnetcore #designpatterns #softwaredevelopment #cleanarchitecture #backenddevelopment #softwareengineering








