在 ASP.NET Core 依赖注入Dependency injection in ASP.NET Core

作者:Steve SmithScott AddieLuke LathamBy Steve Smith, Scott Addie, and Luke Latham

ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术。ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

有关特定于 MVC 控制器中依赖关系注入的详细信息,请参阅 在 ASP.NET Core 中将依赖项注入到控制器For more information specific to dependency injection within MVC controllers, see 在 ASP.NET Core 中将依赖项注入到控制器.

查看或下载示例代码如何下载View or download sample code (how to download)

依赖关系注入概述Overview of dependency injection

依赖项 是另一个对象所需的任何对象。A dependency is any object that another object requires. 使用应用中其他类所依赖的 WriteMessage 方法检查以下 MyDependency 类:Examine the following MyDependency class with a WriteMessage method that other classes in an app depend upon:

public class MyDependency
{
    public MyDependency()
    {
    }

    public Task WriteMessage(string message)
    {
        Console.WriteLine(
            $"MyDependency.WriteMessage called. Message: {message}");

        return Task.FromResult(0);
    }
}

可以创建 MyDependency 类的实例以使 WriteMessage 方法可用于类。An instance of the MyDependency class can be created to make the WriteMessage method available to a class. MyDependency 类是 IndexModel 类的依赖项:The MyDependency class is a dependency of the IndexModel class:

public class IndexModel : PageModel
{
    MyDependency _dependency = new MyDependency();

    public async Task OnGetAsync()
    {
        await _dependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

该类创建并直接依赖于 MyDependency 实例。The class creates and directly depends on the MyDependency instance. 代码依赖关系(如前面的示例)存在问题,应该避免使用,原因如下:Code dependencies (such as the previous example) are problematic and should be avoided for the following reasons:

  • 要用不同的实现替换 MyDependency,必须修改类。To replace MyDependency with a different implementation, the class must be modified.
  • 如果 MyDependency 具有依赖关系,则必须由类对其进行配置。If MyDependency has dependencies, they must be configured by the class. 在具有多个依赖于 MyDependency 的类的大型项目中,配置代码在整个应用中会变得分散。In a large project with multiple classes depending on MyDependency, the configuration code becomes scattered across the app.
  • 这种实现很难进行单元测试。This implementation is difficult to unit test. 应用应使用模拟或存根 MyDependency 类,该类不能使用此方法。The app should use a mock or stub MyDependency class, which isn't possible with this approach.

依赖关系注入通过以下方式解决了这些问题:Dependency injection addresses these problems through:

  • 使用接口或基类抽象化依赖关系实现。The use of an interface or base class to abstract the dependency implementation.
  • 注册服务容器中的依赖关系。Registration of the dependency in a service container. ASP.NET Core 提供了一个内置的服务容器 IServiceProviderASP.NET Core provides a built-in service container, IServiceProvider. 服务已在应用的 Startup.ConfigureServices 方法中注册。Services are registered in the app's Startup.ConfigureServices method.
  • 将服务注入 到使用它的类的构造函数中。Injection of the service into the constructor of the class where it's used. 框架负责创建依赖关系的实例,并在不再需要时对其进行处理。The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

示例应用中,IMyDependency 接口定义了服务为应用提供的方法:In the sample app, the IMyDependency interface defines a method that the service provides to the app:

public interface IMyDependency
{
    Task WriteMessage(string message);
}

此接口由具体类型 MyDependency 实现:This interface is implemented by a concrete type, 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);
    }
}

MyDependency 在其构造函数中请求一个 ILogger<TCategoryName>MyDependency requests an ILogger<TCategoryName> in its constructor. 以链式方式使用依赖关系注入并不罕见。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 referred to as a dependency tree, dependency graph, or object graph.

