Dependency Injection in ASP.NET Core Why it exists? and what problem it actually solves?

Published: (February 6, 2026 at 10:24 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

If you’ve worked with ASP.NET Core, you’ve probably used Dependency Injection (DI) without thinking much about it. You register services in Program.cs, inject them into controllers, and move on. But why does DI exist at all? What problem is it solving? Let’s answer that properly — with code.

1. The problem: tight coupling

A very common beginner pattern looks like this:

public class PostsController : ControllerBase
{
    private readonly PostsService _service = new PostsService();

    [HttpGet]
    public IEnumerable Get()
    {
        return _service.GetPosts();
    }
}

public class PostsService
{
    public IEnumerable GetPosts()
    {
        return new[] { "Post A", "Post B" };
    }
}

At first glance, this looks fine. The code works. The API runs.

Why this becomes a problem

❌ Change is expensive

If tomorrow PostsService needs database access, you want a new implementation, or you need a mock for testing, you must edit the controller code. That means:

  • touching multiple files
  • risking bugs
  • violating the open‑for‑extension, closed‑for‑modification principle

❌ Testing is painful

Because the controller creates the dependency itself, you cannot easily replace it. You’re forced to:

  • hit a real database
  • call real APIs
  • or write hacks to bypass logic

This is classic tight coupling.

2. The core idea of Dependency Injection (one line)

Don’t create dependencies inside a class. Ask for them.

Instead of saying “I will decide which service to use,” you say “Give me something that follows this contract.”

3. The fix: abstraction + constructor injection

Define a contract (interface)

public interface IPostsService
{
    IEnumerable GetPosts();
}

Implement the contract

public class PostsService : IPostsService
{
    public IEnumerable GetPosts()
    {
        return new[] { "Post A", "Post B" };
    }
}

Inject it into the controller

[ApiController]
[Route("api/posts")]
public class PostsController : ControllerBase
{
    private readonly IPostsService _service;

    public PostsController(IPostsService service)
    {
        _service = service;
    }

    [HttpGet]
    public IEnumerable Get()
    {
        return _service.GetPosts();
    }
}

What changed?

  • The controller no longer knows which implementation it uses; it depends only on an abstraction.
  • Decoupling starts here.

4. Let ASP.NET Core resolve the dependency

Register the service in the DI container:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// One‑line decision
builder.Services.AddScoped();

var app = builder.Build();
app.MapControllers();
app.Run();

If you later add another implementation:

public class PostsServiceNew : IPostsService
{
    public IEnumerable GetPosts()
    {
        return new[] { "Post X", "Post Y" };
    }
}

You can switch it with:

builder.Services.AddScoped();

No controller changes, no ripple effects.

5. What the DI container actually does

Behind the scenes ASP.NET Core:

  1. Creates the controller.
  2. Sees it needs IPostsService.
  3. Looks up the registration in the container.
  4. Instantiates the correct implementation.
  5. Injects it automatically.

You didn’t lose control—you moved the responsibility to the framework. This is Inversion of Control (IoC).

6. How this relates to SOLID (briefly)

  • Abstraction – program against interfaces, not implementations.
  • Decoupling – controllers don’t know or care which service is used.
  • Dependency Inversion Principle (DIP) – high‑level modules depend on abstractions, not concrete classes.
  • Inversion of Control (IoC) – the framework creates and wires objects for you.

DI is the practical application of these principles.

7. Why DI makes testing easy

Because dependencies are injected, you can pass a fake implementation:

public class FakePostsService : IPostsService
{
    public IEnumerable GetPosts()
    {
        return new[] { "Fake Post" };
    }
}
var controller = new PostsController(new FakePostsService());
var result = controller.Get();

No database, no infrastructure—just pure logic. This is where DI really pays off.

8. When Dependency Injection is useful ✅

  • You need replaceable implementations.
  • You care about testability.
  • You build medium‑to‑large applications.
  • You expect requirements to change.

9. When DI is overkill ❌

  • Very small scripts.
  • One‑off console apps.
  • Code that will never grow or be tested.

DI is a tool—not a rule.

Back to Blog

Related posts

Read more »