Introduction
Building modern software applications requires more than writing code that works. Enterprise applications must be maintainable, testable, scalable, and flexible enough to support future changes.
Design patterns provide proven solutions for common software engineering problems. They are not ready-made libraries or frameworks, but reusable approaches that help developers structure applications in a cleaner and more predictable way.
In the .NET ecosystem, design patterns are widely used inside ASP.NET Core, Entity Framework Core, dependency injection systems, middleware pipelines, and microservice architectures.

Why Design Patterns Matter in .NET Applications
Without proper architecture, applications often become difficult to modify as they grow. Business logic becomes tightly coupled, testing becomes complicated, and small changes introduce unexpected problems.
- ●
Reduce code duplication
- ●
Improve maintainability
- ●
Increase testability
- ●
Reduce dependencies
- ●
Improve scalability
- ●
Create predictable architectures
Design patterns help teams communicate using a shared vocabulary. Instead of explaining an entire implementation, developers can discuss concepts such as Factory, Strategy, Repository, or Observer patterns.
Understanding SOLID Principles
SOLID principles are five fundamental object-oriented design principles that help developers create flexible and maintainable software systems.
| Principle | Meaning |
|---|---|
| Single Responsibility | A class should have one reason to change |
| Open/Closed | Software should be open for extension but closed for modification |
| Liskov Substitution | Derived types should replace base types safely |
| Interface Segregation | Prefer smaller focused interfaces |
| Dependency Inversion | Depend on abstractions instead of implementations |
Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one responsibility and only one reason to change.
A common mistake in .NET applications is creating large service classes that handle database access, business rules, notifications, and logging at the same time.
public class OrderService
{
public void CreateOrder()
{
// Validate order
// Save to database
// Send email
// Generate report
}
}
A better approach is separating responsibilities into focused services.
public class OrderService
{
private readonly IOrderRepository repository;
private readonly IEmailService emailService;
public OrderService(
IOrderRepository repository,
IEmailService emailService)
{
this.repository = repository;
this.emailService = emailService;
}
}
Dependency Inversion Principle in ASP.NET Core
Dependency Inversion Principle encourages applications to depend on abstractions instead of concrete implementations.
ASP.NET Core dependency injection is built around this principle and allows developers to replace implementations easily.
public interface IPaymentService
{
Task ProcessAsync();
}
public class StripePaymentService
: IPaymentService
{
public Task ProcessAsync()
{
return Task.CompletedTask;
}
}
builder.Services
.AddScoped<IPaymentService,
StripePaymentService>();
Creational Design Patterns
Creational patterns focus on object creation. They provide flexible ways to create objects while reducing dependency between application components and concrete implementations.
- ●
Singleton Pattern
- ●
Factory Pattern
- ●
Abstract Factory Pattern
- ●
Builder Pattern
- ●
Prototype Pattern
Singleton Pattern in .NET
The Singleton pattern ensures that only one instance of a class exists during application lifetime.
ASP.NET Core uses this concept through Singleton dependency injection services.
public class ApplicationSettings
{
public string Environment { get; set; }
}
builder.Services
.AddSingleton<ApplicationSettings>();
- ●
Configuration services
- ●
Caching providers
- ●
Logging services
- ●
Application-wide state
Factory Pattern
The Factory pattern moves object creation logic into a dedicated component. This prevents application code from depending directly on specific implementations.
public interface IPaymentProcessor
{
void Process();
}
public class PaymentFactory
{
public static IPaymentProcessor Create(
string type)
{
return type switch
{
"card" => new CardPayment(),
"paypal" => new PaypalPayment(),
_ => throw new Exception(
"Unsupported payment")
};
}
}
Abstract Factory Pattern
The Abstract Factory pattern provides an interface for creating related objects without specifying their concrete implementations. It is useful when applications need to support multiple product families.
In .NET applications, this pattern is commonly used when supporting multiple databases, cloud providers, payment systems, or external integrations.
public interface ICloudStorageFactory
{
IFileStorage CreateStorage();
}
public class AzureStorageFactory
: ICloudStorageFactory
{
public IFileStorage CreateStorage()
{
return new AzureBlobStorage();
}
}
public class AwsStorageFactory
: ICloudStorageFactory
{
public IFileStorage CreateStorage()
{
return new S3Storage();
}
}
Builder Pattern
The Builder pattern is used when creating complex objects that require multiple configuration steps.
Instead of having constructors with many parameters, builders provide a cleaner and more readable way to create objects.
public class ReportBuilder
{
private readonly Report report =
new();
public ReportBuilder WithTitle(
string title)
{
report.Title = title;
return this;
}
public Report Build()
{
return report;
}
}
var report =
new ReportBuilder()
.WithTitle("Monthly Sales")
.Build();
Structural Design Patterns
Structural patterns focus on how objects and classes are combined to create larger application structures while keeping components flexible.
- ●
Adapter Pattern
- ●
Decorator Pattern
- ●
Facade Pattern
- ●
Proxy Pattern
- ●
Composite Pattern
Adapter Pattern
The Adapter pattern allows incompatible systems to work together by converting one interface into another expected by the application.
This pattern is frequently used when integrating third-party APIs, legacy systems, and external services.
public interface IPaymentService
{
Task PayAsync(decimal amount);
}
public class StripeAdapter
: IPaymentService
{
private readonly StripeClient client;
public async Task PayAsync(decimal amount)
{
await client.Charge(amount);
}
}
Decorator Pattern
The Decorator pattern adds new behavior to objects without changing their original implementation.
ASP.NET Core middleware, caching layers, logging, and authorization systems use similar concepts.
public class LoggingOrderService
: IOrderService
{
private readonly IOrderService service;
public async Task CreateAsync()
{
Console.WriteLine(
"Starting order");
await service.CreateAsync();
}
}
- ●
Logging
- ●
Caching
- ●
Authorization
- ●
Validation
- ●
Monitoring
Facade Pattern
The Facade pattern provides a simplified interface over a complex subsystem.
It is commonly used in application service layers where multiple internal services are combined into a single business operation.
public class OrderFacade
{
private readonly PaymentService payment;
private readonly InventoryService inventory;
public async Task CreateOrder()
{
await inventory.Reserve();
await payment.Process();
}
}
Proxy Pattern
The Proxy pattern creates an object that controls access to another object.
- ●
Lazy loading
- ●
Caching proxies
- ●
Security checks
- ●
Remote service calls
Entity Framework Core uses proxy-based techniques for features such as lazy loading.
Behavioral Design Patterns
Behavioral patterns focus on communication between objects and define how responsibilities are distributed across components.
- ●
Strategy Pattern
- ●
Observer Pattern
- ●
Command Pattern
- ●
Mediator Pattern
- ●
Chain of Responsibility
Strategy Pattern
The Strategy pattern allows selecting different algorithms or behaviors at runtime without changing the client code.
It is widely used in business applications for pricing rules, payment methods, shipping calculations, and validation logic.
public interface IDiscountStrategy
{
decimal Calculate(decimal price);
}
public class VipDiscount
: IDiscountStrategy
{
public decimal Calculate(decimal price)
{
return price * 0.8m;
}
}
Observer Pattern
The Observer pattern allows objects to subscribe and receive notifications when an event occurs.
In .NET, events, domain events, message queues, and event-driven microservices follow this concept.
public class OrderCreatedEvent
{
public int OrderId { get; set; }
}
Command Pattern
The Command pattern represents an action as an object. It separates the request from the component that performs the operation.
CQRS architectures and MediatR libraries commonly use this pattern.
public record CreateOrderCommand(
int CustomerId);
Mediator Pattern
The Mediator pattern reduces direct dependencies between objects by introducing a central communication component.
MediatR is a popular .NET library implementing mediator-style communication.
await mediator.Send(
new CreateOrderCommand(10));
Repository Pattern in .NET
The Repository pattern provides an abstraction layer between the application business logic and the data access layer. It allows developers to work with data without directly depending on database implementation details.
In .NET applications, repositories are commonly used together with Entity Framework Core to separate database operations from business rules.
public interface IRepository<T>
{
Task<T?> GetAsync(int id);
Task AddAsync(T entity);
Task DeleteAsync(T entity);
}
public class UserRepository
: IRepository<User>
{
private readonly AppDbContext context;
public async Task<User?> GetAsync(int id)
{
return await context.Users
.FirstOrDefaultAsync(
x => x.Id == id);
}
}
- ●
Separates data access logic
- ●
Improves unit testing
- ●
Centralizes database operations
- ●
Reduces duplicated queries
Unit of Work Pattern
The Unit of Work pattern manages multiple database operations as a single transaction. It ensures that related changes are committed together or rolled back together.
Entity Framework Core already provides Unit of Work behavior through DbContext because SaveChanges manages all tracked changes.
public interface IUnitOfWork
{
IUserRepository Users { get; }
IOrderRepository Orders { get; }
Task SaveChangesAsync();
}
await unitOfWork.Users.AddAsync(user);
await unitOfWork.Orders.AddAsync(order);
await unitOfWork.SaveChangesAsync();
Specification Pattern with Entity Framework Core
The Specification pattern allows developers to create reusable business rules and query definitions.
It is especially useful in large EF Core applications where filtering and querying logic becomes complex.
public class ActiveUsersSpecification
{
public Expression<Func<User,bool>> Criteria =>
x => x.IsActive;
}
- ●
Reusable queries
- ●
Cleaner services
- ●
Better separation of concerns
- ●
Improved maintainability
Dependency Injection Pattern in ASP.NET Core
Dependency Injection is one of the most important patterns in the .NET ecosystem. ASP.NET Core includes a built-in dependency injection container.
builder.Services
.AddScoped<IEmailService,
EmailService>();
builder.Services
.AddSingleton<ICacheService,
CacheService>();
| Lifetime | Usage |
|---|---|
| Transient | New instance every request |
| Scoped | One instance per HTTP request |
| Singleton | One instance for application lifetime |
Design Patterns in Microservices Architecture
Modern distributed systems use many design patterns to solve communication, reliability, and consistency challenges.
- ●
API Gateway Pattern
- ●
CQRS Pattern
- ●
Saga Pattern
- ●
Outbox Pattern
- ●
Event Sourcing
- ●
Retry Pattern
- ●
Circuit Breaker Pattern

