🔧 Day 3: SOLID Principles in .NET Web Apps – Explained with Real Code

"Bad code works. But good code works for years."
In today's post, we're diving into the SOLID principles — 5 key design rules that help us build maintainable and scalable software.
These principles fit perfectly within Clean Architecture and act like the backbone of writing high-quality .NET code.
💡 What is SOLID?
SOLID is an acronym for:
Principle | Meaning |
---|---|
S | Single Responsibility Principle |
O | Open/Closed Principle |
L | Liskov Substitution Principle |
I | Interface Segregation Principle |
D | Dependency Inversion Principle |
Let’s break them down with simple real-world .NET
examples 👇
🧱 1. Single Responsibility Principle (SRP)
A class should have only one reason to change.
Bad Example:
public class InvoiceService
{
public void GenerateInvoice() { /* logic */ }
public void SaveToDatabase() { /* logic */ }
public void SendEmail() { /* logic */ }
}
Why bad?
One class is handling too many things – business logic, persistence, and notifications.
Refactored (Good Example):
public class InvoiceGenerator { public void Generate() { } }
public class InvoiceRepository { public void Save() { } }
public class EmailSender { public void Send() { } }
Now each class has one responsibility ✅
🔓 2. Open/Closed Principle (OCP)
Software should be open for extension but closed for modification.
Bad Example:
public class NotificationService
{
public void Notify(string type)
{
if (type == "Email") { /* send email */ }
else if (type == "SMS") { /* send sms */ }
}
}
Good Example (With Interfaces):
public interface INotifier { void Notify(); }
public class EmailNotifier : INotifier
{
public void Notify() { /* Email logic */ }
}
public class SMSNotifier : INotifier
{
public void Notify() { /* SMS logic */ }
}
public class NotificationService
{
private readonly INotifier _notifier;
public NotificationService(INotifier notifier) { _notifier = notifier; }
public void Send() => _notifier.Notify();
}
Now you can add more notification types without modifying the service 👌
🧬 3. Liskov Substitution Principle (LSP)
Derived classes must be substitutable for their base class.
Bad Example:
public class Bird { public virtual void Fly() { } }
public class Ostrich : Bird { public override void Fly() { throw new Exception("Can't fly"); } }
Better:
public interface IBird { }
public interface IFlyingBird : IBird
{
void Fly();
}
public class Sparrow : IFlyingBird
{
public void Fly() { }
}
public class Ostrich : IBird
{
// No Fly method needed
}
Don’t force a child class to implement behavior it doesn’t support 🚫
🪝 4. Interface Segregation Principle (ISP)
Keep interfaces small and focused.
Bad Example:
public interface IWorker
{
void Work();
void Eat();
}
Better:
public interface IWorkable { void Work(); }
public interface IFeedable { void Eat(); }
public class Robot : IWorkable { public void Work() { } }
public class Human : IWorkable, IFeedable
{
public void Work() { }
public void Eat() { }
}
Clients should not be forced to depend on interfaces they don’t use 💡
🧩 5. Dependency Inversion Principle (DIP)
Depend on abstractions, not concretions.
Bad Example:
public class OrderService
{
private readonly SqlOrderRepository _repository;
public OrderService() { _repository = new SqlOrderRepository(); }
}
Good Example:
public interface IOrderRepository { void Save(); }
public class SqlOrderRepository : IOrderRepository
{
public void Save() { }
}
public class OrderService
{
private readonly IOrderRepository _repository;
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
}
Now you can easily swap out the implementation (e.g., for testing or new DB) 🧪
🚀 Final Thoughts
By applying SOLID principles, you build:
- Flexible systems
- Easily testable code
- Maintainable architecture for the long run
They’re not just theory — they help your .NET apps survive years of changes with fewer bugs and better team collaboration.
✍️ End Note
Next up in , we’ll implement Deep Dive Into Layered Architecture , giving you a real project structure you can follow. Stay tuned!