Namraj Pudasaini
May 1, 2026
Dependency injection is a software design pattern where an object is given its dependencies(other objects it needs) from an external source, rather than creating itself.
ASP.NET Core's built-in container supports constructor injection. Setter and interface injection are patterns you will meet in other frameworks or in hand-rolled code, but the framework will not populate a property or call an injection interface for you.
Start with an interface and an implementation:
public interface IGreetingService
{
string Greet(string name);
}
public class GreetingService : IGreetingService
{
public string Greet(string name) => $"Hello, {name}!";
}
The consumer asks for the interface in its constructor and stores it in a readonly field. It never calls new GreetingService().
public class HomeController : Controller
{
private readonly IGreetingService _greetings;
public HomeController(IGreetingService greetings)
{
_greetings = greetings;
}
public IActionResult Index() => Content(_greetings.Greet("world"));
}
Because the controller depends on the interface and not the concrete class, you can hand it a fake implementation in a unit test without touching the real one.
The container has to be told which concrete type to supply. Register it in Program.cs, before builder.Build() is called:
builder.Services.AddScoped<IGreetingService, GreetingService>();
Skip that line and the request fails when the class is constructed, with Unable to resolve service for type 'IGreetingService' while attempting to activate 'HomeController'. The message names both the missing service and the class that asked for it, which usually points straight at the registration you forgot.
AddScoped is one of three registration methods, and the choice controls how long an instance lives.
AddSingleton: one instance for the lifetime of the application. Every request shares it, so the implementation has to be thread safe.AddScoped: one instance per HTTP request. Everything resolved during that request gets the same object, and it is disposed when the request ends. This is the usual choice for anything holding per-request state, such as an Entity Framework DbContext.AddTransient: a new instance every time the service is resolved, even twice within the same request. Best for small, stateless services.Lifetimes have to line up. A singleton is built once and holds on to whatever it was given, so injecting a scoped service into a singleton captures that instance and keeps it alive long after its request ended. In Development the host validates the container at startup and throws instead:
Cannot consume scoped service 'IGreetingService' from singleton 'IReportCache'.
The rule is that a service can only depend on services whose lifetime is at least as long as its own. A singleton can consume singletons. A scoped service can consume scoped and singleton services. A transient service can consume anything. When a singleton genuinely needs scoped work done, inject IServiceScopeFactory, create a scope for the duration of that work, and resolve the scoped service from it.