必须在服务容器中注册 IMyDependencyILogger<TCategoryName>IMyDependency and ILogger<TCategoryName> must be registered in the service container. IMyDependency 已在 Startup.ConfigureServices 中注册。IMyDependency is registered in Startup.ConfigureServices. ILogger<TCategoryName> 由日志记录抽象基础结构注册,因此它是框架默认注册的框架提供的服务ILogger<TCategoryName> is registered by the logging abstractions infrastructure, so it's a framework-provided service registered by default by the framework.

容器通过利用(泛型)开放类型解析 ILogger<TCategoryName>,而无需注册每个(泛型)构造类型The container resolves ILogger<TCategoryName> by taking advantage of (generic) open types, eliminating the need to register every (generic) constructed type:

services.AddSingleton(typeof(ILogger<T>), typeof(Logger<T>));

在示例应用中,使用具体类型 MyDependency 注册 IMyDependency 服务。In the sample app, the IMyDependency service is registered with the concrete type MyDependency. 注册将服务生存期的范围限定为单个请求的生存期。The registration scopes the service lifetime to the lifetime of a single request. 本主题后面将介绍服务生存期Service lifetimes are described later in this topic.

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} 扩展方法添加(并可能配置)服务。Each services.Add{SERVICE_NAME} extension method adds (and potentially configures) services. 例如,services.AddMvc() 添加 Razor Pages 和 MVC 需要的服务。For example, services.AddMvc() adds the services Razor Pages and MVC require. 我们建议应用遵循此约定。We recommended that apps follow this convention. 将扩展方法置于 Microsoft.Extensions.DependencyInjection 命名空间中以封装服务注册的组。Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations.

如果服务的构造函数需要内置类型(如 string),则可以使用配置选项模式注入该类型:If the service's constructor requires a built in type, such as a string, the type can be injected by using configuration or the options pattern:

public class MyDependency : IMyDependency
{
    public MyDependency(IConfiguration config)
    {
        var myStringValue = config["MyStringKey"];

        // Use myStringValue
    }

    ...
}

通过使用服务并分配给私有字段的类的构造函数请求服务的实例。An instance of the service is requested via the constructor of a class where the service is used and assigned to a private field. 该字段用于在整个类中根据需要访问服务。The field is used to access the service as necessary throughout the class.

在示例应用中,请求 IMyDependency 实例并用于调用服务的 WriteMessage 方法:In the sample app, the IMyDependency instance is requested and used to call the service's WriteMessage method:

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.");
    }
}

框架提供的服务Framework-provided services

Startup.ConfigureServices 方法负责定义应用使用的服务,包括 Entity Framework Core 和 ASP.NET Core MVC 等平台功能。The Startup.ConfigureServices method is responsible for defining the services the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. 最初,提供给 ConfigureServicesIServiceCollection 定义了以下服务(具体取决于配置主机的方式):Initially, the IServiceCollection provided to ConfigureServices has the following services defined (depending on how the host was configured):

服务类型Service Type 生存期Lifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 暂时Transient
Microsoft.AspNetCore.Hosting.IApplicationLifetime 单例Singleton
Microsoft.AspNetCore.Hosting.IHostingEnvironment 单例Singleton
Microsoft.AspNetCore.Hosting.IStartup 单例Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter 暂时Transient
Microsoft.AspNetCore.Hosting.Server.IServer 单例Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory 暂时Transient
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.DiagnosticSource 单例Singleton
System.Diagnostics.DiagnosticListener 单例Singleton

当服务集合扩展方法可用于注册服务(及其依赖服务,如果需要)时,约定使用单个 Add{SERVICE_NAME} 扩展方法来注册该服务所需的所有服务。When a service collection extension method is available to register a service (and its dependent services, if required), the convention is to use a single Add{SERVICE_NAME} extension method to register all of the services required by that service. 以下代码是如何使用扩展方法 AddDbContext<TContext>AddIdentityCoreAddMvc 向容器添加其他服务的示例:The following code is an example of how to add additional services to the container using the extension methods AddDbContext<TContext>, AddIdentityCore, and AddMvc:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();
}

有关详细信息,请参阅 API 文档中的 ServiceCollection 类。For more information, see the ServiceCollection class in the API documentation.

