Improper Scope Context in .NET Dependency Injection(DI)
1. Understanding DI Scopes in .NET
Dependency Injection (DI) is one of the most powerful features in .NET (formerly .NET Core). Among the three built-in service lifetimes—Transient, Scoped, and Singleton—the Scoped lifetime is the most misunderstood and, when used correctly, the most useful. Please read my blogs on Dependency Injection and Service Lifetimes to know more.
What Exactly Is a "Scope" or "Scope Context" in .NET DI?
Scoped context/Scope in dependency injection defines a boundary within which services are created and shared.
In ASP.NET Core, the framework automatically creates a new scope for every HTTP request (via middleware). All your controllers, Razor Pages, Minimal API endpoints, filters, etc., within that request share the same scoped services. At the end of the request, the scope is disposed automatically.
Outside of web requests (console apps, background services, Azure Functions, etc.), no scope exists by default. You must create one yourself.
Where Does Scope Come From?
In Web Applications
Incoming Request
→ Create Scope
→ Resolve Scoped Services
→ Execute Request
→ Dispose Scope
You don’t manage it—framework does it
Outside Web
In below apps ,No scope exists by default , You must manually create one.
Real Lifecycle of Scope:
Why Scoped Context Exists?
It solves below problems
What Lives Inside a Scope?
A scope contains:
Scope (Request 1)
├── DbContext (Scoped - 1 instance)
├── OrderService (Scoped - 1 instance)
├── Helper (Transient - many)
└── Logger (Singleton - shared globally)
When Scoped Services Are Extremely Useful ?
Example 1: Entity Framework Core DbContext DbContext must be scoped because it tracks changes, maintains a change tracker, and should be disposed after each request.
// Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
// In a service
public class OrderService(IScopedService scopedService, ApplicationDbContext db)
{
public async Task CreateOrderAsync(OrderDto dto)
{
// Same DbContext instance is used throughout this HTTP request
var order = new Order { ... };
db.Orders.Add(order);
await db.SaveChangesAsync(); // All changes are tracked together
}
}
If you made DbContext singleton, you would leak connections and get stale data across requests. If transient, you would lose change tracking. Scoped is perfect.
Example 2: Per-Request User Context Great for avoiding HttpContextAccessor everywhere.
public interface ICurrentUserService
{
string UserId { get; }
bool IsAdmin { get; }
}
public class CurrentUserService : ICurrentUserService, IDisposable
{
public string UserId { get; }
public CurrentUserService(IHttpContextAccessor accessor)
{
UserId = accessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous";
}
public void Dispose() { /* cleanup if needed */ }
}
// Registration
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
Now every service in the same request shares the exact same user context.
Example 3: Scoped Background Processing (Worker Service) Background services run outside any HTTP request, so you must create scopes manually.
public sealed class ScopedBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public ScopedBackgroundService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using IServiceScope scope = _scopeFactory.CreateScope(); // ← Critical!
var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
await processor.ProcessPendingOrdersAsync();
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
Without the using scope, your IOrderProcessor (if scoped) would either fail or become a captive dependency.
We will discuss more on captive dependency in next blog.
Recommended by LinkedIn
Improper DI Scope Issues:
Injecting Scoped Services into Singletons(Dangerous)
public class ReportService
{
private readonly AppDbContext _dbContext;
public ReportService(AppDbContext dbContext) // DbContext is Scoped
{
_dbContext = dbContext;
}
}
builder.Services.AddSingleton<ReportService>();
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connString));
Problem: ReportService is singleton but holds a scoped DbContext. The same DbContext instance will be reused across requests — huge risk for concurrency issues.
Using Scoped Services in Background Services
public class EmailSenderService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public EmailSenderService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var emails = await dbContext.Emails.ToListAsync();
// send emails
}
}
}
Never inject a scoped service directly into a singleton or background service.
Mixing Transient Services with Scoped Dependencies
public class AnalyticsService
{
private readonly AppDbContext _dbContext;
public AnalyticsService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
}
builder.Services.AddTransient<AnalyticsService>();
builder.Services.AddDbContext<AppDbContext>();
Real Production Failure Issues
Scenario 1: Disposed DbContext Exception
A web app had a singleton caching service that injected DbContext to preload data. After a few hours, users started seeing:
System.ObjectDisposedException: Cannot access a disposed object.
DbContext has been disposed.
Cause: Singleton kept a reference to a scoped DbContext that was disposed after the HTTP request completed.
Scenario 2: Cross-Request Data Leakage
A reporting service incorrectly injected DbContext into a singleton. Requests from User A could see data from User B’s request due to shared DbContext instance.
Scenario 3: Background Job Fails Randomly
A background job that sends daily emails failed intermittently with:
InvalidOperationException: Cannot resolve scoped service 'AppDbContext' from root provider.
Cause: The job tried to use scoped DbContext without creating a scope manually.
Best Practices to Avoid Improper DI Scope
DI Scope Diagram (Simplified)
[Singleton Service] --------> only other singleton or stateless service
|
x (wrong!) -----------------> Scoped DbContext
[Scoped Service] -----------> can inject transient or scoped safely
[Transient Service] -------> can inject any lifetime safely, but cannot hold long-lived state
Conclusion:
Scoped services are really important for anything that needs to be request-specific or unit-of-work specific: database contexts, user context, HTTP clients with per-request headers, transaction scopes, etc. They give you isolation without the overhead of creating a new object on every resolution.
But they come with responsibility. The most dangerous mistake is letting a scoped service escape its scope (captive dependency). Once you internalize the rule “never let a longer lifetime capture a shorter one,” scoped services become one of the most reliable and powerful tools in .NET.