π‘ Understanding Dependency Injection (DI) in .NET
π§ Introduction
In modern software development, Dependency Injection (DI) has become one of the most essential design patterns β especially in .NET applications. It promotes clean, maintainable, and testable code by managing how dependencies are created and injected into classes.
If youβve ever seen code that creates too many objects manually using new, youβve likely encountered tight coupling. Dependency Injection helps solve exactly that problem.
βοΈ What is Dependency Injection?
Dependency Injection (DI) is a design pattern used to implement Inversion of Control (IoC) β a principle where objects do not create their own dependencies but receive them from an external source.
In simpler terms:
Instead of a class creating the objects it needs, those objects are βinjectedβ into it.
This helps make your code more modular, flexible, and easier to test.
π Example Without Dependency Injection
Letβs look at a tightly coupled example:
Problem:
NotificationManager directly depends on EmailService.
If you later change EmailService to SmsService, you must modify NotificationManager.
This creates tight coupling and makes testing harder.
β Example With Dependency Injection
Hereβs the same example using Constructor Injection:
Now you can easily switch to another service:
Or swap it with an SMS implementation:
You can pass SmsService to the constructor without changing any code in NotificationManager.
π§© Types of Dependency Injection
There are three main types of dependency injection in .NET:
Type Description Example
Among these, constructor injection is the most widely used in ASP.NET Core.
π§± Dependency Injection in ASP.NET Core
ASP.NET Core has built-in support for Dependency Injection out of the box.
You can register dependencies in the Startup.cs (or Program.cs in .NET 6+) file.
Example:
Registering in Program.cs
π§ DI Service Lifetime in .NET Core
When registering dependencies, you can specify their lifetime:
Example:
π Benefits of Dependency Injection
β Loose Coupling:
Classes depend on abstractions (interfaces), not concrete implementations.
β Improved Testability:
Easily replace dependencies with mock objects during unit testing.
β Reusability and Flexibility:
Swap services without modifying dependent code.
β Better Maintainability:
Encourages cleaner architecture and easier refactoring.
β οΈ Common Mistakes to Avoid
β Registering services with incorrect lifetime (e.g., using Singleton for DbContext).
β Overusing DI β not every dependency needs injection.
β Injecting too many dependencies in a single class (consider refactoring).
π Conclusion
Dependency Injection is more than just a design pattern β itβs a core principle of modern .NET development.
By decoupling components, it promotes clean architecture, scalability, and testability.
In ASP.NET Core, you get DI built-in by default, so make the most of it to write elegant, maintainable, and professional-grade code.
β¨ Quick Summary
DI implements Inversion of Control (IoC).
ASP.NET Core provides built-in DI container.
Use the right service lifetime.
Promotes clean, testable, and scalable code.
public class EmailService
{
public void SendEmail() => Console.WriteLine("Email Sent Successfully!");
}
public class NotificationManager
{
private EmailService _emailService = new EmailService();
public void Notify()
{
_emailService.SendEmail();
}
}
public interface IMessageService
{
void Send();
}
public class EmailService : IMessageService
{
public void Send() => Console.WriteLine("Email sent successfully!");
}
public class NotificationManager
{
private readonly IMessageService _messageService;
// Dependency is injected via constructor
public NotificationManager(IMessageService messageService)
{
_messageService = messageService;
}
public void Notify()
{
_messageService.Send();
}
}
IMessageService messageService = new EmailService();
NotificationManager manager = new NotificationManager(messageService);
manager.Notify();
public class SmsService : IMessageService
{
public void Send() => Console.WriteLine("SMS sent successfully!");
}


public interface IMessageService
{
void Send();
}
public class EmailService : IMessageService
{
public void Send() => Console.WriteLine("Email sent successfully!");
}
public class NotificationManager
{
private readonly IMessageService _messageService;
// Dependency is injected via constructor
public NotificationManager(IMessageService messageService)
{
_messageService = messageService;
}
public void Notify()
{
_messageService.Send();
}
}
var builder = WebApplication.CreateBuilder(args);
// Register service with DI container
builder.Services.AddScoped<IMessageService, EmailService>();
var app = builder.Build();
app.MapGet("/", (NotificationController controller) =>
{
controller.SendNotification();
return "Notification Sent!";
});
app.Run();


builder.Services.AddTransient<IEmailService, EmailService>(); // New each time
builder.Services.AddScoped<IEmailService, EmailService>(); // Per request
builder.Services.AddSingleton<IEmailService, EmailService>(); // One for all