.NET Core  

Understanding Dependency Injection (DI) In ASP.NET Core — A Beginner-Friendly Guide

Dependency Injection (DI) is one of the most important concepts in ASP.NET Core.
Almost every project—small or enterprise—uses DI because it makes your code cleaner, more testable, and easier to maintain.

In this article, we will explore DI in simple language, with real examples that new developers can understand.

What Is Dependency Injection?

To understand DI, let’s look at what a “dependency” is.

A dependency is simply another class that your class needs to work.

Example


A controller needs a service.

public class OrderController
{
    private readonly OrderService _orderService;
    public OrderController()
    {
        _orderService = new OrderService();   // ← dependency created manually
    }
}

This approach has a big problem: You are hard-coding the dependency using new. Problems Without DI

  • Your classes become tightly coupled

  • Hard to replace or mock services

  • Unit testing becomes painful

  • Changing one class forces you to modify others

This is why ASP.NET Core uses Dependency Injection by default.

What DI Does?

With DI, you don’t create the dependency yourself.

You ask ASP.NET Core to provide it.

ASP.NET Core automatically creates:

  • Services

  • Repositories

  • Loggers

  • Database contexts

  • Configurations

And injects them where they are needed.

Basic Example of DI

Step 1: Create an Interface

public interface IOrderService
{
    string GetOrderStatus(int id);
}

Step 2: Create the Service

public class OrderService : IOrderService
{
    public string GetOrderStatus(int id)
    {
        return $"Order {id} is completed.";
    }
}

Step 3: Register Service in Program.cs

builder.Services.AddScoped<IOrderService, OrderService>();

Step 4: Inject Into Controller

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService;
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        return Ok(_orderService.GetOrderStatus(id));
    }
}

✔️ No new
✔️ Clean
✔️ Testable
✔️ Follows best practices

DI Lifetimes (Very Important)

ASP.NET Core has 3 main lifetimes:

1. Transient – (AddTransient)

Created every time it is requested.

builder.Services.AddTransient<IEmailService, EmailService>();

Used for:

  • Quick, lightweight services

  • Stateless operations

2. Scoped – (AddScoped)

Created once per HTTP request.

builder.Services.AddScoped<IOrderService, OrderService>();

Used for:

  • Services working with database

  • Request-level logic

3. Singleton – (AddSingleton)

Created only once and reused for whole application lifetime.

builder.Services.AddSingleton<ILogService, LogService>();

Used for:

  • Caching

  • Logging

  • Configurations

Common Mistake: Using 'new' inside Controllers

Beginners often do this:

var service = new OrderService(); 

This breaks DI completely.

Instead, always inject dependencies through the constructor:

public OrderController(IOrderService service)
{
    _orderService = service;
}

Best Practices for DI

Here are some professional-level tips:

  • Use interfaces for all services

  • Avoid injecting more than 3–4 services into one class

  • Prefer Scoped for database-related classes

  • Don’t put business logic inside controllers

  • Keep your services small and focused

Conclusion

Dependency Injection is not just a feature of ASP.NET Core — it’s the foundation of how modern .NET applications are built.

If you want to grow as a professional C# developer, mastering DI will make your code:

  • Cleaner

  • Easier to test

  • Easier to maintain

  • More structured

  • More scalable

Start with small services, inject them into controllers, and with practice, DI will become second nature.