🤔 What is Dependency Injection?
Dependency Injection means providing an object's dependencies rather than having it construct them itself.
Instead of:
var service = new EmailService();
var controller = new NotificationController(service);
We let a container (like the built-in DI in .NET Core) handle this:
services.AddScoped<IEmailService, EmailService>();
🧱 Types of Dependency Injection
.NET Core supports three main types:
- Constructor Injection (most common)
- Method Injection
- Property Injection
Let's focus on Constructor Injection since it’s most widely used.
🛠 Built-in Dependency Injection in .NET Core
When you create an ASP.NET Core application, the DI container is automatically set up.
The default container is lightweight but powerful. You register services in Program.cs
(or Startup.cs
in older versions) and the framework injects them where needed.
✍️ Step-by-Step Example
Let’s build a small service and inject it.
1. Define an Interface
public interface IMessageService
{
string SendMessage(string message);
}
2. Create the Implementation
public class EmailService : IMessageService
{
public string SendMessage(string message)
{
return $"Email sent with message: {message}";
}
}
3. Register the Service in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Register service
builder.Services.AddScoped<IMessageService, EmailService>();
var app = builder.Build();
// Configure the HTTP request pipeline, etc.
app.Run();
AddScoped
means a new instance will be created per request. Other options are AddSingleton
and AddTransient
.
4. Inject the Service in a Controller
[ApiController]
[Route("[controller]")]
public class NotificationController : ControllerBase
{
private readonly IMessageService _messageService;
public NotificationController(IMessageService messageService)
{
_messageService = messageService;
}
[HttpGet]
public IActionResult Send()
{
var result = _messageService.SendMessage("Hello from DI!");
return Ok(result);
}
}
🔁 Service Lifetimes Explained
Method |
Lifetime |
Description |
AddSingleton |
One instance for the entire app |
Use for stateless services |
AddScoped |
One per request |
Ideal for web apps |
AddTransient |
New every time requested |
Lightweight, stateless |
🧪 Unit Testing with Dependency Injection
Thanks to DI, testing is simple. You can mock dependencies easily.
[Fact]
public void SendMessage_ReturnsExpectedString()
{
// Arrange
var mockService = new Mock<IMessageService>();
mockService.Setup(s => s.SendMessage("Hi")).Returns("Mocked message");
var controller = new NotificationController(mockService.Object);
// Act
var result = controller.Send() as OkObjectResult;
// Assert
Assert.Equal("Mocked message", result.Value);
}
🧩 Injecting Configuration and Logging
You can also inject:
private readonly IConfiguration _config;
private readonly ILogger<SomeClass> _logger;
public SomeClass(IConfiguration config, ILogger<SomeClass> logger)
{
_config = config;
_logger = logger;
}
⚠️ Common Mistakes to Avoid
- Registering services after
builder.Build()
– won’t work.
- Mixing lifetimes inappropriately (e.g., injecting scoped into singleton).
- Not using interfaces makes testing and swapping implementations harder.
✅ Best Practices
- Always use interfaces for services.
- Prefer constructor injection over method/property.
- Understand service lifetimes before choosing one.
- Use built-in DI unless your app truly needs a more advanced container.
🏁 Conclusion
Dependency Injection is a first-class citizen in .NET Core. It promotes clean architecture, separation of concerns, and makes unit testing straightforward.
By understanding service lifetimes and the registration process, you can build maintainable, testable, and scalable applications with ease.