Sitemap
Sachin Ghadi

Sachin Ghadi

10 C# Design Patterns Every ASP.NET Core Developer Should Know (With Simple Examples)

4 min readJul 6, 2026

--

Press enter or click to view image in full size

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.

Press enter or click to view image in full size

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

ProductService

Split into

CreateProductCommand
GetProductQuery

Commands

✔ Modify data

Queries

✔ Read data

Benefits

  • Easier scaling
  • Cleaner architecture
  • Better separation of concerns

Which Patterns Does ASP.NET Core Already Use?

Press enter or click to view image in full size

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

--

--