服务生存期Service lifetimes

为每个注册的服务选择适当的生存期。Choose an appropriate lifetime for each registered service. 可以使用以下生存期配置 ASP.NET Core 服务:ASP.NET Core services can be configured with the following lifetimes:

暂时Transient

暂时生存期服务 (AddTransient) 是每次从服务容器进行请求时创建的。Transient lifetime services (AddTransient) are created each time they're requested from the service container. 这种生存期适合轻量级、 无状态的服务。This lifetime works best for lightweight, stateless services.

范围内Scoped

作用域生存期服务 (AddScoped) 以每个客户端请求(连接)一次的方式创建。Scoped lifetime services (AddScoped) are created once per client request (connection).

警告

在中间件内使用有作用域的服务时,请将该服务注入至 InvokeInvokeAsync 方法。When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. 请不要通过构造函数注入进行注入,因为它会强制服务的行为与单一实例类似。Don't inject via constructor injection because it forces the service to behave like a singleton. 有关详细信息,请参阅 写入自定义 ASP.NET Core 中间件For more information, see 写入自定义 ASP.NET Core 中间件.

单例Singleton

单一实例生存期服务 (AddSingleton) 是在第一次请求时(或者在运行 Startup.ConfigureServices 并且使用服务注册指定实例时)创建的。Singleton lifetime services (AddSingleton) are created the first time they're requested (or when Startup.ConfigureServices is run and an instance is specified with the service registration). 每个后续请求都使用相同的实例。Every subsequent request uses the same instance. 如果应用需要单一实例行为,建议允许服务容器管理服务的生存期。If the app requires singleton behavior, allowing the service container to manage the service's lifetime is recommended. 不要实现单一实例设计模式并提供用户代码来管理对象在类中的生存期。Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.

警告

从单一实例解析有作用域的服务很危险。It's dangerous to resolve a scoped service from a singleton. 当处理后续请求时,它可能会导致服务处于不正确的状态。It may cause the service to have incorrect state when processing subsequent requests.

服务注册方法Service registration methods

每个服务注册扩展方法提供适用于特定场景的重载。Each service registration extension method offers overloads that are useful in specific scenarios.

方法Method 自动Automatic
对象 (object)object
处置disposal
多个Multiple
实现implementations
传递参数Pass args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
示例:Example:
services.AddScoped<IMyDep, MyDep>();
Yes Yes NoNo
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
示例:Examples:
services.AddScoped<IMyDep>(sp => new MyDep());
services.AddScoped<IMyDep>(sp => new MyDep("A string!"));
Yes Yes Yes
Add{LIFETIME}<{IMPLEMENTATION}>()
示例:Example:
services.AddScoped<MyDep>();
Yes No NoNo
Add{LIFETIME}<{SERVICE}>(new {IMPLEMENTATION})
示例:Examples:
services.AddScoped<IMyDep>(new MyDep());
services.AddScoped<IMyDep>(new MyDep("A string!"));
NoNo Yes Yes
Add{LIFETIME}(new {IMPLEMENTATION})
示例:Examples:
services.AddScoped(new MyDep());
services.AddScoped(new MyDep("A string!"));
NoNo No Yes

要详细了解类型处置,请参阅服务处置部分。For more information on type disposal, see the Disposal of services section. 多个实现的常见场景是为测试模拟类型A common scenario for multiple implementations is mocking types for testing.

TryAdd{LIFETIME} 方法仅当尚未注册实现时,注册该服务。TryAdd{LIFETIME} methods only register the service if there isn't already an implementation registered.

在以下示例中,第一行向 IMyDependency 注册 MyDependencyIn the following example, the first line registers MyDependency for IMyDependency. 第二行没有任何作用,因为 IMyDependency 已有一个已注册的实现:The second line has no effect because IMyDependency already has a registered implementation:

services.AddSingleton<IMyDependency, MyDependency>();
// The following line has no effect:
services.TryAddSingleton<IMyDependency, DifferentDependency>();

