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:

With Dependency Injection

public class HomeController : ControllerBase
{
    private readonly ILoggerService _logger;

    public HomeController(ILoggerService logger)
    {
        _logger = logger;
    }
}

Now:

Real-Time Scenario: Logging in an ASP.NET Core Application

In almost every project I’ve worked on, logging is mandatory:

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?

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:

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)

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:

Example in Action

When a request hits:

GET /api/home

What happens internally:

  1. ASP.NET Core creates HomeController

  2. DI container injects ConsoleLoggerService

  3. _logger.Log() executes

  4. 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:

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:

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.