In modern .NET applications especially in ASP.NET Core and background services we rely heavily on dependency injection (DI) to manage object lifetimes and dependencies. While singleton and transient services are easy to understand, things become nuanced when it comes to scoped services, especially outside of the request pipeline.
This is where IServiceScopeFactory becomes crucial.
This article breaks down.
What is IServiceScopeFactory?
IServiceScopeFactory is an interface provided by the .NET DI container that allows you to create a new service scope manually.
When you call CreateScope(), it returns an IServiceScope, which has its own IServiceProvider. You can use this scoped provider to resolve scoped services, which would otherwise be inaccessible during a web request.
public interface IServiceScopeFactory
{
IServiceScope CreateScope();
}
Why is it needed?
Typically, scoped services (like a DbContext) are created per HTTP request . But what if you’re in a background task or a singleton service and need to use a scoped service?
You can’t directly inject a scoped service into a singleton. That would break the lifetime rules and throw an exception (or worse, cause memory leaks).
That’s where IServiceScopeFactory shines. It allows you to safely create a scope and use scoped services, even from singletons or hosted services.
When to Use IServiceScopeFactory?
Let’s look at practical scenarios.
Using Scoped Services in Hosted Background Services
Let’s say you’re running a background task that syncs data every few minutes, and you need to access MyDbContext.
MyDbContext is registered as scoped, but BackgroundService is a singleton.
Correct way of using IServiceScopeFactory.
public class DataSyncService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public DataSyncService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var records = await dbContext.Records.ToListAsync();
// process records...
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
This safely creates a scope and ensures all scoped services are disposed of properly.
Queue-based Processing (Worker Pattern)
Let’s say you’re queuing jobs and processing them using a singleton MessageProcessor. You need access to scoped services per message.
public class MessageProcessor
{
private readonly IServiceScopeFactory _scopeFactory;
public MessageProcessor(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task ProcessMessageAsync(MyMessage message)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<IMessageHandler>();
await handler.HandleAsync(message);
}
}
This ensures every message gets its own fresh scope.
On-Demand Scoped Dependencies in Console Apps or Middleware
In a console app or custom middleware where there’s no implicit scope, you can create one manually.
var serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
using var scope = serviceScopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
When Not to Use IServiceScopeFactory?
While it’s powerful, misuse can create hard-to-debug memory leaks or broken DI lifetimes.
1. Don’t Use IServiceScopeFactory Inside Controllers
Controllers and Razor Pages already operate within a scope—no need to create another.
Bad Example
public class HomeController : Controller
{
private readonly IServiceScopeFactory _scopeFactory;
public HomeController(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;
public IActionResult Index()
{
using var scope = _scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
// Not needed!
}
}
Good example
public class HomeController : Controller
{
private readonly MyDbContext _db;
public HomeController(MyDbContext db) => _db = db;
public IActionResult Index()
{
var data = _db.Records.ToList();
return View(data);
}
}
2. Don’t Keep a Scope Alive Too Long
A scope is meant to be short-lived (per request or operation). Don’t hold onto a scope and reuse it across multiple operations.
This can lead to,
Key Benefits of IServiceScopeFactory
Enables the use of scoped services in singleton/background services
Ensures services are disposed of properly
Helps maintain correct DI lifetimes
Prevents common anti-patterns like injecting scoped into a singleton
Alternative: Use IServiceProvider.CreateScope()
If you’re in Program.cs or somewhere you already have access to IServiceProvider, you can call.
using var scope = app.Services.CreateScope();
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
But in long-lived services, it’s better to inject IServiceScopeFactory to avoid capturing the root IServiceProvider.
If you’re building long-running applications or background services in .NET, mastering IServiceScopeFactory will make your code cleaner, safer, and more maintainable.
Thank you for reading. Please share your questions, thoughts, or feedback in the comments section. I appreciate your feedback and encouragement.
Keep learning…! 😊