Previous logging and monitoring in a .NET Agile-and-QA-Metrics Next

In .NET Core, dependency injection (DI) service lifetimes

DI Service Lifetimes (Transient, Scoped, Singleton)

In .NET Core, dependency injection (DI) service lifetimes—transient, scoped, and singleton—determine how instances of a service are created, shared, and managed. They are configured during application startup, typically in Program.cs, using methods like AddTransient(), AddScoped(), and AddSingleton().

Transient

Creation: A new instance is created every single time the service is requested from the DI container.

Sharing: No instances are shared. Even within the same HTTP request, if a transient service is injected into multiple components, each will receive its own unique instance.

Best for: Lightweight, stateless services that should not share state. Examples include helper classes, random number generators, and simple utility services.

Registration:

services.AddTransient<IMyTransientService, MyTransientService>();

Scoped

Creation: A new instance is created once per client request (or "scope"). In a typical ASP.NET Core web application, this means one instance is created per HTTP request.

Sharing: The same instance is shared across all components that request it within the same request scope. A new request, however, will create a new instance.

Best for: Services that need to maintain state throughout a single, contained operation, such as a database context (DbContext) in Entity Framework Core. This ensures that all components within a single request use the same database transaction.

Registration:

services.AddScoped<IMyScopedService, MyScopedService>();

Singleton

Creation: Only one instance is created for the entire application's lifetime. This happens the first time the service is requested.

Sharing: The exact same instance is shared across all requests and all components in the application until the application is shut down.

Best for: Stateless and thread-safe services that are expensive to create, such as a logging service, a configuration manager, or an in-memory cache. Because the instance is shared, it must be designed to be thread-safe.

Registration:

services.AddSingleton<IMySingletonService, MySingletonService>();

Important considerations and pitfalls

"Captive dependency" issue

You should never inject a service with a shorter lifetime (scoped or transient) into a service with a longer lifetime (singleton). Doing so can lead to a "captive dependency" bug, where the longer-lived service holds onto an instance of the shorter-lived service for its entire lifetime, overriding its intended behavior. For example, injecting a scoped DbContext into a singleton background service would cause the DbContext instance to live forever, leading to memory leaks and incorrect state.

Solving captive dependencies

If you must use a shorter-lived service inside a longer-lived one, you can manually create a service scope. This is often done by injecting IServiceScopeFactory into the singleton service and then creating a new scope to resolve the shorter-lived dependency only when you need it.

Registration in Program.cs

In .NET 6 and later, service registrations happen directly in Program.cs.

var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddTransient\();
builder.Services.AddScoped\();
builder.Services.AddSingleton\();

Quick Analogy

Imagine you’re at a coffee shop:

Transient

Every time you order a coffee, you get a brand new cup.

Scoped

You get one cup per visit; you can refill it during your stay, but next time you visit, it’s a new cup.

Singleton

The shop gives you one thermos for life; you carry it everywhere, and it’s refilled forever.

💡 Pro Tip for Large-Scale Apps (like your modular platforms)

  • Transient: Great for small, stateless utility classes.
  • Scoped: Ideal for request-bound data (e.g., EF Core DbContext).
  • Singleton: Perfect for configuration, caching, or services that must be reused across the app.

In multi-tenant SaaS, be careful with singletons holding tenant-specific data — they can leak state between tenants.

Back to Index
Previous logging and monitoring in a .NET Agile-and-QA-Metrics Next
*