Introduction
When I started working on real-world ASP.NET Core projects, one mistake I repeatedly made was creating objects directly inside controllers. At first, this felt simple and fast. But as the application grew, the code became tightly coupled, difficult to test, and painful to maintain.
That’s when I truly understood the importance of Dependency Injection (DI).
ASP.NET Core provides built-in support for DI, and once you start using it correctly, it completely changes how you structure applications. In this article, I’ll explain Dependency Injection from a practical developer’s perspective, using a simple logging example that I’ve personally used while debugging APIs and tracking requests.
What is Dependency Injection (In Simple Terms)
Dependency Injection is a design pattern where a class does not create the objects it depends on.
Instead, those objects are provided externally.
Without Dependency Injection
public class HomeController : ControllerBase
{
private ConsoleLoggerService _logger = new ConsoleLoggerService();
}
Problems I faced with this approach:
Controller is tightly coupled to one implementation
Hard to replace logger later
Unit testing becomes difficult
With Dependency Injection
public class HomeController : ControllerBase
{
private readonly ILoggerService _logger;
public HomeController(ILoggerService logger)
{
_logger = logger;
}
}
Now:
Controller depends on an interface
Implementation can change anytime
Code becomes clean and testable
Real-Time Scenario: Logging in an ASP.NET Core Application
In almost every project I’ve worked on, logging is mandatory:
API request tracking
Error debugging
Auditing user actions
Instead of hardcoding logging logic everywhere, DI allows us to centralize and inject it.
Step 1: Define a Logging Interface
I always start with an interface. This gives flexibility from day one.
public interface ILoggerService
{
void Log(string message);
}
Why interface first?
Allows multiple implementations
Makes unit testing easier
Follows clean architecture principles
Step 2: Implement the Service
For this demo, I’m using a simple console logger.
public class ConsoleLoggerService : ILoggerService
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
}
In real projects, I later replace this with:
File logging
Database logging
Serilog / NLog
Without touching controller code.
Step 3: Register the Service in Program.cs
This is where ASP.NET Core’s built-in DI container comes into play.
var builder = WebApplication.CreateBuilder(args);
// Register service
builder.Services.AddScoped<ILoggerService, ConsoleLoggerService>();
var app = builder.Build();
app.MapControllers();
app.Run();
Understanding Service Lifetimes (From Experience)
AddSingleton
One instance for entire application
Useful for configuration or caching
AddScoped
One instance per HTTP request
Best choice for most services (used here)
AddTransient
New instance every time
Useful for lightweight, stateless services
I generally start with Scoped unless there’s a clear reason otherwise.
Step 4: Inject the Service into a Controller
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
private readonly ILoggerService _logger;
public HomeController(ILoggerService logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
_logger.Log("HomeController GET method called");
return Ok("Hello from Dependency Injection!");
}
}
Key learning:
Controller does not know how logging works
It only knows that logging exists
DI container handles object creation automatically
Example in Action
When a request hits:
GET /api/home
What happens internally:
ASP.NET Core creates HomeController
DI container injects ConsoleLoggerService
_logger.Log() executes
Message appears in console
Later, I can replace ConsoleLoggerService with FileLoggerService without changing the controller at all.
Why Dependency Injection Matters (From Real Projects)
From my experience, DI gives:
Flexibility – Swap implementations easily
Testability – Mock services in unit tests
Maintainability – Clean, readable code
Scalability – Easier to grow applications
Once I adopted DI properly, debugging and refactoring became significantly easier.
Conclusion
Dependency Injection is not just a concept — it’s a core foundation of ASP.NET Core applications. By using DI correctly, you write code that is clean, modular, and future-proof.
The logging example shown here is simple, but the same pattern applies to:
Database access
Authentication
Email services
External APIs
Understanding and using DI effectively has improved the quality of my projects, and I strongly recommend mastering it early in your ASP.NET Core journey.