// Register example service in IServiceCollection
builder.Services.AddSingleton(new ExampleService());
In the preceding code:
The ExampleService
instance is not created by the service container.
The framework does not dispose of the services automatically.
The developer is responsible for disposing the services.
IDisposable guidance for transient and shared instances
Transient, limited lifetime
Scenario
The app requires an IDisposable instance with a transient lifetime for either of the following scenarios:
The instance is resolved in the root scope (root container).
The instance should be disposed before the scope ends.
Solution
Use the factory pattern to create an instance outside of the parent scope. In this situation, the app would generally have a Create
method that calls the final type's constructor directly. If the final type has other dependencies, the factory can:
Receive an IServiceProvider in its constructor.
Use ActivatorUtilities.CreateInstance to instantiate the instance outside of the container, while using the container for its dependencies.
Shared instance, limited lifetime
Scenario
The app requires a shared IDisposable instance across multiple services, but the IDisposable instance should have a limited lifetime.
Solution
Register the instance with a scoped lifetime. Use IServiceScopeFactory.CreateScope to create a new IServiceScope. Use the scope's IServiceProvider to get required services. Dispose the scope when it's no longer needed.
General IDisposable
guidelines
Don't register IDisposable instances with a transient lifetime. Use the factory pattern instead.
Don't resolve IDisposable instances with a transient or scoped lifetime in the root scope. The only exception to this is if the app creates/recreates and disposes IServiceProvider, but this isn't an ideal pattern.
Receiving an IDisposable dependency via DI doesn't require that the receiver implement IDisposable itself. The receiver of the IDisposable dependency shouldn't call Dispose on that dependency.
Use scopes to control the lifetimes of services. Scopes aren't hierarchical, and there's no special connection among scopes.
For more information on resource cleanup, see Implement a Dispose
method, or Implement a DisposeAsync
method. Additionally, consider the Disposable transient services captured by container scenario as it relates to resource cleanup.
Default service container replacement
The built-in service container is designed to serve the needs of the framework and most consumer apps. We recommend using the built-in container unless you need a specific feature that it doesn't support, such as:
Property injection
Injection based on name
Child containers
Custom lifetime management
Func<T>
support for lazy initialization
Convention-based registration
The following third-party containers can be used with ASP.NET Core apps:
Autofac
DryIoc
Grace
LightInject
Lamar
Stashbox
Simple Injector
Thread safety
Create thread-safe singleton services. If a singleton service has a dependency on a transient service, the transient service may also require thread safety depending on how it's used by the singleton.
The factory method of a singleton service, such as the second argument to AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), doesn't need to be thread-safe. Like a type (static
) constructor, it's guaranteed to be called only once by a single thread.
Recommendations
async/await
and Task
based service resolution isn't supported. Because C# doesn't support asynchronous constructors, use asynchronous methods after synchronously resolving the service.
Avoid storing data and configuration directly in the service container. For example, a user's shopping cart shouldn't typically be added to the service container. Configuration should use the options pattern. Similarly, avoid "data holder" objects that only exist to allow access to another object. It's better to request the actual item via DI.
Avoid static access to services. For example, avoid capturing IApplicationBuilder.ApplicationServices as a static field or property for use elsewhere.
Keep DI factories fast and synchronous.
Avoid using the service locator pattern. For example, don't invoke GetService to obtain a service instance when you can use DI instead.
Another service locator variation to avoid is injecting a factory that resolves dependencies at run time. Both of these practices mix Inversion of Control strategies.
Avoid calls to BuildServiceProvider when configuring services. Calling BuildServiceProvider
typically happens when the developer wants to resolve a service when registering another service. Instead, use an overload that includes the IServiceProvider
for this reason.
Disposable transient services are captured by the container for disposal. This can turn into a memory leak if resolved from the top-level container.
Enable scope validation to make sure the app doesn't have singletons that capture scoped services. For more information, see Scope validation.
Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Exceptions are rare, mostly special cases within the framework itself.
DI is an alternative to static/global object access patterns. You may not be able to realize the benefits of DI if you mix it with static object access.
Example anti-patterns
In addition to the guidelines in this article, there are several anti-patterns you should avoid. Some of these anti-patterns are learnings from developing the runtimes themselves.
Warning
These are example anti-patterns, do not copy the code, do not use these patterns, and avoid these patterns at all costs.
Disposable transient services captured by container
When you register Transient services that implement IDisposable, by default the DI container will hold onto these references, and not Dispose() of them until the container is disposed when application stops if they were resolved from the container, or until the scope is disposed if they were resolved from a scope. This can turn into a memory leak if resolved from container level.
static void TransientDisposablesWithoutDispose()
var services = new ServiceCollection();
services.AddTransient<ExampleDisposable>();
ServiceProvider serviceProvider = services.BuildServiceProvider();
for (int i = 0; i < 1000; ++ i)
_ = serviceProvider.GetRequiredService<ExampleDisposable>();
// serviceProvider.Dispose();
In the preceding anti-pattern, 1,000 ExampleDisposable
objects are instantiated and rooted. They will not be disposed of until the serviceProvider
instance is disposed.
For more information on debugging memory leaks, see Debug a memory leak in .NET.
Async DI factories can cause deadlocks
The term "DI factories" refers to the overload methods that exist when calling Add{LIFETIME}
. There are overloads accepting a Func<IServiceProvider, T>
where T
is the service being registered, and the parameter is named implementationFactory
. The implementationFactory
can be provided as a lambda expression, local function, or method. If the factory is asynchronous, and you use Task<TResult>.Result, this will cause a deadlock.
static void DeadLockWithAsyncFactory()
var services = new ServiceCollection();
services.AddSingleton<Foo>(implementationFactory: provider =>
Bar bar = GetBarAsync(provider).Result;
return new Foo(bar);
services.AddSingleton<Bar>();
using ServiceProvider serviceProvider = services.BuildServiceProvider();
_ = serviceProvider.GetRequiredService<Foo>();
In the preceding code, the implementationFactory
is given a lambda expression where the body calls Task<TResult>.Result on a Task<Bar>
returning method. This causes a deadlock. The GetBarAsync
method simply emulates an asynchronous work operation with Task.Delay, and then calls GetRequiredService<T>(IServiceProvider).
static async Task<Bar> GetBarAsync(IServiceProvider serviceProvider)
// Emulate asynchronous work operation
await Task.Delay(1000);
return serviceProvider.GetRequiredService<Bar>();
For more information on asynchronous guidance, see Asynchronous programming: Important info and advice. For more information debugging deadlocks, see Debug a deadlock in .NET.
When you're running this anti-pattern and the deadlock occurs, you can view the two threads waiting from Visual Studio's Parallel Stacks window. For more information, see View threads and tasks in the Parallel Stacks window.
Captive dependency
The term "captive dependency" was coined by Mark Seemann, and refers to the misconfiguration of service lifetimes, where a longer-lived service holds a shorter-lived service captive.
static void CaptiveDependency()
var services = new ServiceCollection();
services.AddSingleton<Foo>();
services.AddScoped<Bar>();
using ServiceProvider serviceProvider = services.BuildServiceProvider();
// Enable scope validation
// using ServiceProvider serviceProvider = services.BuildServiceProvider(validateScopes: true);
_ = serviceProvider.GetRequiredService<Foo>();
In the preceding code, Foo
is registered as a singleton and Bar
is scoped - which on the surface seems valid. However, consider the implementation of Foo
.
namespace DependencyInjection.AntiPatterns
public class Foo
public Foo(Bar bar)
The Foo
object requires a Bar
object, and since Foo
is a singleton, and Bar
is scoped - this is a misconfiguration. As is, Foo
would only be instantiated once, and it would hold onto Bar
for its lifetime, which is longer than the intended scoped lifetime of Bar
. You should consider validating scopes, by passing validateScopes: true
to the BuildServiceProvider(IServiceCollection, Boolean). When you validate the scopes, you'd get an InvalidOperationException with a message similar to "Cannot consume scoped service 'Bar' from singleton 'Foo'.".
For more information, see Scope validation.
Scoped service as singleton
When using scoped services, if you're not creating a scope or within an existing scope - the service becomes a singleton.
static void ScopedServiceBecomesSingleton()
var services = new ServiceCollection();
services.AddScoped<Bar>();
using ServiceProvider serviceProvider = services.BuildServiceProvider(validateScopes: true);
using (IServiceScope scope = serviceProvider.CreateScope())
// Correctly scoped resolution
Bar correct = scope.ServiceProvider.GetRequiredService<Bar>();
// Not within a scope, becomes a singleton
Bar avoid = serviceProvider.GetRequiredService<Bar>();
In the preceding code, Bar
is retrieved within an IServiceScope, which is correct. The anti-pattern is the retrieval of Bar
outside of the scope, and the variable is named avoid
to show which example retrieval is incorrect.
See also
Dependency injection in .NET
Tutorial: Use dependency injection in .NET