本文讨论注册服务组和框架提供的服务。 它还提供有关 .NET 提供的服务注册扩展方法的详细信息。
使用扩展方法注册服务组
.NET 使用约定注册一组相关服务。 约定使用单个 Add{GROUP_NAME} 扩展方法来注册该框架功能所需的所有服务。 例如,AddOptions 扩展方法会注册使用选项所需的所有服务。
框架提供的服务
使用任何可用的主机或应用生成器模式时,会应用默认值,并由框架注册服务。 请考虑一些最常用的主机和应用生成器模式:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
从其中任一 API 创建生成器后, IServiceCollection 框架定义的服务取决于 主机的配置方式。 对于基于 .NET 模板的应用,该框架会注册数百个服务。
下表列出了框架注册的这些服务的一小部分:
| 服务类型 | 辈子 |
|---|---|
| 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 |
注册方法
该框架提供在特定方案中有用的服务注册扩展方法:
| 方法 | 自动对象销毁 | 多个实现 | 传递参数 |
|---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()示例: services.AddSingleton<IMyDep, MyDep>(); |
是的 | 是的 | 否 |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})示例: services.AddSingleton<IMyDep>(sp => new MyDep());services.AddSingleton<IMyDep>(sp => new MyDep(99)); |
是的 | 是的 | 是的 |
Add{LIFETIME}<{IMPLEMENTATION}>()示例: services.AddSingleton<MyDep>(); |
是的 | 否 | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})示例: services.AddSingleton<IMyDep>(new MyDep());services.AddSingleton<IMyDep>(new MyDep(99)); |
否 | 是的 | 是的 |
AddSingleton(new {IMPLEMENTATION})示例: services.AddSingleton(new MyDep());services.AddSingleton(new MyDep(99)); |
否 | 否 | 是的 |
有关类型处置的详细信息,请参阅 服务处置。
仅使用实现类型注册服务等效于使用相同的实现和服务类型注册该服务。 例如,考虑以下代码:
services.AddSingleton<ExampleService>();
这相当于将服务注册到相同类型的服务和实现:
services.AddSingleton<ExampleService, ExampleService>();
这种等效性就是无法使用未采用显式服务类型的方法来注册服务的多个实现的原因。 这些方法可以注册多个 实例 但它们都具有相同的 执行 类型
任何服务注册方法都可用于注册同一服务类型的多个服务实例。 下面的示例以 AddSingleton 作为服务类型调用 IMessageWriter 两次。 第二次对 AddSingleton 的调用在解析为 IMessageWriter 时替代上一次调用,在通过 IEnumerable<IMessageWriter> 解析多个服务时添加到上一次调用。 通过 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();
前面的示例源代码注册了 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);
}
}
ExampleService 定义两个构造函数参数:一个是 IMessageWriter,另一个是 IEnumerable<IMessageWriter>。 单一 IMessageWriter 是要注册的最后一个实现,而 IEnumerable<IMessageWriter> 表示所有已注册的实现。
框架还提供 TryAdd{LIFETIME} 扩展方法,只有当尚未注册某个实现时,才注册该服务。
在下面的示例中,对 AddSingleton 的调用会将 ConsoleMessageWriter 注册为 IMessageWriter的实现。 对 TryAddSingleton 的调用没有任何作用,因为 IMessageWriter 已有一个已注册的实现:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
TryAddSingleton 无效,因为它已被添加,并且“尝试”失败。 断 ExampleService 言以下内容:
public class ExampleService
{
public ExampleService(
IMessageWriter messageWriter,
IEnumerable<IMessageWriter> messageWriters)
{
Trace.Assert(messageWriter is ConsoleMessageWriter);
Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
}
}
有关详细信息,请参见:
TryAddEnumerable(ServiceDescriptor) 方法仅会在没有同一类型实现的情况下才注册该服务。 多个服务通过 IEnumerable<{SERVICE}> 解析。 注册服务时,如果尚未添加同类型的实例,请添加一个实例。 库作者使用 TryAddEnumerable 来避免在容器中注册一个实现的多个副本。
在下面的示例中,对 TryAddEnumerable 的第一次调用会将 MessageWriter 注册为 IMessageWriter1的实现。 第二次调用向 MessageWriter 注册 IMessageWriter2。 第三次调用没有任何作用,因为 IMessageWriter1 已有一个 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>());
服务注册独立于顺序,除非注册同一类型的多个实现。
IServiceCollection 是 ServiceDescriptor 对象的集合。 以下示例演示如何通过创建和添加 ServiceDescriptor 来注册服务:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
typeof(IMessageWriter),
_ => new DefaultMessageWriter(secretKey),
ServiceLifetime.Transient);
services.Add(descriptor);
内置 Add{LIFETIME} 方法使用同一种方式。 相关示例请参阅 AddScoped 源代码。