π― Dependency Injection in ASP.NET Core
The Magic Coffee Shop Analogy β
Imagine you run a coffee shop. Every morning, your barista needs:
- Coffee beans
- Milk
- A coffee machine
Now, there are two ways to get these supplies:
The OLD Way (No DI): The barista goes to the store, picks beans, finds milk, buys a machineβ¦ EVERY. SINGLE. DAY. π° Exhausting! What if the store changes location? Everything breaks!
The NEW Way (With DI): Someone delivers everything to the barista each morning. The barista just says βI need coffee beansβ and poof β they appear! π Easy! If the supplier changes, the barista doesnβt even notice.
Thatβs Dependency Injection! Your code says βI need this thingβ and ASP.NET Core delivers it automatically.
π What is ASP.NET Core DI?
Dependency Injection (DI) is a built-in superpower in ASP.NET Core.
Think of it like a magical delivery service that:
- Knows what your classes need
- Creates those things
- Delivers them automatically
Why Should You Care?
| Without DI π« | With DI π |
|---|---|
| Classes create their own dependencies | Dependencies are delivered |
| Hard to test | Easy to test |
| Tightly coupled code | Loosely coupled code |
| Change one thing, break everything | Change is painless |
π¦ The DI Container
ASP.NET Core has a container β think of it as a smart warehouse.
graph TD A["Your Code"] -->|Asks for| B["DI Container"] B -->|Delivers| C["Ready-to-use Service"] B -->|Stores| D["Service Recipes"]
The container:
- Stores recipes for creating services
- Builds services when asked
- Manages their lifetime (more on this soon!)
π Service Registration
Before the magic works, you must tell the container what services exist.
This happens in Program.cs:
// "Hey container, when someone
// asks for ICoffeeService,
// give them CoffeeService!"
builder.Services.AddTransient
<ICoffeeService, CoffeeService>();
The Three Key Methods
| Method | When to Use |
|---|---|
AddTransient |
New instance every time |
AddScoped |
One per request |
AddSingleton |
One for the whole app |
Real Example
// Program.cs
var builder = WebApplication
.CreateBuilder(args);
// Register services
builder.Services.AddTransient
<IEmailSender, SmtpEmailSender>();
builder.Services.AddScoped
<IShoppingCart, ShoppingCart>();
builder.Services.AddSingleton
<ILogger, FileLogger>();
var app = builder.Build();
β° Service Lifetimes
This is crucial! Each service lives for a different duration.
π Transient β The Disposable Cup
graph TD R1["Request 1"] --> T1["New Instance"] R1 --> T2["New Instance"] R2["Request 2"] --> T3["New Instance"]
Every time someone asks = brand new instance.
builder.Services.AddTransient
<INotification, EmailNotification>();
Use for: Lightweight, stateless services.
π Scoped β The Refillable Cup
graph TD R1["Request 1"] --> S1["Same Instance"] R1 --> S1 R2["Request 2"] --> S2["Different Instance"]
Same instance throughout ONE request. New instance for the next request.
builder.Services.AddScoped
<IShoppingCart, ShoppingCart>();
Use for: Database contexts, per-request data.
π Singleton β The Permanent Mug
graph TD R1["Request 1"] --> S["Same Instance"] R2["Request 2"] --> S R3["Request 100"] --> S
ONE instance for the entire application lifetime.
builder.Services.AddSingleton
<ICache, MemoryCache>();
Use for: Caching, configuration, logging.
β οΈ Lifetime Mismatch Warning!
NEVER inject a Scoped service into a Singleton!
// β WRONG - This will break!
public class MySingleton
{
// Scoped inside Singleton = BAD
private readonly IScopedService _scoped;
}
Why? The Singleton lives forever, but the Scoped service should die after each request. Chaos ensues!
π Service Resolution
Resolution = Getting a service from the container.
Constructor Injection (Most Common)
Your class declares what it needs in its constructor:
public class OrderController
{
private readonly IOrderService _orders;
private readonly IEmailSender _email;
// "I need these two things!"
public OrderController(
IOrderService orders,
IEmailSender email)
{
_orders = orders;
_email = email;
}
}
ASP.NET Core automatically provides them!
Method Injection
Sometimes you need a service in just one method:
public IActionResult SendReport(
[FromServices] IReportGenerator gen)
{
// gen is injected just for this method
return Ok(gen.CreateReport());
}
Manual Resolution (Rarely Needed)
// Inside a controller or middleware
var service = HttpContext
.RequestServices
.GetService<IMyService>();
// Or require it (throws if missing)
var required = HttpContext
.RequestServices
.GetRequiredService<IMyService>();
π Keyed Services (.NET 8+)
What if you have multiple implementations of the same interface?
The Coffee Shop Problem:
You have TWO coffee machines β one for espresso, one for drip.
Both are ICoffeeMachine. How do you choose?
Keyed Services to the rescue!
Registration with Keys
builder.Services.AddKeyedSingleton
<ICoffeeMachine, EspressoMachine>
("espresso");
builder.Services.AddKeyedSingleton
<ICoffeeMachine, DripMachine>
("drip");
Resolution with Keys
public class CoffeeController
{
private readonly ICoffeeMachine _espresso;
public CoffeeController(
[FromKeyedServices("espresso")]
ICoffeeMachine espresso)
{
_espresso = espresso;
}
}
Another Way to Resolve
var espresso = provider
.GetKeyedService<ICoffeeMachine>
("espresso");
var drip = provider
.GetKeyedService<ICoffeeMachine>
("drip");
π― Quick Reference
graph LR A["Service Registration"] -->|AddTransient| B["New Every Time"] A -->|AddScoped| C["One Per Request"] A -->|AddSingleton| D["One Forever"] A -->|AddKeyed*| E["Multiple by Key"] F["Service Resolution"] -->|Constructor| G["Auto-injected"] F -->|FromServices| H["Method Parameter"] F -->|GetService| I["Manual Lookup"]
π Complete Example
// Program.cs
var builder = WebApplication
.CreateBuilder(args);
// 1. Register services
builder.Services.AddTransient
<IEmailSender, SmtpEmailSender>();
builder.Services.AddScoped
<IUserRepository, SqlUserRepository>();
builder.Services.AddSingleton
<ICache, RedisCache>();
builder.Services.AddKeyedSingleton
<IPayment, StripePayment>("stripe");
builder.Services.AddKeyedSingleton
<IPayment, PayPalPayment>("paypal");
var app = builder.Build();
// 2. Use them!
app.MapGet("/", (
IEmailSender email,
IUserRepository users,
ICache cache,
[FromKeyedServices("stripe")]
IPayment payment) =>
{
return "Services injected! β¨";
});
app.Run();
π You Did It!
You now understand:
β DI Overview β The magical delivery service β Service Lifetimes β Transient, Scoped, Singleton β Registration β Teaching the container β Resolution β Getting what you need β Keyed Services β Choosing between multiple options
Remember the coffee shop:
- Register = Tell the supplier what you need
- Lifetime = How long supplies last
- Resolution = Getting your delivery
- Keyed = Picking which supplier
Now go build something amazing! π
