หมายเหตุ
การเข้าถึงหน้านี้ต้องได้รับการอนุญาต คุณสามารถลอง ลงชื่อเข้าใช้หรือเปลี่ยนไดเรกทอรีได้
การเข้าถึงหน้านี้ต้องได้รับการอนุญาต คุณสามารถลองเปลี่ยนไดเรกทอรีได้
This article discusses registering groups of services and framework-provided services. It also provides details about the service-registration extension methods that .NET provides.
Register groups of services with extension methods
.NET uses a convention for registering a group of related services. The convention is to use a single Add{GROUP_NAME} extension method to register all of the services required by a framework feature. For example, the AddOptions extension method registers all of the services required for using options.
Framework-provided services
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:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
After creating a builder from any of these APIs, the IServiceCollection has services defined by the framework, depending on how 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 |
Registration methods
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 Disposal of services.
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 multiple instances of a service, but they all have the same implementation 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 with IMessageWriter as the service type. The second call to AddSingleton overrides the previous one when resolved as IMessageWriter and adds to the previous one when multiple services are resolved via IEnumerable<IMessageWriter>. Services appear in the order they were registered when resolved via IEnumerable<{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 the IMessageWriter.
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);
}
}
The ExampleService defines two constructor parameters; a single IMessageWriter, and an IEnumerable<IMessageWriter>. The single IMessageWriter is the last implementation to be registered, whereas the IEnumerable<IMessageWriter> represents all registered implementations.
The framework also provides TryAdd{LIFETIME} extension methods, which register the service only if there isn't already an implementation registered.
In the following example, the call to AddSingleton registers ConsoleMessageWriter as an implementation for IMessageWriter. The call to TryAddSingleton has no effect because IMessageWriter already has a registered implementation:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
The TryAddSingleton has no effect, as it was already added and the "try" fails. The ExampleService 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:
The TryAddEnumerable(ServiceDescriptor) methods register the service only if there isn't already an implementation of the same type. Multiple services are resolved via IEnumerable<{SERVICE}>. When registering services, add an instance if one of the same types wasn't already added. Library authors use TryAddEnumerable to avoid registering multiple copies of an implementation in the container.
In the following example, the first call to TryAddEnumerable registers MessageWriter as an implementation for IMessageWriter1. The second call registers MessageWriter for IMessageWriter2. The third call has no effect because IMessageWriter1 already has a registered implementation of MessageWriter:
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 of ServiceDescriptor objects. The following example shows how to register a service by creating and adding a ServiceDescriptor:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
The built-in Add{LIFETIME} methods use the same approach. For example, see the AddScoped source code.