DEV Community

Sahith kumar Singari
Sahith kumar Singari

Posted on

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

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<string> Get()
    {
        return _service.GetPosts();
    }
}

public class PostsService
{
    public IEnumerable<string> GetPosts()
    {
        return new[] { "Post A", "Post B" };
    }
}
Enter fullscreen mode Exit fullscreen mode

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

So what’s the issue?

2. Why this becomes a problem
❌ Change is expensive

If tomorrow:

  • PostsService needs database access
  • or you want PostsServiceNew
  • or you want a mock service 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 tight coupling.

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

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

That’s it.
That’s the whole idea.

Instead of saying:

“I will decide which service to use”

You say:

“Give me something that follows this contract”

4. The fix: abstraction + constructor injection

First, define a contract (interface):

public interface IPostsService
{
    IEnumerable<string> GetPosts();
}
Enter fullscreen mode Exit fullscreen mode

Then implement it:

public class PostsService : IPostsService
{
    public IEnumerable<string> GetPosts()
    {
        return new[] { "Post A", "Post B" };
    }
}
Enter fullscreen mode Exit fullscreen mode

Now 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<string> Get()
    {
        return _service.GetPosts();
    }
}
Enter fullscreen mode Exit fullscreen mode

What changed?

  • The controller no longer knows which implementation it uses
  • It only depends on an abstraction

This is where **decoupling **starts.

5. Let ASP.NET Core resolve the dependency

Now tell ASP.NET Core which implementation to use:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// One-line decision
builder.Services.AddScoped<IPostsService, PostsService>();

var app = builder.Build();
app.MapControllers();
app.Run();
Enter fullscreen mode Exit fullscreen mode

If later you add another implementation:

public class PostsServiceNew : IPostsService
{
    public IEnumerable<string> GetPosts()
    {
        return new[] { "Post X", "Post Y" };
    }
}
Enter fullscreen mode Exit fullscreen mode

You can switch it like this:

builder.Services.AddScoped<IPostsService, PostsServiceNew>();
Enter fullscreen mode Exit fullscreen mode

No controller changes.
No ripple effects.

6. What the DI container actually does

Behind the scenes, ASP.NET Core:

  • creates the controller
  • sees it needs IPostsService
  • looks in the container
  • creates the correct implementation
  • injects it automatically

You didn’t lose control — you moved the responsibility to the framework.

This is Inversion of Control (IoC).

7. How this relates to SOLID (briefly)
Abstraction

You 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 not a trend — it’s the practical application of these principles.

8. Why DI makes testing easy
Because dependencies are injected, you can pass a fake implementation:

public class FakePostsService : IPostsService
{
    public IEnumerable<string> GetPosts()
    {
        return new[] { "Fake Post" };
    }
}
Enter fullscreen mode Exit fullscreen mode
var controller = new PostsController(new FakePostsService());
var result = controller.Get();
Enter fullscreen mode Exit fullscreen mode

No database.
No infrastructure.
Just logic.

This is where DI really pays off.

9. When Dependency Injection is useful ✅

  • You want replaceable implementations
  • You care about testability
  • You build medium to large applications
  • You expect requirements to change

10. 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.

Top comments (0)