Imagine you run a pizza shop. The chef does not grow tomatoes, raise cows for cheese, or forge ovens. Suppliers deliver ingredients; the chef focuses on cooking. If tomato quality changes, you swap suppliers — the chef's recipe stays the same.
Dependency injection (DI) applies that idea to code. A class should not secretly build every helper it needs. It should receive them — injected — from the outside. ASP.NET Core does this automatically, which is why your API from Lesson 7 can cleanly use a database from Lesson 8.
What Is Dependency Injection?
A dependency is something a class needs to do its job — a database context, an email sender, a payment gateway.
Dependency injection means passing those needs in instead of creating them with new inside the class.
Without DI, an order service might look like:
// Tight coupling — hard to test or swap implementations
var db = new AppDbContext();
var email = new SmtpEmailService();
With DI, the framework supplies ready-made instances configured once at startup.
Constructor Injection
The most common pattern — list dependencies in the constructor:
public class OrderService
{
private readonly AppDbContext _db;
public OrderService(AppDbContext db)
{
_db = db;
}
public double GetCustomerTotal(int customerId)
{
return _db.Orders
.Where(o => o.CustomerId == customerId)
.Sum(o => o.Amount);
}
}
OrderService declares "I need a database." ASP.NET Core's DI container (built into the framework) creates OrderService and passes a configured AppDbContext automatically when someone asks for an order calculation.
Registering Services
In Program.cs, you tell the container how to build each type:
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<OrderService>();
Service lifetimes control how long instances live:
Transient— new instance every time (lightweight helpers)Scoped— one per HTTP request (typical for DbContext)Singleton— one for entire app (configuration, caches)
DI in a Request
HTTP request arrives
↓
Container creates scoped DbContext
↓
Injects DbContext into OrderService
↓
Injects OrderService into API endpoint
↓
Request ends → scoped objects disposed
Like a food delivery platform assigning one driver and one insulated bag per order — resources match the job duration, then get recycled.
Using DI in an Endpoint
app.MapGet("/api/customer/{id}/total", (int id, OrderService orders) =>
{
double total = orders.GetCustomerTotal(id);
return new { CustomerId = id, TotalSpent = total };
});
Notice OrderService orders as a parameter — not created by you. The container sees the type and injects it. Same magic works for controllers in larger apps.
Real-World Example
A banking app might inject:
AppDbContextfor account dataIFraudCheckerfor suspicious transaction rulesISmsGatewayfor OTP texts
In testing, developers swap real SMS with a fake that logs messages instead of sending them — because DI depends on interfaces, not concrete phone networks. That flexibility saves money and prevents accidental texts to real customers during development.
Common Misconceptions
"DI is just a buzzword for passing parameters." True at the micro level — but the container manages lifetimes, graphs of nested dependencies, and configuration centrally.
"Singleton is always best — one instance is efficient." DbContext must not be singleton — it is not thread-safe for concurrent requests. Use scoped.
"DI means I never use new." You still new plain data objects (new BankAccount()). Do not new infrastructure services inside business logic.
"DI is optional in ASP.NET Core." The framework is built around it. Fighting DI makes life harder, not simpler.
Quick Recap
- Dependencies are services a class needs.
- Injection supplies them from outside — usually via constructor.
- Register services in
Program.cswith appropriate lifetimes. - Endpoints and controllers receive services as parameters.
- DI improves testing and swapping implementations (real SMS vs fake).
Summary
Dependency injection is organised sharing — the pizza chef focusing on cooking while trusted suppliers handle ingredients. Your API endpoints focus on HTTP while the container handles wiring databases and business services.
Lesson 10 closes the path with debugging — finding why injected services or EF queries misbehave when something goes wrong.
Frequently Asked Questions
new.DbContext so each request gets a fresh database session.Key Takeaways
- DI passes dependencies in rather than building them inside classes.
- Register services once in
Program.cs. - Choose lifetimes carefully: Scoped for DbContext, Singleton for config.
- Constructor injection is the standard pattern in .NET.
- DI enables testing with fake email, payment, and database services.