有关详细信息,请参见:For more information, see:

TryAddEnumerable(ServiceDescriptor) 方法仅当没有同一类型的实现时,注册该服务。 .TryAddEnumerable(ServiceDescriptor) methods only register the service if there isn't already an implementation of the same type. 多个服务通过 IEnumerable<{SERVICE}> 解析。Multiple services are resolved via IEnumerable<{SERVICE}>. 注册服务时,开发人员只希望在尚未添加一个相同类型时添加实例。When registering services, the developer only wants to add an instance if one of the same type hasn't already been added. 通常情况下,库创建者使用此方法来避免在容器中注册一个实例的两个副本。Generally, this method is used by library authors to avoid registering two copies of an instance in the container.

在以下示例中,第一行向 IMyDep1 注册 MyDepIn the following example, the first line registers MyDep for IMyDep1. 第二行向 IMyDep2 注册 MyDepThe second line registers MyDep for IMyDep2. 第三行没有任何作用,因为 IMyDep1 已有一个 MyDep 的已注册的实现:The third line has no effect because IMyDep1 already has a registered implementation of 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

服务可以通过两种机制来解析:Services can be resolved by two mechanisms:

  • IServiceProvider
  • ActivatorUtilities – 允许在依赖关系注入容器中创建没有服务注册的对象。ActivatorUtilities – Permits object creation without service registration in the dependency injection container. ActivatorUtilities 用于面向用户的抽象,例如标记帮助器、MVC 控制器和模型绑定器。ActivatorUtilities is used with user-facing abstractions, such as Tag Helpers, MVC controllers, and model binders.

构造函数可以接受依赖关系注入不提供的参数,但参数必须分配默认值。Constructors can accept arguments that aren't provided by dependency injection, but the arguments must assign default values.

当服务由 IServiceProviderActivatorUtilities 解析时,构造函数注入需要 public 构造函数。When services are resolved by IServiceProvider or ActivatorUtilities, constructor injection requires a public constructor.

当服务由 ActivatorUtilities 解析时,构造函数注入要求只存在一个适用的构造函数。When services are resolved by ActivatorUtilities, 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.

实体框架上下文Entity Framework contexts

通常使用设置了范围的生存期将实体框架上下文添加到服务容器中,因为 Web 应用数据库操作通常将范围设置为客户端请求。Entity Framework contexts are usually added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. 如果在注册数据库上下文时,AddDbContext<TContext> 重载未指定生存期,则设置默认生存期范围。The default lifetime is scoped if a lifetime isn't specified by an AddDbContext<TContext> overload when registering the database context. 给定生存期的服务不应使用生存期比服务短的数据库上下文。Services of a given lifetime shouldn't use a database context with a shorter lifetime than the service.

生存期和注册选项Lifetime and registration options

为了演示生存期和注册选项之间的差异,请考虑以下接口,将任务表示为具有唯一标识符 OperationId 的操作。To demonstrate the difference between the lifetime and registration options, consider the following interfaces that represent tasks as an operation with a unique identifier, OperationId. 根据为以下接口配置操作服务的生存期的方式,容器在类请求时提供相同或不同的服务实例:Depending on how the lifetime of an operations service is configured for the following interfaces, the container provides either the same or a different instance of the service when requested by a class:

public interface IOperation
{
    Guid OperationId { get; }
}

public interface IOperationTransient : IOperation
{
}

public interface IOperationScoped : IOperation
{
}

public interface IOperationSingleton : IOperation
{
}

public interface IOperationSingletonInstance : IOperation
{
}

