Dependency Injection (DI) is one of the core pillars of ASP.NET Core. Most developers quickly learn the three service lifetimes—Transient, Scoped, and Singleton—but confusion often begins when these services start depending on one another.
Questions like these are common:
- Can a Singleton use a Scoped service?
- What happens when a Singleton depends on a Transient service?
- Why does ASP.NET Core throw an exception for some combinations but allow others?
- Is a Transient always recreated?
In this article, we’ll explore not only the three lifetimes but also the relationships between them.
The Three Service Lifetimes
ASP.NET Core provides three built-in service lifetimes.
| Lifetime | Created | Why | Shared | Real-world service |
|---|---|---|---|---|
| Transient | Every time it is requested | Stateless and inexpensive to create | ❌ No | EmailSender, PdfGenerator, PasswordHasher |
| Scoped | Once per HTTP request | Multiple components in a request should use the same instance | ✅ Within the same request | AppDbContext, OrderRepository, OrderService |
| Singleton | Once when the application starts (or first requested) | Data and configuration should be shared application-wide | ✅ Across all requests | MemoryCache, JwtTokenSettings, ExchangeRateCache |
Understanding Service Lifetimes Visually
Imagine two HTTP requests hitting your API.
Request 1
Controller
↓
Service
↓
Repository
↓
DbContext
Request 2
Controller
↓
Service
↓
Repository
↓
DbContext
Transient
A new object is created every time:
Request 1
EmailSender A
EmailSender B
Request 2
EmailSender C
Scoped
The same object is reused within a request:
Request 1
OrderService A
Repository A
DbContext A
Request 2
OrderService B
Repository B
DbContext B
Singleton
The same object is reused by the entire application:
Application Start
↓
MemoryCache A
Request 1 → MemoryCache A
Request 2 → MemoryCache A
Request 3 → MemoryCache A
Which Service Can Depend on Which?
This is where many developers get confused.
| Consumer ↓ / Dependency → | Singleton | Scoped | Transient |
| Singleton | ✅ Allowed | ❌ Not allowed | ✅ Allowed* |
| Scoped | ✅ Allowed | ✅ Allowed | ✅ Allowed |
| Transient | ✅ Allowed | ✅ Allowed | ✅ Allowed |
The asterisk on Singleton → Transient is important because although ASP.NET Core allows it, the behavior may not be what you expect.
1.A Singleton → Singleton ✅
This is perfectly safe.
builder.Services.AddSingleton<CacheService>();
builder.Services.AddSingleton<ReportService>();
Both services live for the entire application lifetime.
1.B Singleton → Scoped ❌
This is the only combination that ASP.NET Core completely blocks.
builder.Services.AddSingleton<ReportService>();
builder.Services.AddScoped<AppDbContext>();
public class ReportService
{
public ReportService(AppDbContext dbContext)
{
}
}
The application throws:
Cannot consume scoped service 'AppDbContext'
from singleton 'ReportService'.
Why?
Because the singleton lives for the entire application, while the scoped service exists only during a single HTTP request.
How Can a Singleton Use a Scoped Service?
The correct solution is to create a scope manually.
public class ReportService
{
private readonly IServiceScopeFactory _scopeFactory;
public ReportService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task GenerateReport()
{
using var scope = _scopeFactory.CreateScope();
var repository =
scope.ServiceProvider
.GetRequiredService<OrderRepository>();
await repository.GenerateReport();
}
}
Execution flow:
Singleton Service
↓
Create Scope
↓
Resolve Scoped Service
↓
Use DbContext
↓
Dispose Scope
This pattern is commonly used in BackgroundService and scheduled jobs.
1.C Singleton → Transient ✅ (But Be Careful)
Most developers assume that a transient dependency is always recreated.
Not necessarily.
builder.Services.AddTransient<EmailSender>();
builder.Services.AddSingleton<NotificationService>();
public class NotificationService
{
private readonly EmailSender _emailSender;
public NotificationService(EmailSender emailSender)
{
_emailSender = emailSender;
}
}
Although EmailSender is registered as transient, it is created only once when the singleton is constructed.
Application Start
↓
NotificationService created
↓
EmailSender created
Request 1 → EmailSender A
Request 2 → EmailSender A
Request 3 → EmailSender A
In practice, the transient behaves like a singleton.
If you truly need a fresh transient instance each time, use IServiceProvider or a factory.
2.A Scoped → Scoped ✅
This is the most common scenario in ASP.NET Core.
builder.Services.AddScoped<AppDbContext>();
builder.Services.AddScoped<OrderRepository>();
builder.Services.AddScoped<OrderService>();
Within a request, all components share the same scope.
Request 1
OrderService A
↓
Repository A
↓
DbContext A
Request 2
OrderService B
↓
Repository B
↓
DbContext B
2.B Scoped → Singleton ✅
This is safe because a shorter-lived object can depend on a longer-lived object.
builder.Services.AddSingleton<MemoryCache>();
builder.Services.AddScoped<OrderService>();
Every request gets a new OrderService, but all requests share the same cache.
2.C Scoped → Transient ✅
Also safe.
builder.Services.AddScoped<OrderService>();
builder.Services.AddTransient<EmailSender>();
Each request creates a new OrderService, which receives a fresh EmailSender.
3. Transient → Anything ✅
Transient services can safely depend on Singleton, Scoped, or other Transient services because they have the shortest lifetime.
Transient → Singleton ✅
Transient → Scoped ✅
Transient → Transient ✅
The Golden Rule
A simple way to remember all of this is:
Longer lifetime → Shorter lifetime = Dangerous
Shorter lifetime → Longer lifetime = Safe
ASP.NET Core lifetimes, from longest to shortest:
Singleton
↓
Scoped
↓
Transient
Summary
Dependency Injection lifetimes are easy to memorize but harder to reason about in real applications.
The key takeaway is this:
- Singleton lives for the entire application.
- Scoped lives for a single request.
- Transient is created whenever needed.
- Singleton cannot directly depend on Scoped.
- Singleton can depend on Transient, but the transient may effectively become a singleton.
- If a singleton needs a scoped service, create a scope manually.
Once you understand the relationship between these lifetimes, many ASP.NET Core concepts—such as DbContext, repositories, middleware, background services, and caching—start to make much more sense.
