Dependency Injection in C#: Writing Clean, Modular, and Testable Code

I'm a fullstack developer and my stack is includes .net, angular, reactjs, mondodb and mssql
I currently work in a little tourism company, I'm not only a developer but I manage a team and customers.
I love learning new things and I like the continuous comparison with other people on ideas.
Dependency Injection (DI) is a technique that makes this possible: it separates the creation of dependencies from the business logic, improving modularity, maintainability, and testability.
What Is Dependency Injection?
It's a pattern where an object’s dependencies are provided (injected) from the outside, rather than having the object create or locate them itself.
Before (without DI):
public class OrderService
{
private readonly EmailService _emailService = new EmailService();
public void PlaceOrder(Order order)
{
// order logic...
_emailService.SendConfirmation(order);
}
}
Here, OrderService depends directly on EmailService and creates it internally. This makes it hard to replace or test.
With Dependency Injection
public class OrderService
{
private readonly IEmailService _emailService;
public OrderService(IEmailService emailService)
{
_emailService = emailService;
}
public void PlaceOrder(Order order)
{
// order logic...
_emailService.SendConfirmation(order);
}
}
The IEmailService interface is passed in from the outside (for example, by a DI container).
Benefits of DI
✅ Decoupling: OrderService doesn't know the concrete implementation of IEmailService.
✅ Testability: You can pass in a mock or fake IEmailService in tests.
✅ Flexibility: You can switch implementations without changing OrderService.
✅ Simplified maintenance: Dependencies are clear and managed centrally.
Types of Dependency Injection
Constructor Injection (most common): Dependencies are passed through the constructor.
Property Injection: Dependencies are set via public properties.
Method Injection: Dependencies are passed as method parameters.
DI with ASP.NET Core’s Built-In Container
ASP.NET Core includes a simple built-in DI container.
Service registration in Startup.cs or Program.cs:
services.AddTransient<IEmailService, EmailService>();
services.AddScoped<IOrderRepository, OrderRepository>();
Usage in controllers or classes:
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
}
Practical Tips
Always inject interfaces or abstract classes, not concrete implementations.
Avoid injecting too many services into a single class — it may indicate an SRP violation.
Prefer constructor injection for clarity and immutability.
Set the appropriate lifetime:
Transient,Scoped, orSingleton.
Testability with DI
In unit tests, you can easily replace dependencies with mocks:
var mockEmail = new Mock<IEmailService>();
var orderService = new OrderService(mockEmail.Object);
Conclusion
Dependency Injection is a powerful ally in writing clean, modular, and easily testable C# code.
If you’re not using it yet, start introducing it gradually — and you’ll see a significant improvement in code quality.
In the next article, we’ll explore how to write testable code and best practices for unit testing.