接口在 Operation 类中实现。The interfaces are implemented in the Operation class. Operation 构造函数将生成一个 GUID(如果未提供):The Operation constructor generates a GUID if one isn't supplied:

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 类型。An OperationService is registered that depends on each of the other Operation types. 当通过依赖关系注入请求 OperationService 时,它将接收每个服务的新实例或基于从属服务的生存期的现有实例。When OperationService is requested via dependency injection, it receives either a new instance of each service or an existing instance based on the lifetime of the dependent service.

  • 如果从容器请求时创建了临时服务,则 IOperationTransient 服务的 OperationIdOperationServiceOperationId 不同。When transient services are created when requested from the container, the OperationId of the IOperationTransient service is different than the OperationId of the OperationService. OperationService 将接收 IOperationTransient 类的新实例。OperationService receives a new instance of the IOperationTransient class. 新实例将生成一个不同的 OperationIdThe new instance yields a different OperationId.
  • 如果按客户端请求创建有作用域的服务,则 IOperationScoped 服务的 OperationId 与客户端请求中 OperationService 的该 ID 相同。When scoped services are created per client request, the OperationId of the IOperationScoped service is the same as that of OperationService within a client request. 在客户端请求中,两个服务共享不同的 OperationId 值。Across client requests, both services share a different OperationId value.
  • 如果单一数据库和单一实例服务只创建一次并在所有客户端请求和所有服务中使用,则 OperationId 在所有服务请求中保持不变。When singleton and singleton-instance services are created once and used across all client requests and all services, the OperationId is constant across all service requests.
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 中,根据其指定的生存期,将每个类型添加到容器中:In Startup.ConfigureServices, each type is added to the container according to its named lifetime:

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 的特定实例。The IOperationSingletonInstance service is using a specific instance with a known ID of Guid.Empty. 此类型在使用时很明显(其 GUID 全部为零)。It's clear when this type is in use (its GUID is all zeroes).

示例应用演示了各个请求中和之间的对象生存期。The sample app demonstrates object lifetimes within and between individual requests. 示例应用的 IndexModel 请求每种 IOperation 类型和 OperationServiceThe sample app's IndexModel requests each kind of IOperation type and the OperationService. 然后,页面通过属性分配显示所有页面模型类和服务的 OperationId 值:The page then displays all of the page model class's and service's OperationId values through property assignments:

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.");
    }
}

以下两个输出显示了两个请求的结果:Two following output shows the results of two requests:

第一个请求:First request:

控制器操作:Controller operations:

暂时性:d233e165-f417-469b-a866-1cf1935d2518Transient: d233e165-f417-469b-a866-1cf1935d2518
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

OperationService 操作:OperationService operations:

暂时性:c6b049eb-1318-4e31-90f1-eb2dd849ff64Transient: c6b049eb-1318-4e31-90f1-eb2dd849ff64
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

第二个请求:Second request:

控制器操作:Controller operations:

暂时性:b63bd538-0a37-4ff1-90ba-081c5138dda0Transient: b63bd538-0a37-4ff1-90ba-081c5138dda0
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

OperationService 操作:OperationService operations:

暂时性:c4cbacb8-36a2-436d-81c8-8c1b78808aafTransient: c4cbacb8-36a2-436d-81c8-8c1b78808aaf
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000Instance: 00000000-0000-0000-0000-000000000000

观察哪个 OperationId 值会在一个请求之内和不同请求之间变化:Observe which of the OperationId values vary within a request and between requests:

  • 暂时性 对象始终不同。Transient objects are always different. 第一个和第二个客户端请求的暂时性 OperationId 值对于 OperationService 操作和在客户端请求内都是不同的。The transient OperationId value for both the first and second client requests are different for both OperationService operations and across client requests. 为每个服务请求和客户端请求提供了一个新实例。A new instance is provided to each service request and client request.
  • 作用域 对象在一个客户端请求中是相同的,但在多个客户端请求中是不同的。Scoped objects are the same within a client request but different across client requests.
  • 单一实例 对象对每个对象和每个请求都是相同的(不管 Startup.ConfigureServices 中是否提供 Operation 实例)。Singleton objects are the same for every object and every request regardless of whether an Operation instance is provided in Startup.ConfigureServices.

从 main 调用服务Call services from main

