This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can trysigning in orchanging directories.
Access to this page requires authorization. You can trychanging directories.
.NET supports the dependency injection (DI) software design pattern, which is a technique for achievingInversion of Control (IoC) between classes and their dependencies. Dependency injection in .NET is a built-in part of the framework, along with configuration, logging, and the options pattern.
Adependency is an object that another object depends on. Examine the followingMessageWriter class with aWrite method that other classes depend on:
public class MessageWriter{ public void Write(string message) { Console.WriteLine($"MessageWriter.Write(message: \"{message}\")"); }}A class can create an instance of theMessageWriter class to use itsWrite method. In the following example, theMessageWriter class is a dependency of theWorker class:
public class Worker : BackgroundService{ private readonly MessageWriter _messageWriter = new(); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}"); await Task.Delay(1_000, stoppingToken); } }}The class creates and directly depends on theMessageWriter class. Hard-coded dependencies, such as in the previous example, are problematic and should be avoided for the following reasons:
MessageWriter with a different implementation, you must modify theWorker class.MessageWriter has dependencies, theWorker class must also configure them. In a large project with multiple classes depending onMessageWriter, the configuration code becomes scattered across the app.MessageWriter class, which isn't possible with this approach.Dependency injection addresses the following problems through:
As an example, theIMessageWriter interface defines theWrite method:
namespace DependencyInjection.Example;public interface IMessageWriter{ void Write(string message);}This interface is implemented by a concrete type,MessageWriter:
namespace DependencyInjection.Example;public class MessageWriter : IMessageWriter{ public void Write(string message) { Console.WriteLine($"MessageWriter.Write(message: \"{message}\")"); }}The sample code registers theIMessageWriter service with the concrete typeMessageWriter. TheAddSingleton method registers the service with a singleton lifetime, the lifetime of the app.Service lifetimes are described later in this article.
using DependencyInjection.Example;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Services.AddHostedService<Worker>();builder.Services.AddSingleton<IMessageWriter, MessageWriter>();using IHost host = builder.Build();host.Run();In the preceding code, the sample app:
Creates a host app builder instance.
Configures the services by registering:
Worker as a hosted service. For more information, seeWorker Services in .NET.IMessageWriter interface as a singleton service with a corresponding implementation of theMessageWriter class.Builds the host and runs it.
The host contains the dependency injection service provider. It also contains all the other relevant services required to automatically instantiate theWorker and provide the correspondingIMessageWriter implementation as an argument.
namespace DependencyInjection.Example;public sealed class Worker(IMessageWriter messageWriter) : BackgroundService{ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { messageWriter.Write($"Worker running at: {DateTimeOffset.Now}"); await Task.Delay(1_000, stoppingToken); } }}By using the DI pattern, the worker service:
MessageWriter, only theIMessageWriter interface that it implements. This makes it easy to change the implementation that the worker service uses without modifying the worker service.MessageWriter. The DI container creates the instance.The implementation of theIMessageWriter interface can be improved using the built-in logging API:
namespace DependencyInjection.Example;public class LoggingMessageWriter( ILogger<LoggingMessageWriter> logger) : IMessageWriter{ public void Write(string message) => logger.LogInformation("Info: {Msg}", message);}The updatedAddSingleton method registers the newIMessageWriter implementation:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();TheHostApplicationBuilder (builder) type is part of theMicrosoft.Extensions.Hosting NuGet package.
LoggingMessageWriter depends onILogger<TCategoryName>, which it requests in the constructor.ILogger<TCategoryName> is aframework-provided service.
It's not unusual to use dependency injection in a chained fashion. Each requested dependency in turn requests its own dependencies. The container resolves the dependencies in the graph and returns the fully resolved service. The collective set of dependencies that must be resolved is typically called adependency tree,dependency graph, orobject graph.
The container resolvesILogger<TCategoryName> by taking advantage of(generic) open types, eliminating the need to register every(generic) constructed type.
With dependency injection terminology, a service:
IMessageWriter service.The framework provides a robust logging system. TheIMessageWriter implementations shown in the preceding examples demonstrate basic DI, not logging. Most apps shouldn't need to write loggers. The following code demonstrates using the default logging, which only requires theWorker to be registered as a hosted serviceAddHostedService:
public sealed class Worker(ILogger<Worker> logger) : BackgroundService{ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1_000, stoppingToken); } }}Using the preceding code, there's no need to updateProgram.cs, because the framework provides logging.
When a type defines more than one constructor, the service provider has logic for determining which constructor to use. The constructor with the most parameters where the types are DI-resolvable is selected. Consider the following C# example service:
public class ExampleService{ public ExampleService() { } public ExampleService(ILogger<ExampleService> logger) { // omitted for brevity } public ExampleService(FooService fooService, BarService barService) { // omitted for brevity }}In the preceding code, assume that logging has been added and is resolvable from the service provider but theFooService andBarService types aren't. The constructor with theILogger<ExampleService> parameter resolves theExampleService instance. Even though there's a constructor that defines more parameters, theFooService andBarService types aren't DI-resolvable.
If there's ambiguity when discovering constructors, an exception is thrown. Consider the following C# example service:
public class ExampleService{ public ExampleService() { } public ExampleService(ILogger<ExampleService> logger) { // omitted for brevity } public ExampleService(IOptions<ExampleOptions> options) { // omitted for brevity }}Warning
TheExampleService code with ambiguous DI-resolvable type parameters throws an exception.Don't do this—it's intended to show what is meant by "ambiguous DI-resolvable types".
In the preceding example, there are three constructors. The first constructor is parameterless and requires no services from the service provider. Assume that both logging and options have been added to the DI container and are DI-resolvable services. When the DI container attempts to resolve theExampleService type, it throws an exception, as the two constructors are ambiguous.
Avoid ambiguity by defining a constructor that accepts both DI-resolvable types instead:
public class ExampleService{ public ExampleService() { } public ExampleService( ILogger<ExampleService> logger, IOptions<ExampleOptions> options) { // omitted for brevity }}Microsoft Extensions uses a convention for registering a group of related services. The convention is to use a singleAdd{GROUP_NAME} extension method to register all of the services required by a framework feature. For example, theAddOptions extension method registers all of the services required for using options.
When using any of the available host or app builder patterns, defaults are applied and services are registered by the framework. Consider some of the most popular host and app builder patterns:
After creating a builder from any of these APIs, theIServiceCollection has services defined by the framework, depending onhow you configured the host. For apps based on the .NET templates, the framework could register hundreds of services.
The following table lists a small sample of these framework-registered services:
| Service Type | Lifetime |
|---|---|
| Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | Singleton |
| IHostApplicationLifetime | Singleton |
| Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
| Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
| Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
| Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Transient |
| Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
| System.Diagnostics.DiagnosticListener | Singleton |
| System.Diagnostics.DiagnosticSource | Singleton |
Services can be registered with one of the following lifetimes:
The following sections describe each of the preceding lifetimes. Choose an appropriate lifetime for each registered service.
Transient lifetime services are created each time they're requested from the service container. To register a service astransient, callAddTransient.
In apps that process requests, transient services are disposed at the end of the request. This lifetime incurs per/request allocations, as services are resolved and constructed every time. For more information, seeDependency Injection Guidelines: IDisposable guidance for transient and shared instances.
For web applications, a scoped lifetime indicates that services are created once per client request (connection). Register scoped services withAddScoped.
In apps that process requests, scoped services are disposed at the end of the request.
Note
When using Entity Framework Core, theAddDbContext extension method registersDbContext types with a scoped lifetime by default.
A scoped service should always be used from within a scope—either an implicit scope (such as ASP.NET Core's per-request scope) or an explicit scope created withIServiceScopeFactory.CreateScope().
Donot resolve a scoped service directly from a singleton using constructor injection or by requesting it fromIServiceProvider in the singleton. Doing so causes the scoped service to behave like a singleton, which can lead to incorrect state when processing subsequent requests.
It's acceptable to resolve a scoped service within a singleton if you create and use an explicit scope withIServiceScopeFactory.
It's also fine to:
By default, in the development environment, resolving a service from another service with a longer lifetime throws an exception. For more information, seeScope validation.
Singleton lifetime services are created either:
Every subsequent request of the service implementation from the dependency injection container uses the same instance. If the app requires singleton behavior, allow the service container to manage the service's lifetime. Don't implement the singleton design pattern and provide code to dispose of the singleton. Services should never be disposed by code that resolved the service from the container. If a type or factory is registered as a singleton, the container disposes the singleton automatically.
Register singleton services withAddSingleton. Singleton services must be thread safe and are often used in stateless services.
In apps that process requests, singleton services are disposed when theServiceProvider is disposed on application shutdown. Because memory isn't released until the app shuts down, consider memory use with a singleton service.
The framework provides service registration extension methods that are useful in specific scenarios:
| Method | Automatic object disposal | Multiple implementations | Pass args |
|---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()Example: services.AddSingleton<IMyDep, MyDep>(); | Yes | Yes | No |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})Examples: services.AddSingleton<IMyDep>(sp => new MyDep());services.AddSingleton<IMyDep>(sp => new MyDep(99)); | Yes | Yes | Yes |
Add{LIFETIME}<{IMPLEMENTATION}>()Example: services.AddSingleton<MyDep>(); | Yes | No | No |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})Examples: services.AddSingleton<IMyDep>(new MyDep());services.AddSingleton<IMyDep>(new MyDep(99)); | No | Yes | Yes |
AddSingleton(new {IMPLEMENTATION})Examples: services.AddSingleton(new MyDep());services.AddSingleton(new MyDep(99)); | No | No | Yes |
For more information on type disposal, see theDisposal of services section.
Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. For example, consider the following code:
services.AddSingleton<ExampleService>();This is equivalent to registering the service with both the service and implementation of the same types:
services.AddSingleton<ExampleService, ExampleService>();This equivalency is why multiple implementations of a service can't be registered using the methods that don't take an explicit service type. These methods can register multipleinstances of a service, but they all have the sameimplementation type.
Any of the service registration methods can be used to register multiple service instances of the same service type. In the following example,AddSingleton is called twice withIMessageWriter as the service type. The second call toAddSingleton overrides the previous one when resolved asIMessageWriter and adds to the previous one when multiple services are resolved viaIEnumerable<IMessageWriter>. Services appear in the order they were registered when resolved viaIEnumerable<{SERVICE}>.
using ConsoleDI.IEnumerableExample;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();builder.Services.AddSingleton<ExampleService>();using IHost host = builder.Build();_ = host.Services.GetService<ExampleService>();await host.RunAsync();The preceding sample source code registers two implementations of theIMessageWriter.
using System.Diagnostics;namespace ConsoleDI.IEnumerableExample;public sealed class ExampleService{ public ExampleService( IMessageWriter messageWriter, IEnumerable<IMessageWriter> messageWriters) { Trace.Assert(messageWriter is LoggingMessageWriter); var dependencyArray = messageWriters.ToArray(); Trace.Assert(dependencyArray[0] is ConsoleMessageWriter); Trace.Assert(dependencyArray[1] is LoggingMessageWriter); }}TheExampleService defines two constructor parameters; a singleIMessageWriter, and anIEnumerable<IMessageWriter>. The singleIMessageWriter is the last implementation to be registered, whereas theIEnumerable<IMessageWriter> represents all registered implementations.
The framework also providesTryAdd{LIFETIME} extension methods, which register the service only if there isn't already an implementation registered.
In the following example, the call toAddSingleton registersConsoleMessageWriter as an implementation forIMessageWriter. The call toTryAddSingleton has no effect becauseIMessageWriter already has a registered implementation:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();TheTryAddSingleton has no effect, as it was already added and the "try" fails. TheExampleService asserts the following:
public class ExampleService{ public ExampleService( IMessageWriter messageWriter, IEnumerable<IMessageWriter> messageWriters) { Trace.Assert(messageWriter is ConsoleMessageWriter); Trace.Assert(messageWriters.Single() is ConsoleMessageWriter); }}For more information, see:
TheTryAddEnumerable(ServiceDescriptor) methods register the service only if there isn't already an implementationof the same type. Multiple services are resolved viaIEnumerable<{SERVICE}>. When registering services, add an instance if one of the same types wasn't already added. Library authors useTryAddEnumerable to avoid registering multiple copies of an implementation in the container.
In the following example, the first call toTryAddEnumerable registersMessageWriter as an implementation forIMessageWriter1. The second call registersMessageWriter forIMessageWriter2. The third call has no effect becauseIMessageWriter1 already has a registered implementation ofMessageWriter:
public interface IMessageWriter1 { }public interface IMessageWriter2 { }public class MessageWriter : IMessageWriter1, IMessageWriter2{}services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());Service registration is order-independent except when registering multiple implementations of the same type.
IServiceCollection is a collection ofServiceDescriptor objects. The following example shows how to register a service by creating and adding aServiceDescriptor:
string secretKey = Configuration["SecretKey"];var descriptor = new ServiceDescriptor( typeof(IMessageWriter), _ => new DefaultMessageWriter(secretKey), ServiceLifetime.Transient);services.Add(descriptor);The built-inAdd{LIFETIME} methods use the same approach. For example, see theAddScoped source code.
Services can be resolved using:
Constructors can accept arguments that aren't provided by dependency injection, but the arguments must assign default values.
WhenIServiceProvider orActivatorUtilities resolve services, constructor injection requires apublic constructor.
WhenActivatorUtilities resolves services, constructor injection requires that only one applicable constructor exists. Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection.
When the app runs in theDevelopment environment and callsCreateApplicationBuilder to build the host, the default service provider performs checks to verify that:
The root service provider is created whenBuildServiceProvider is called. The root service provider's lifetime corresponds to the app's lifetime when the provider starts with the app and is disposed when the app shuts down.
Scoped services are disposed by the container that created them. If a scoped service is created in the root container, the service's lifetime is effectively promoted to singleton because it's only disposed by the root container when the app shuts down. Validating service scopes catches these situations whenBuildServiceProvider is called.
TheIServiceScopeFactory is always registered as a singleton, but theIServiceProvider can vary based on the lifetime of the containing class. For example, if you resolve services from a scope, and any of those services take anIServiceProvider, it is a scoped instance.
To achieve scoping services within implementations ofIHostedService, such as theBackgroundService,don't inject the service dependencies via constructor injection. Instead, injectIServiceScopeFactory, create a scope, then resolve dependencies from the scope to use the appropriate service lifetime.
namespace WorkerScope.Example;public sealed class Worker( ILogger<Worker> logger, IServiceScopeFactory serviceScopeFactory) : BackgroundService{ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using (IServiceScope scope = serviceScopeFactory.CreateScope()) { try { logger.LogInformation( "Starting scoped work, provider hash: {hash}.", scope.ServiceProvider.GetHashCode()); var store = scope.ServiceProvider.GetRequiredService<IObjectStore>(); var next = await store.GetNextAsync(); logger.LogInformation("{next}", next); var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>(); await processor.ProcessAsync(next); logger.LogInformation("Processing {name}.", next.Name); var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>(); await relay.RelayAsync(next); logger.LogInformation("Processed results have been relayed."); var marked = await store.MarkAsync(next); logger.LogInformation("Marked as processed: {next}", marked); } finally { logger.LogInformation( "Finished scoped work, provider hash: {hash}.{nl}", scope.ServiceProvider.GetHashCode(), Environment.NewLine); } } } }}In the preceding code, while the app is running, the background service:
From the sample source code, you can see how implementations ofIHostedService can benefit from scoped service lifetimes.
Starting with .NET 8, there's support for service registrations and lookups based on a key, meaning it's possible to register multiple services with a different key, and use this key for the lookup.
For example, consider the case where you have different implementations of the interfaceIMessageWriter:MemoryMessageWriter andQueueMessageWriter.
You can register these services using the overload of the service registration methods (seen earlier) that supports a key as a parameter:
services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");Thekey isn't limited tostring. Thekey can be anyobject you want, as long as the type correctly implementsEquals.
In the constructor of the class that usesIMessageWriter, you add theFromKeyedServicesAttribute to specify the key of the service to resolve:
public class ExampleService{ public ExampleService( [FromKeyedServices("queue")] IMessageWriter writer) { // Omitted for brevity... }}Was this page helpful?
Need help with this topic?
Want to try using Ask Learn to clarify or guide you through this topic?
Was this page helpful?
Want to try using Ask Learn to clarify or guide you through this topic?