CQRS Pattern
CQRS (Command Query Responsibility Segregation) separates read operations from write operations.
This allows applications to optimize queries independently from business commands.
public record CreateProductCommand(
string Name);
- ●
Better scalability
- ●
Independent read models
- ●
Cleaner business logic
- ●
Works well with event-driven systems
Saga Pattern
The Saga pattern manages distributed transactions across multiple microservices without using traditional database transactions.
Instead of locking resources, each service performs a local transaction and publishes an event.
Order Service
|
v
Payment Service
|
v
Inventory Service
|
v
Shipping Service
- ●
Handles failures
- ●
Supports distributed systems
- ●
Avoids long transactions
Outbox Pattern
The Outbox pattern solves the problem of reliably publishing events after a database transaction.
Instead of directly sending messages to Kafka or RabbitMQ, events are stored in an outbox table and published asynchronously.
public class OutboxMessage
{
public Guid Id { get; set; }
public string EventType { get; set; }
public string Payload { get; set; }
}
Common Design Pattern Mistakes
- ●
Using patterns everywhere without a real problem
- ●
Creating unnecessary abstractions
- ●
Overengineering simple applications
- ●
Adding interfaces for every class
- ●
Ignoring application requirements
- ●
Following patterns blindly
"Good architecture is about solving problems, not collecting patterns."
Design Patterns Comparison
| Pattern | Category | Common .NET Usage |
|---|---|---|
| Singleton | Creational | Configuration, caching |
| Factory | Creational | Creating services |
| Strategy | Behavioral | Business rules |
| Decorator | Structural | Logging and caching |
| Mediator | Behavioral | CQRS and MediatR |
| Repository | Data | Database abstraction |
| Unit of Work | Data | Transactions |
Frequently Asked Questions
Which design patterns are most commonly used in .NET?
Does every .NET project need design patterns?
Is Repository Pattern required with Entity Framework Core?
Are design patterns important for microservices?
Are design patterns the same as SOLID principles?
Conclusion
Design patterns help .NET developers build applications that are easier to maintain, test, and scale. By combining SOLID principles with proven patterns such as Dependency Injection, Strategy, Repository, CQRS, and Saga, developers can create reliable enterprise systems without unnecessary complexity.