使用 IServiceScopeFactory.CreateScope 创建 IServiceScope 以解析应用范围内的已设置范围的服务。Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app's scope. 此方法可以用于在启动时访问有作用域的服务以便运行初始化任务。This approach is useful to access a scoped service at startup to run initialization tasks. 以下示例演示如何在 Program.Main 中获取 MyScopedService 的上下文:The following example shows how to obtain a context for the MyScopedService in Program.Main:

public static void 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.");
        }
    }

    host.Run();
}

作用域验证Scope validation

如果在开发环境中运行应用,默认的服务提供程序会执行检查,从而确认以下内容:When the app is running in the Development environment, the default service provider performs checks to verify that:

  • 没有从根服务提供程序直接或间接解析到有作用域的服务。Scoped services aren't directly or indirectly resolved from the root service provider.
  • 未将有作用域的服务直接或间接注入到单一实例。Scoped services aren't directly or indirectly injected into singletons.

调用 BuildServiceProvider 时创建根服务提供程序。The root service provider is created when BuildServiceProvider is called. 在启动提供程序和应用时,根服务提供程序的生存期对应于应用/服务的生存期,并在关闭应用时释放。The root service provider's lifetime corresponds to the app/server'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 app/server is shut down. 验证服务作用域,将在调用 BuildServiceProvider 时收集这类情况。Validating service scopes catches these situations when BuildServiceProvider is called.

有关详细信息,请参阅 ASP.NET Core Web 主机For more information, see ASP.NET Core Web 主机.

请求服务Request Services

来自 HttpContext 的 ASP.NET Core 请求中可用的服务通过 HttpContext.RequestServices 集合公开。The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.

请求服务表示作为应用的一部分配置和请求的服务。Request Services represent the services configured and requested as part of the app. 当对象指定依赖关系时,RequestServices(而不是 ApplicationServices)中的类型将满足这些要求。When the objects specify dependencies, these are satisfied by the types found in RequestServices, not ApplicationServices.

通常,应用不应直接使用这些属性。Generally, the app shouldn't use these properties directly. 相反,通过类构造函数请求类所需的类型,并允许框架注入依赖关系。Instead, request the types that classes require via class constructors and allow the framework inject the dependencies. 这样生成的类更易于测试。This yields classes that are easier to test.

备注

与访问 RequestServices 集合相比,以构造函数参数的形式请求依赖项是更优先的选择。Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.

设计能够进行依赖关系注入的服务Design services for dependency injection

最佳做法是:Best practices are to:

  • 设计服务以使用依赖关系注入来获取其依赖关系。Design services to use dependency injection to obtain their dependencies.
  • 避免进行有状态的静态方法调用。Avoid stateful, static method calls.
  • 避免在服务中直接实例化依赖类。Avoid direct instantiation of dependent classes within services. 直接实例化将代码耦合到特定实现。Direct instantiation couples the code to a particular implementation.
  • 不在应用类中包含过多内容,确保设计规范,并易于测试。Make app classes small, well-factored, and easily tested.

如果一个类似乎有过多的注入依赖关系,这通常表明该类拥有过多的责任并且违反了单一责任原则 (SRP)If a class seems to have too many injected dependencies, this is generally a sign that the class has too many responsibilities and is violating the Single Responsibility Principle (SRP). 尝试通过将某些职责移动到一个新类来重构类。Attempt to refactor the class by moving some of its responsibilities into a new class. 请记住,Razor Pages 页模型类和 MVC 控制器类应关注用户界面问题。Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns. 业务规则和数据访问实现细节应保留在适用于这些分离的关注点的类中。Business rules and data access implementation details should be kept in classes appropriate to these separate concerns.

服务处理Disposal of services

容器为其创建的 IDisposable 类型调用 DisposeThe container calls Dispose for the IDisposable types it creates. 如果通过用户代码将实例添加到容器中,则不会自动处理该实例。If an instance is added to the container by user code, it isn't disposed automatically.

// 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

