← Back to Articles
.NET C# Design Patterns Software Architecture SOLID Backend

.NET Design Patterns Guide: SOLID Principles, C# Examples, and Enterprise Application Architecture

Learn the most important .NET design patterns with practical C# examples. Understand SOLID principles, creational, structural, and behavioral patterns used to build scalable and maintainable enterprise applications.

July 2026
30 min read
Written by Engineering Team

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.

Common .NET design patterns overview
Popular design patterns used in modern .NET enterprise applications.

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.

BadExample.cs
csharp
              public class OrderService
{
    public void CreateOrder()
    {
        // Validate order

        // Save to database

        // Send email

        // Generate report
    }
}

            

A better approach is separating responsibilities into focused services.

BetterExample.cs
csharp
              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.

DependencyInjection.cs
csharp
              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.

SingletonService.cs
csharp
              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.

PaymentFactory.cs
csharp
              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.

AbstractFactory.cs
csharp
              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.

ReportBuilder.cs
csharp
              public class ReportBuilder
{
    private readonly Report report =
        new();


    public ReportBuilder WithTitle(
        string title)
    {
        report.Title = title;
        return this;
    }


    public Report Build()
    {
        return report;
    }
}

            
Usage.cs
csharp
              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.

PaymentAdapter.cs
csharp
              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.

LoggingDecorator.cs
csharp
              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.

OrderFacade.cs
csharp
              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.

DiscountStrategy.cs
csharp
              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.

DomainEvent.cs
csharp
              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.

CreateOrderCommand.cs
csharp
              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.

MediatorExample.cs
csharp
              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.

IRepository.cs
csharp
              public interface IRepository<T>
{
    Task<T?> GetAsync(int id);

    Task AddAsync(T entity);

    Task DeleteAsync(T entity);
}

            
UserRepository.cs
csharp
              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.

UnitOfWork.cs
csharp
              public interface IUnitOfWork
{
    IUserRepository Users { get; }

    IOrderRepository Orders { get; }

    Task SaveChangesAsync();
}

            
Usage.cs
csharp
              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.

ActiveUsersSpecification.cs
csharp
              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.

Program.cs
csharp
              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

Microservices design patterns architecture
Common patterns used in distributed .NET microservice systems.

CQRS Pattern

CQRS (Command Query Responsibility Segregation) separates read operations from write operations.

This allows applications to optimize queries independently from business commands.

CQRSCommand.cs
csharp
              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.

OrderWorkflow.txt
text
              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.

OutboxMessage.cs
csharp
              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?
Dependency Injection, Repository, Unit of Work, Strategy, Factory, Mediator, and Decorator patterns are among the most common in .NET applications.
Does every .NET project need design patterns?
No. Patterns should solve real problems. Adding unnecessary patterns can make applications harder to understand.
Is Repository Pattern required with Entity Framework Core?
No. EF Core already provides repository-like functionality. Additional repositories should be introduced only when they provide architectural value.
Are design patterns important for microservices?
Yes. Patterns such as Saga, CQRS, Outbox, and Circuit Breaker help solve distributed system challenges.
Are design patterns the same as SOLID principles?
No. SOLID principles are design guidelines, while patterns are reusable solutions built using those 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.

We use cookies to improve your experience.