- 在 ASP.NET Core 依赖注入Dependency injection in ASP.NET Core
- 依赖关系注入概述Overview of dependency injection
- 注入启动的服务Services injected into Startup
- 框架提供的服务Framework-provided services
- 使用扩展方法注册附加服务Register additional services with extension methods
- 服务生存期Service lifetimes
- 服务注册方法Service registration methods
- 实体框架上下文Entity Framework contexts
- 生存期和注册选项Lifetime and registration options
- 从 main 调用服务Call services from main
- 作用域验证Scope validation
- 请求服务Request Services
- 设计能够进行依赖关系注入的服务Design services for dependency injection
- 默认服务容器替换Default service container replacement
- 建议Recommendations
- 其他资源Additional resources
在 ASP.NET Core 依赖注入Dependency injection in ASP.NET Core
本文内容
作者:Steve Smith 和 Scott Addie
ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术。
有关特定于 MVC 控制器中依赖关系注入的详细信息,请参阅 在 ASP.NET Core 中将依赖项注入到控制器。
依赖关系注入概述Overview of dependency injection
依赖项 是另一个对象所需的任何对象。使用应用中其他类所依赖的 WriteMessage
方法检查以下 MyDependency
类:
public class MyDependency
{
public MyDependency()
{
}
public Task WriteMessage(string message)
{
Console.WriteLine(
$"MyDependency.WriteMessage called. Message: {message}");
return Task.FromResult(0);
}
}
可以创建 MyDependency
类的实例以使 WriteMessage
方法可用于类。MyDependency
类是 IndexModel
类的依赖项:
public class IndexModel : PageModel
{
MyDependency _dependency = new MyDependency();
public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
该类创建并直接依赖于 MyDependency
实例。代码依赖关系(如前面的示例)存在问题,应该避免使用,原因如下:
- 要用不同的实现替换
MyDependency
,必须修改类。 - 如果
MyDependency
具有依赖关系,则必须由类对其进行配置。在具有多个依赖于MyDependency
的类的大型项目中,配置代码在整个应用中会变得分散。 - 这种实现很难进行单元测试。应用应使用模拟或存根
MyDependency
类,该类不能使用此方法。
依赖关系注入通过以下方式解决了这些问题:
- 使用接口或基类抽象化依赖关系实现。
- 注册服务容器中的依赖关系。ASP.NET Core 提供了一个内置的服务容器 IServiceProvider。服务已在应用的
Startup.ConfigureServices
方法中注册。 - 将服务注入 到使用它的类的构造函数中。框架负责创建依赖关系的实例,并在不再需要时对其进行处理。
在示例应用中,IMyDependency
接口定义了服务为应用提供的方法:
public interface IMyDependency
{
Task WriteMessage(string message);
}
public interface IMyDependency
{
Task WriteMessage(string message);
}
此接口由具体类型 MyDependency
实现:
public class MyDependency : IMyDependency
{
private readonly ILogger<MyDependency> _logger;
public MyDependency(ILogger<MyDependency> logger)
{
_logger = logger;
}
public Task WriteMessage(string message)
{
_logger.LogInformation(
"MyDependency.WriteMessage called. Message: {MESSAGE}",
message);
return Task.FromResult(0);
}
}
public class MyDependency : IMyDependency
{
private readonly ILogger<MyDependency> _logger;
public MyDependency(ILogger<MyDependency> logger)
{
_logger = logger;
}
public Task WriteMessage(string message)
{
_logger.LogInformation(
"MyDependency.WriteMessage called. Message: {Message}",
message);
return Task.FromResult(0);
}
}
MyDependency
在其构造函数中请求一个 ILogger<TCategoryName>。以链式方式使用依赖关系注入并不罕见。每个请求的依赖关系相应地请求其自己的依赖关系。容器解析图中的依赖关系并返回完全解析的服务。必须被解析的依赖关系的集合通常被称为“依赖关系树” 、“依赖关系图” 或“对象图” 。
必须在服务容器中注册 IMyDependency
和 ILogger<TCategoryName>
。IMyDependency
已在 Startup.ConfigureServices
中注册。ILogger<TCategoryName>
由日志记录抽象基础结构注册,因此它是框架默认注册的框架提供的服务。
容器通过利用(泛型)开放类型解析 ILogger<TCategoryName>
,而无需注册每个(泛型)构造类型:
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
在示例应用中,使用具体类型 MyDependency
注册 IMyDependency
服务。注册将服务生存期的范围限定为单个请求的生存期。本主题后面将介绍服务生存期。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
// OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
// OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
备注
每个 services.Add{SERVICE_NAME}
扩展方法添加(并可能配置)服务。例如,services.AddMvc()
添加 Razor Pages 和 MVC 需要的服务。我们建议应用遵循此约定。将扩展方法置于 Microsoft.Extensions.DependencyInjection 命名空间中以封装服务注册的组。
如果服务的构造函数需要内置类型(如 string
),则可以使用配置或选项模式注入该类型:
public class MyDependency : IMyDependency
{
public MyDependency(IConfiguration config)
{
var myStringValue = config["MyStringKey"];
// Use myStringValue
}
...
}
通过使用服务并分配给私有字段的类的构造函数请求服务的实例。该字段用于在整个类中根据需要访问服务。
在示例应用中,请求 IMyDependency
实例并用于调用服务的 WriteMessage
方法:
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
注入启动的服务Services injected into Startup
使用泛型主机 (IHostBuilder) 时,只能将以下服务类型注入 Startup
构造函数:
IWebHostEnvironment
- IHostEnvironment
- IConfiguration
服务可以注入 Startup.Configure
:
public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
{
...
}
有关详细信息,请参阅 ASP.NET Core 中的应用启动。
框架提供的服务Framework-provided services
Startup.ConfigureServices
方法负责定义应用使用的服务,包括 Entity Framework Core 和 ASP.NET Core MVC 等平台功能。最初,提供给 ConfigureServices
的 IServiceCollection
具有框架定义的服务(具体取决于主机配置方式)。基于 ASP.NET Core 模板的应用程序具有框架注册的数百个服务的情况并不少见。下表列出了框架注册的服务的一个小示例。
使用扩展方法注册附加服务Register additional services with extension methods
当服务集合扩展方法可用于注册服务(及其依赖服务,如果需要)时,约定使用单个 Add{SERVICE_NAME}
扩展方法来注册该服务所需的所有服务。以下代码是如何使用扩展方法 AddDbContext<TContext> 和 AddIdentityCore 向容器添加附加服务的示例:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
...
}
有关详细信息,请参阅 API 文档中的 ServiceCollection 类。
服务生存期Service lifetimes
为每个注册的服务选择适当的生存期。可以使用以下生存期配置 ASP.NET Core 服务:
暂时Transient
暂时生存期服务 (AddTransient) 是每次从服务容器进行请求时创建的。这种生存期适合轻量级、 无状态的服务。
范围内Scoped
作用域生存期服务 (AddScoped) 以每个客户端请求(连接)一次的方式创建。
警告
在中间件内使用有作用域的服务时,请将该服务注入至 Invoke
或 InvokeAsync
方法。请不要通过构造函数注入进行注入,因为它会强制服务的行为与单一实例类似。有关详细信息,请参阅 写入自定义 ASP.NET Core 中间件。
单例Singleton
单一实例生存期服务 (AddSingleton) 是在第一次请求时(或者在运行 Startup.ConfigureServices
并且使用服务注册指定实例时)创建的。每个后续请求都使用相同的实例。如果应用需要单一实例行为,建议允许服务容器管理服务的生存期。不要实现单一实例设计模式并提供用户代码来管理对象在类中的生存期。
警告
从单一实例解析有作用域的服务很危险。当处理后续请求时,它可能会导致服务处于不正确的状态。
服务注册方法Service registration methods
服务注册扩展方法提供适用于特定场景的重载。
方法 | 自动对象 (object)处置 | 多种实现 | 传递参数 |
---|---|---|---|
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("A string!")); | 是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>() 示例:services.AddSingleton<MyDep>(); | 是 | 否 | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) 示例:services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep("A string!")); | 否 | 是 | 是 |
AddSingleton(new {IMPLEMENTATION}) 示例:services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep("A string!")); | 否 | 否 | 是 |
要详细了解类型处置,请参阅服务处置部分。多个实现的常见场景是为测试模拟类型。
TryAdd{LIFETIME}
方法仅当尚未注册实现时,注册该服务。
在以下示例中,第一行向 IMyDependency
注册 MyDependency
。第二行没有任何作用,因为 IMyDependency
已有一个已注册的实现:
services.AddSingleton<IMyDependency, MyDependency>();
// The following line has no effect:
services.TryAddSingleton<IMyDependency, DifferentDependency>();
有关详细信息,请参见:
TryAddEnumerable(ServiceDescriptor) 方法仅当没有同一类型的实现时,注册该服务。 多个服务通过 IEnumerable<{SERVICE}>
解析。注册服务时,开发人员只希望在尚未添加一个相同类型时添加实例。通常情况下,库创建者使用此方法来避免在容器中注册一个实例的两个副本。
在以下示例中,第一行向 IMyDep1
注册 MyDep
。第二行向 IMyDep2
注册 MyDep
。第三行没有任何作用,因为 IMyDep1
已有一个 MyDep
的已注册的实现:
public interface IMyDep1 {}
public interface IMyDep2 {}
public class MyDep : IMyDep1, IMyDep2 {}
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep2, MyDep>());
// Two registrations of MyDep for IMyDep1 is avoided by the following line:
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyDep1, MyDep>());
构造函数注入行为Constructor injection behavior
服务可以通过两种机制来解析:
- IServiceProvider
- ActivatorUtilities – 允许在依赖关系注入容器中创建没有服务注册的对象。
ActivatorUtilities
用于面向用户的抽象,例如标记帮助器、MVC 控制器和模型绑定器。
构造函数可以接受依赖关系注入不提供的参数,但参数必须分配默认值。
当服务由 IServiceProvider
或 ActivatorUtilities
解析时,构造函数注入需要 public 构造函数。
当服务由 ActivatorUtilities
解析时,构造函数注入要求只存在一个适用的构造函数。支持构造函数重载,但其参数可以全部通过依赖注入来实现的重载只能存在一个。
实体框架上下文Entity Framework contexts
通常使用设置了范围的生存期将实体框架上下文添加到服务容器中,因为 Web 应用数据库操作通常将范围设置为客户端请求。如果在注册数据库上下文时,AddDbContext<TContext> 重载未指定生存期,则设置默认生存期范围。给定生存期的服务不应使用生存期比服务短的数据库上下文。
生存期和注册选项Lifetime and registration options
为了演示生存期和注册选项之间的差异,请考虑以下接口,将任务表示为具有唯一标识符 OperationId
的操作。根据为以下接口配置操作服务的生存期的方式,容器在类请求时提供相同或不同的服务实例:
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
接口在 Operation
类中实现。Operation
构造函数将生成一个 GUID(如果未提供):
public class Operation : IOperationTransient,
IOperationScoped,
IOperationSingleton,
IOperationSingletonInstance
{
public Operation() : this(Guid.NewGuid())
{
}
public Operation(Guid id)
{
OperationId = id;
}
public Guid OperationId { get; private set; }
}
public class Operation : IOperationTransient,
IOperationScoped,
IOperationSingleton,
IOperationSingletonInstance
{
public Operation() : this(Guid.NewGuid())
{
}
public Operation(Guid id)
{
OperationId = id;
}
public Guid OperationId { get; private set; }
}
注册 OperationService
取决于,每个其他 Operation
类型。当通过依赖关系注入请求 OperationService
时,它将接收每个服务的新实例或基于从属服务的生存期的现有实例。
- 如果从容器请求时创建了临时服务,则
IOperationTransient
服务的OperationId
与OperationService
的OperationId
不同。OperationService
将接收IOperationTransient
类的新实例。新实例将生成一个不同的OperationId
。 - 如果按客户端请求创建有作用域的服务,则
IOperationScoped
服务的OperationId
与客户端请求中OperationService
的该 ID 相同。在客户端请求中,两个服务共享不同的OperationId
值。 - 如果单一数据库和单一实例服务只创建一次并在所有客户端请求和所有服务中使用,则
OperationId
在所有服务请求中保持不变。
public class OperationService
{
public OperationService(
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
}
public class OperationService
{
public OperationService(
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
}
在 Startup.ConfigureServices
中,根据其指定的生存期,将每个类型添加到容器中:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
// OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
// OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
IOperationSingletonInstance
服务正在使用已知 ID 为 Guid.Empty
的特定实例。此类型在使用时很明显(其 GUID 全部为零)。
示例应用演示了各个请求中和之间的对象生存期。示例应用的 IndexModel
请求每种 IOperation
类型和 OperationService
。然后,页面通过属性分配显示所有页面模型类和服务的 OperationId
值:
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency;
public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
}
public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
以下两个输出显示了两个请求的结果:
第一个请求:
控制器操作:
暂时性:d233e165-f417-469b-a866-1cf1935d2518作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9实例:00000000-0000-0000-0000-000000000000
OperationService
操作:
暂时性:c6b049eb-1318-4e31-90f1-eb2dd849ff64作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9实例:00000000-0000-0000-0000-000000000000
第二个请求:
控制器操作:
暂时性:b63bd538-0a37-4ff1-90ba-081c5138dda0作用域:31e820c5-4834-4d22-83fc-a60118acb9f4单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9实例:00000000-0000-0000-0000-000000000000
OperationService
操作:
暂时性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf作用域:31e820c5-4834-4d22-83fc-a60118acb9f4单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9实例:00000000-0000-0000-0000-000000000000
观察哪个 OperationId
值会在一个请求之内和不同请求之间变化:
- 暂时性 对象始终不同。第一个和第二个客户端请求的暂时性
OperationId
值对于OperationService
操作和在客户端请求内都是不同的。为每个服务请求和客户端请求提供了一个新实例。 - 作用域 对象在一个客户端请求中是相同的,但在多个客户端请求中是不同的。
- 单一实例 对象对每个对象和每个请求都是相同的(不管
Startup.ConfigureServices
中是否提供Operation
实例)。
从 main 调用服务Call services from main
使用 IServiceScopeFactory.CreateScope 创建 IServiceScope 以解析应用范围内的已设置范围的服务。此方法可以用于在启动时访问有作用域的服务以便运行初始化任务。以下示例演示如何在 Program.Main
中获取 MyScopedService
的上下文:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var serviceContext = services.GetRequiredService<MyScopedService>();
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
public class Program
{
public static async Task Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var serviceContext = services.GetRequiredService<MyScopedService>();
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
作用域验证Scope validation
如果应用正在开发环境中运行,并调用 CreateDefaultBuilder 生成主机,默认服务提供程序会执行检查,以确认以下内容:
如果应用正在开发环境中运行,并调用 CreateDefaultBuilder 生成主机,默认服务提供程序会执行检查,以确认以下内容:
- 没有从根服务提供程序直接或间接解析到有作用域的服务。
- 未将有作用域的服务直接或间接注入到单一实例。
调用 BuildServiceProvider 时创建根服务提供程序。在启动提供程序和应用时,根服务提供程序的生存期对应于应用/服务的生存期,并在关闭应用时释放。
有作用域的服务由创建它们的容器释放。如果作用域创建于根容器,则该服务的生存会有效地提升至单一实例,因为根容器只会在应用/服务关闭时将其释放。验证服务作用域,将在调用 BuildServiceProvider
时收集这类情况。
有关详细信息,请参阅 ASP.NET Core Web 主机。
请求服务Request Services
来自 HttpContext
的 ASP.NET Core 请求中可用的服务通过 HttpContext.RequestServices 集合公开。
请求服务表示作为应用的一部分配置和请求的服务。当对象指定依赖关系时,RequestServices
(而不是 ApplicationServices
)中的类型将满足这些要求。
通常,应用不应直接使用这些属性。相反,通过类构造函数请求类所需的类型,并允许框架注入依赖关系。这样生成的类更易于测试。
备注
与访问 RequestServices
集合相比,以构造函数参数的形式请求依赖项是更优先的选择。
设计能够进行依赖关系注入的服务Design services for dependency injection
最佳做法是:
- 设计服务以使用依赖关系注入来获取其依赖关系。
- 避免有状态的、静态类和成员。将应用设计为改用单一实例服务,可避免创建全局状态。
- 避免在服务中直接实例化依赖类。直接实例化将代码耦合到特定实现。
- 不在应用类中包含过多内容,确保设计规范,并易于测试。
如果一个类似乎有过多的注入依赖关系,这通常表明该类拥有过多的责任并且违反了单一责任原则 (SRP)。尝试通过将某些职责移动到一个新类来重构类。请记住,Razor Pages 页模型类和 MVC 控制器类应关注用户界面问题。业务规则和数据访问实现细节应保留在适用于这些分离的关注点的类中。
服务处理Disposal of services
容器为其创建的 IDisposable 类型调用 Dispose。如果通过用户代码将实例添加到容器中,则不会自动处理该实例。
// Services that implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}
public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}
public void ConfigureServices(IServiceCollection services)
{
// The container creates the following instances and disposes them automatically:
services.AddScoped<Service1>();
services.AddSingleton<Service2>();
services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());
// The container doesn't create the following instances, so it doesn't dispose of
// the instances automatically:
services.AddSingleton<Service3>(new Service3());
services.AddSingleton(new Service3());
}
默认服务容器替换Default service container replacement
内置的服务容器旨在满足框架和大多数消费者应用的需求。我们建议使用内置容器,除非你需要的特定功能不受内置容器支持,例如:
- 属性注入
- 基于名称的注入
- 子容器
- 自定义生存期管理
- 对迟缓初始化的
Func<T>
支持 - 基于约定的注册
以下第三方容器可用于 ASP.NET Core 应用:
线程安全Thread safety
创建线程安全的单一实例服务。如果单例服务依赖于一个瞬时服务,那么瞬时服务可能也需要线程安全,具体取决于单例使用它的方式。
单个服务的工厂方法(例如 AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>) 的第二个参数)不必是线程安全的。像类型 (static
) 构造函数一样,它保证由单个线程调用一次。
建议Recommendations
不支持基于
async/await
和Task
的服务解析。C# 不支持异步构造函数;因此建议模式是在同步解析服务后使用异步方法。避免在服务容器中直接存储数据和配置。例如,用户的购物车通常不应添加到服务容器中。配置应使用 选项模型。同样,避免"数据持有者"对象,也就是仅仅为实现对某些其他对象的访问而存在的对象。最好通过 DI 请求实际项目。
避免静态访问服务(例如,静态键入 IApplicationBuilder.ApplicationServices 以便在其他地方使用)。
避免使用服务定位器模式 。例如,可以改用 DI 时,不要调用 GetService 来获取服务实例:
不正确:
public class MyClass()
{
public void MyMethod()
{
var optionsMonitor =
_services.GetService<IOptionsMonitor<MyOptions>>();
var option = optionsMonitor.CurrentValue.Option;
...
}
}
正确:
public class MyClass
{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
}
public void MyMethod()
{
var option = _optionsMonitor.CurrentValue.Option;
...
}
}
要避免的另一个服务定位器变体是注入可在运行时解析依赖项的工厂。这两种做法混合了控制反转策略。
避免静态访问
HttpContext
(例如,IHttpContextAccessor.HttpContext)。
像任何一组建议一样,你可能会遇到需要忽略某建议的情况。例外情况很少见 — 主要是框架本身内部的特殊情况。
DI 是静态/全局对象访问模式的替代方法 。如果将其与静态对象访问混合使用,则可能无法实现 DI 的优点。