内置的服务容器旨在满足框架和大多数消费者应用的需求。The built-in service container is meant 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. 内置容器中找不到第三方容器支持的某些功能:Some of the features supported in 3rd party containers not found in the built-in container:

  • 属性注入Property injection
  • 基于名称的注入Injection based on name
  • 子容器Child containers
  • 自定义生存期管理Custom lifetime management
  • 对迟缓初始化的 Func<T> 支持Func<T> support for lazy initialization

有关支持适配器的部分容器列表,请参阅依赖关系注入 readme.md 文件See the Dependency Injection readme.md file for a list of some of the containers that support adapters.

以下示例将内置容器替换为 AutofacThe following sample replaces the built-in container with Autofac:

  • 安装适当的容器包:Install the appropriate container package(s):

  • Startup.ConfigureServices 中配置容器并返回 IServiceProviderConfigure the container in Startup.ConfigureServices and return an IServiceProvider:

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        // Add other framework services
    
        // Add Autofac
        var containerBuilder = new ContainerBuilder();
        containerBuilder.RegisterModule<DefaultModule>();
        containerBuilder.Populate(services);
        var container = containerBuilder.Build();
        return new AutofacServiceProvider(container);
    }
    

    要使用第三方容器,Startup.ConfigureServices 必须返回 IServiceProviderTo use a 3rd party container, Startup.ConfigureServices must return IServiceProvider.

  • DefaultModule 中配置 Autofac:Configure Autofac in DefaultModule:

    public class DefaultModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
        }
    }
    

在运行时,使用 Autofac 来解析类型,并注入依赖关系。At runtime, Autofac is used to resolve types and inject dependencies. 要了解有关结合使用 Autofac 和 ASP.NET Core 的详细信息,请参阅 Autofac 文档To learn more about using Autofac with ASP.NET Core, see the Autofac documentation.

线程安全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 how it's used by the singleton.

单个服务的工厂方法(例如 AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>) 的第二个参数)不必是线程安全的。The factory method of single service, such as the second argument to AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), doesn't need to be thread-safe. 像类型 (static) 构造函数一样,它保证由单个线程调用一次。Like a type (static) constructor, it's guaranteed to be called once by a single thread.

建议Recommendations

  • 不支持基于 async/awaitTask 的服务解析。async/await and Task based service resolution is not supported. C# 不支持异步构造函数;因此建议模式是在同步解析服务后使用异步方法。C# does not support asynchronous constructors; therefore, the recommended pattern is to 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 some other object. 最好通过 DI 请求实际项目。It's better to request the actual item via DI.

  • 避免静态访问服务(例如,静态键入 IApplicationBuilder.ApplicationServices 以便在其他地方使用)。Avoid static access to services (for example, statically-typing IApplicationBuilder.ApplicationServices for use elsewhere).

  • 避免使用服务定位器模式 。Avoid using the service locator pattern. 例如,可以改用 DI 时,不要调用 GetService 来获取服务实例:For example, don't invoke GetService to obtain a service instance when you can use DI instead:

    不正确:Incorrect:

    public class MyClass()
    {
        public void MyMethod()
        {
            var optionsMonitor = 
                _services.GetService<IOptionsMonitor<MyOptions>>();
            var option = optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    

    正确Correct:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • 要避免的另一个服务定位器变体是注入可在运行时解析依赖项的工厂。Another service locator variation to avoid is injecting a factory that resolves dependencies at runtime. 这两种做法混合了控制反转策略。Both of these practices mix Inversion of Control strategies.

  • 避免静态访问 HttpContext(例如,IHttpContextAccessor.HttpContext)。Avoid static access to HttpContext (for example, IHttpContextAccessor.HttpContext).

像任何一组建议一样,你可能会遇到需要忽略某建议的情况。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 是静态/全局对象访问模式的替代方法 。DI is an alternative to static/global object access patterns. 如果将其与静态对象访问混合使用,则可能无法实现 DI 的优点。You may not be able to realize the benefits of DI if you mix it with static object access.

其他资源Additional resources