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

作者:Kirk LarkinSteve SmithScott AddieBrandon DahlerBy Kirk Larkin, Steve Smith, Scott Addie, and Brandon Dahler

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 中将依赖项注入到控制器.

若要了解如何在 Web 应用以外的应用程序中使用依赖关系注入,请参阅 .NET 中的依赖关系注入For information on using dependency injection in applications other than web apps, see Dependency injection in .NET.

有关选项的依赖项注入的详细信息,请参阅 ASP.NET Core 中的选项模式For more information on dependency injection of options, see ASP.NET Core 中的选项模式.

本主题介绍 ASP.NET Core 中的依赖关系注入。This topic provides information on dependency injection in ASP.NET Core. 有关使用依赖关系注入的主要文档包含在 .NET 中的依赖关系注入The primary documentation on using dependency injection is contained in Dependency injection in .NET.

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

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

依赖项是指另一个对象所依赖的对象。A dependency is an object that another object depends on. 使用其他类所依赖的 WriteMessage 方法检查以下 MyDependency 类:Examine the following MyDependency class with a WriteMessage method that other classes depend on:

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

类可以创建 MyDependency 类的实例,以便利用其 WriteMessage 方法。A class can create an instance of the MyDependency class to make use of its WriteMessage method. 在以下示例中,MyDependency 类是 IndexModel 类的依赖项:In the following example, the MyDependency class is a dependency of the IndexModel class:

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

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet created this message.");
    }
}

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

  • 要用不同的实现替换 MyDependency,必须修改 IndexModel 类。To replace MyDependency with a different implementation, the IndexModel class must be modified.
  • 如果 MyDependency 具有依赖项,则必须由 IndexModel 类对其进行配置。If MyDependency has dependencies, they must also be configured by the IndexModel 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 typically 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 接口定义 WriteMessage 方法:In the sample app, the IMyDependency interface defines the WriteMessage method:

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

此接口由具体类型 MyDependency 实现:This interface is implemented by a concrete type, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

示例应用使用具体类型 MyDependency 注册 IMyDependency 服务。The sample app registers the IMyDependency service with the concrete type MyDependency. AddScoped 方法使用范围内生存期(单个请求的生存期)注册服务。The AddScoped method registers the service with a scoped lifetime, the lifetime of a single request. 本主题后面将介绍服务生存期Service lifetimes are described later in this topic.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();

    services.AddRazorPages();
}

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

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

通过使用 DI 模式,表示控制器:By using the DI pattern, the controller:

  • 不使用具体类型 MyDependency,仅使用它实现的 IMyDependency 接口。Doesn't use the concrete type MyDependency, only the IMyDependency interface it implements. 这样可以轻松地更改控制器使用的实现,而无需修改控制器。That makes it easy to change the implementation that the controller uses without modifying the controller.
  • 不创建 MyDependency 的实例,这由 DI 容器创建。Doesn't create an instance of MyDependency, it's created by the DI container.

可以通过使用内置日志记录 API 来改善 IMyDependency 接口的实现:The implementation of the IMyDependency interface can be improved by using the built-in logging API:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

更新的 ConfigureServices 方法注册新的 IMyDependency 实现:The updated ConfigureServices method registers the new IMyDependency implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 依赖于 ILogger<TCategoryName>,并在构造函数中对其进行请求。MyDependency2 depends on ILogger<TCategoryName>, which it requests in the constructor. ILogger<TCategoryName>框架提供的服务ILogger<TCategoryName> is a framework-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 referred to as a dependency tree, dependency graph, or object graph.

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

在依赖项注入术语中,服务:In dependency injection terminology, a service:

  • 通常是向其他对象提供服务的对象,如 IMyDependency 服务。Is typically an object that provides a service to other objects, such as the IMyDependency service.
  • 与 Web 服务无关,尽管服务可能使用 Web 服务。Is not related to a web service, although the service may use a web service.

框架提供可靠的日志记录系统。The framework provides a robust logging system. 编写上述示例中的 IMyDependency 实现来演示基本的 DI,而不是来实现日志记录。The IMyDependency implementations shown in the preceding examples were written to demonstrate basic DI, not to implement logging. 大多数应用都不需要编写记录器。Most apps shouldn't need to write loggers. 下面的代码演示如何使用默认日志记录,这不要求在 ConfigureServices 中注册任何服务:The following code demonstrates using the default logging, which doesn't require any services to be registered in ConfigureServices:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; }

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

使用前面的代码时,无需更新 ConfigureServices,因为框架提供日志记录Using the preceding code, there is no need to update ConfigureServices, because logging is provided by the framework.

注入 Startup 的服务Services injected into Startup

服务可以注入 Startup 构造函数和 Startup.Configure 方法。Services can be injected into the Startup constructor and the Startup.Configure method.

使用泛型主机 (IHostBuilder) 时,只能将以下服务注入 Startup 构造函数:Only the following services can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

任何向 DI 容器注册的服务都可以注入 Startup.Configure 方法:Any service registered with the DI container can be injected into the Startup.Configure method:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    ...
}

有关详细信息,请参阅 ASP.NET Core 中的应用启动访问 Startup 中的配置For more information, see ASP.NET Core 中的应用启动 and Access configuration in Startup.

使用扩展方法注册服务组Register groups of services with extension methods

ASP.NET Core 框架使用一种约定来注册一组相关服务。The ASP.NET Core framework uses a convention for registering a group of related services. 约定使用单个 Add{GROUP_NAME} 扩展方法来注册该框架功能所需的所有服务。The convention is to use a single Add{GROUP_NAME} extension method to register all of the services required by a framework feature. 例如,AddControllers 扩展方法会注册 MVC 控制器所需的服务。For example, the AddControllers extension method registers the services required for MVC controllers.

下面的代码通过个人用户帐户由 Razor 页面模板生成,并演示如何使用扩展方法 AddDbContextAddDefaultIdentity 将其他服务添加到容器中:The following code is generated by the Razor Pages template using individual user accounts and shows how to add additional services to the container using the extension methods AddDbContext and AddDefaultIdentity:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

请考虑下面的 ConfigureServices 方法,该方法可注册服务并配置选项:Consider the following ConfigureServices method, which registers services and configures options:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

可以将相关的注册组移动到扩展方法以注册服务。Related groups of registrations can be moved to an extension method to register services. 例如,配置服务会被添加到以下类中:For example, the configuration services are added to the following class:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }
    }
}

剩余的服务会在类似的类中注册。The remaining services are registered in a similar class. 下面的 ConfigureServices 方法使用新扩展方法来注册服务:The following ConfigureServices method uses the new extension methods to register the services:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

注意: 每个 services.Add{GROUP_NAME} 扩展方法添加并可能配置服务。Note: Each services.Add{GROUP_NAME} extension method adds and potentially configures services. 例如,AddControllersWithViews 会添加带视图的 MVC 控制器所需的服务,AddRazorPages 会添加 Razor Pages 所需的服务。For example, AddControllersWithViews adds the services MVC controllers with views require, and AddRazorPages adds the services Razor Pages requires. 我们建议应用遵循此命名约定。We recommended that apps follow this naming convention. 将扩展方法置于 Microsoft.Extensions.DependencyInjection 命名空间中以封装服务注册的组。Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations.

服务生存期Service lifetimes

请参阅 .NET 中的依赖关系注入中的服务生存期See Service lifetimes in Dependency injection in .NET

要在中间件中使用范围内服务,请使用以下方法之一:To use scoped services in middleware, use one of the following approaches:

  • 将服务注入中间件的 InvokeInvokeAsync 方法。Inject the service into the middleware's Invoke or InvokeAsync method. 使用构造函数注入会引发运行时异常,因为它强制使范围内服务的行为与单一实例类似。Using constructor injection throws a runtime exception because it forces the scoped service to behave like a singleton. 生存期和注册选项部分中的示例演示了 InvokeAsync 方法。The sample in the Lifetime and registration options section demonstrates the InvokeAsync approach.
  • 使用基于工厂的中间件Use Factory-based middleware. 使用此方法注册的中间件按客户端请求(连接)激活,这也使范围内服务可注入中间件的 InvokeAsync 方法。Middleware registered using this approach is activated per client request (connection), which allows scoped services to be injected into the middleware's InvokeAsync method.

有关详细信息,请参阅 写入自定义 ASP.NET Core 中间件For more information, see 写入自定义 ASP.NET Core 中间件.

服务注册方法Service registration methods

请参阅 .NET 中的依赖关系注入中的服务注册方法See Service registration methods in Dependency injection in .NET

为测试模拟类型时,使用多个实现很常见。It's common to use multiple implementations when mocking types for testing.

仅使用实现类型注册服务等效于使用相同的实现和服务类型注册该服务。Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. 因此,我们不能使用捕获显式服务类型的方法来注册服务的多个实现。This is why multiple implementations of a service cannot be registered using the methods that don't take an explicit service type. 这些方法可以注册服务的多个实例,但它们都具有相同的实现类型 。These methods can register multiple instances of a service, but they will all have the same implementation type.

上述任何服务注册方法都可用于注册同一服务类型的多个服务实例。Any of the above service registration methods can be used to register multiple service instances of the same service type. 下面的示例以 IMyDependency 作为服务类型调用 AddSingleton 两次。In the following example, AddSingleton is called twice with IMyDependency as the service type. 第二次对 AddSingleton 的调用在解析为 IMyDependency 时替代上一次调用,在通过 IEnumerable<IMyDependency> 解析多个服务时添加到上一次调用。The second call to AddSingleton overrides the previous one when resolved as IMyDependency and adds to the previous one when multiple services are resolved via IEnumerable<IMyDependency>. 通过 IEnumerable<{SERVICE}> 解析服务时,服务按其注册顺序显示。Services appear in the order they were registered when resolved via IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

构造函数注入行为Constructor injection behavior

请参阅 .NET 中的依赖关系注入中的构造函数注入行为See Constructor injection behavior in Dependency injection in .NET

实体框架上下文Entity Framework contexts

默认情况下,使用设置了范围的生存期将实体框架上下文添加到服务容器中,因为 Web 应用数据库操作通常将范围设置为客户端请求。By default, Entity Framework contexts are added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. 要使用其他生存期,请使用 AddDbContext 重载来指定生存期。To use a different lifetime, specify the lifetime by using an AddDbContext overload. 给定生存期的服务不应使用生存期比服务生存期短的数据库上下文。Services of a given lifetime shouldn't use a database context with a lifetime that's shorter than the service's lifetime.

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

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

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

以下 Operation 类实现了前面的所有接口。The following Operation class implements all of the preceding interfaces. Operation 构造函数生成 GUID,并将最后 4 个字符存储在 OperationId 属性中:The Operation constructor generates a GUID and stores the last 4 characters in the OperationId property:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Startup.ConfigureServices 方法根据命名生存期创建 Operation 类的多个注册:The Startup.ConfigureServices method creates multiple registrations of the Operation class according to the named lifetimes:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

    services.AddRazorPages();
}

示例应用一并演示了请求中和请求之间的对象生存期。The sample app demonstrates object lifetimes both within and between requests. IndexModel 和中间件请求每种 IOperation 类型,并记录各自的 OperationIdThe IndexModel and the middleware request each kind of IOperation type and log the OperationId for each:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

IndexModel 类似,中间件会解析相同的服务:Similar to the IndexModel, the middleware resolves the same services:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationTransient transientOperation,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

范围内服务必须在 InvokeAsync 方法中进行解析:Scoped services must be resolved in the InvokeAsync method:

public async Task InvokeAsync(HttpContext context,
    IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + _transientOperation.OperationId);
    _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

记录器输出显示:The logger output shows:

  • 暂时性对象始终不同。Transient objects are always different. IndexModel 和中间件中的临时 OperationId 值不同。The transient OperationId value is different in the IndexModel and in the middleware.
  • 范围内对象对每个请求而言是相同的,但在请求之间不同。Scoped objects are the same for each request but different across each request.
  • 单一实例对象对于每个请求是相同的。Singleton objects are the same for every request.

若要减少日志记录输出,请在 appsettings.Development.json 文件中设置“Logging:LogLevel:Microsoft:Error”:To reduce the logging output, set "Logging:LogLevel:Microsoft:Error" in the appsettings.Development.json file:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

从 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.

以下示例演示如何访问范围内 IMyDependency 服务并在 Program.Main 中调用其 WriteMessage 方法:The following example shows how to access the scoped IMyDependency service and call its WriteMessage method in Program.Main:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myDependency = services.GetRequiredService<IMyDependency>();
                myDependency.WriteMessage("Call services from main");
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

作用域验证Scope validation

请参阅 .NET 中的依赖关系注入中的构造函数注入行为See Constructor injection behavior in Dependency injection in .NET

有关详细信息,请参阅作用域验证For more information, see Scope validation.

请求服务Request Services

ASP.NET Core 请求中可用的服务通过 HttpContext.RequestServices 集合公开。The services available within an ASP.NET Core request are exposed through the HttpContext.RequestServices collection. 从请求内部请求服务时,将从 RequestServices 集合解析服务及其依赖项。When services are requested from inside of a request, the services and their dependencies are resolved from the RequestServices collection.

为每个请求创建一个作用域,RequestServices 公开作用域服务提供程序。The framework creates a scope per request and RequestServices exposes the scoped service provider. 只要请求处于活动状态,所有作用域服务都有效。All scoped services are valid for as long as the request is active.

备注

与解析 RequestServices 集合中的服务相比,以构造函数参数的形式请求依赖项是更优先的选择。Prefer requesting dependencies as constructor parameters to resolving services from the RequestServices collection. 这样生成的类更易于测试。This results in classes that are easier to test.

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

在设计能够进行依赖注入的服务时:When designing services for dependency injection:

  • 避免有状态的、静态类和成员。Avoid stateful, static classes and members. 通过将应用设计为改用单一实例服务,避免创建全局状态。Avoid creating global state by designing apps to use singleton services instead.
  • 避免在服务中直接实例化依赖类。Avoid direct instantiation of dependent classes within services. 直接实例化会将代码耦合到特定实现。Direct instantiation couples the code to a particular implementation.
  • 不在服务中包含过多内容,确保设计规范,并易于测试。Make services small, well-factored, and easily tested.

如果一个类有过多注入依赖项,这可能表明该类拥有过多的责任并且违反了单一责任原则 (SRP)If a class has a lot of injected dependencies, it might be a sign that the class has too many responsibilities and violates the Single Responsibility Principle (SRP). 尝试通过将某些职责移动到一个新类来重构类。Attempt to refactor the class by moving some of its responsibilities into new classes. 请记住,Razor Pages 页面模型类和 MVC 控制器类应关注用户界面问题。Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns.

服务释放Disposal of services

容器为其创建的 IDisposable 类型调用 DisposeThe container calls Dispose for the IDisposable types it creates. 从容器中解析的服务绝对不应由开发人员释放。Services resolved from the container should never be disposed by the developer. 如果类型或工厂注册为单一实例,则容器自动释放单一实例。If a type or factory is registered as a singleton, the container disposes the singleton automatically.

在下面的示例中,服务由服务容器创建,并自动释放:In the following example, the services are created by the service container and disposed automatically:

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    
    var myKey = Configuration["MyKey"];
    services.AddSingleton<IService3>(sp => new Service3(myKey));

    services.AddRazorPages();
}
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

每次刷新索引页后,调试控制台显示以下输出:The debug console shows the following output after each refresh of the Index page:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet
Service1.Dispose

不由服务容器创建的服务Services not created by the service container

考虑下列代码:Consider the following code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());

    services.AddRazorPages();
}

在上述代码中:In the preceding code:

  • 服务实例不是由服务容器创建的。The service instances aren't created by the service container.
  • 框架不会自动释放服务。The framework doesn't dispose of the services automatically.
  • 开发人员负责释放服务。The developer is responsible for disposing the services.

暂时和共享实例的 IDisposable 指南IDisposable guidance for Transient and shared instances

请参阅 .NET 中的依赖关系注入中的暂时和共享实例的 IDisposable 指南See IDisposable guidance for Transient and shared instance in Dependency injection in .NET

默认服务容器替换Default service container replacement

请参阅 .NET 中的依赖关系注入中的默认服务容器替换See Default service container replacement in Dependency injection in .NET

建议Recommendations

请参阅 .NET 中的依赖关系注入中的建议See Recommendations in Dependency injection in .NET

  • 避免使用服务定位器模式。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:

    错误代码

    正确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).

  • 避免在 ConfigureServices 中调用 BuildServiceProviderAvoid calls to BuildServiceProvider in ConfigureServices. 当开发人员想要在 ConfigureServices 中解析服务时,通常会调用 BuildServiceProviderCalling BuildServiceProvider typically happens when the developer wants to resolve a service in ConfigureServices. 例如,假设 LoginPath 从配置中加载。For example, consider the case where the LoginPath is loaded from configuration. 避免采用以下方法:Avoid the following approach:

    调用 BuildServiceProvider 的错误代码

    在上图中,选择 services.BuildServiceProvider 下的绿色波浪线将显示以下 ASP0000 警告:In the preceding image, selecting the green wavy line under services.BuildServiceProvider shows the following ASP0000 warning:

    ASP0000 从应用程序代码调用“BuildServiceProvider”会导致创建单一实例服务的其他副本。ASP0000 Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. 考虑依赖项注入服务等替代项作为“Configure”的参数。Consider alternatives such as dependency injecting services as parameters to 'Configure'.

    调用 BuildServiceProvider 会创建第二个容器,该容器可创建残缺的单一实例并导致跨多个容器引用对象图。Calling BuildServiceProvider creates a second container, which can create torn singletons and cause references to object graphs across multiple containers.

    获取 LoginPath 的正确方法是使用选项模式对 DI 的内置支持:A correct way to get LoginPath is to use the options pattern's built-in support for DI:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    
        services.AddOptions<CookieAuthenticationOptions>(
                            CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IMyService>((options, myService) =>
            {
                options.LoginPath = myService.GetLoginPath();
            });
    
        services.AddRazorPages();
    }
    
  • 可释放的暂时性服务由容器捕获以进行释放。Disposable transient services are captured by the container for disposal. 如果从顶级容器解析,这会变为内存泄漏。This can turn into a memory leak if resolved from the top level container.

  • 启用范围验证,确保应用没有捕获范围内服务的单一实例。Enable scope validation to make sure the app doesn't have singletons that capture scoped services. 有关详细信息,请参阅作用域验证For more information, see Scope validation.

像任何一组建议一样,你可能会遇到需要忽略某建议的情况。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.

Orchard Core 是用于在 ASP.NET Core 上构建模块化多租户应用程序的应用程序框架。Orchard Core is an application framework for building modular, multi-tenant applications on ASP.NET Core. 有关详细信息,请参阅 Orchard Core 文档For more information, see the Orchard Core Documentation.

请参阅 Orchard Core 示例,获取有关如何仅使用 Orchard Core Framework 而无需任何 CMS 特定功能来构建模块化和多租户应用的示例。See the Orchard Core samples for examples of how to build modular and multi-tenant apps using just the Orchard Core Framework without any of its CMS-specific features.

框架提供的服务Framework-provided services

Startup.ConfigureServices 方法注册应用使用的服务,包括 Entity Framework Core 和 ASP.NET Core MVC 等平台功能。The Startup.ConfigureServices method registers services that the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. 最初,提供给 ConfigureServicesIServiceCollection 具有框架定义的服务(具体取决于主机配置方式)。Initially, the IServiceCollection provided to ConfigureServices has services defined by the framework depending on how the host was configured. 对于基于 ASP.NET Core 模板的应用,该框架会注册 250 个以上的服务。For apps based on the ASP.NET Core templates, the framework registers more than 250 services.

下表列出了框架注册的这些服务的一小部分:The following table lists a small sample of these framework-registered services:

服务类型Service Type 生存期Lifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 暂时Transient
IHostApplicationLifetime 单例Singleton
IWebHostEnvironment 单例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

其他资源Additional resources

作者:Steve SmithScott AddieBrandon DahlerBy Steve Smith, Scott Addie, and Brandon Dahler

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. IMyDependencyStartup.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<>), typeof(Logger<>));

在示例应用中,使用具体类型 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.AddControllersWithViewsservices.AddRazorPagesservices.AddControllers 会添加 ASP.NET Core 应用所需的服务。For example, services.AddControllersWithViews, services.AddRazorPages, and services.AddControllers adds the services ASP.NET Core apps 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. 还包括用于 DI 扩展方法的命名空间部分 Microsoft.Extensions.DependencyInjectionIncluding the namespace portion Microsoft.Extensions.DependencyInjection for DI extension methods also:

  • 允许在不添加其他 using 块的情况下在 IntelliSense 中显示它们。Allows them to be displayed in IntelliSense without adding additional using blocks.
  • 防止在通常从中调用这些扩展方法的 Startup 类中出现过多的 using 语句。Prevents excessive using statements in the Startup class where these extension methods are typically called from.

如果服务的构造函数需要内置类型(如 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.");
    }
}

注入 Startup 的服务Services injected into Startup

使用泛型主机 (IHostBuilder) 时,只能将以下服务类型注入 Startup 构造函数:Only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

服务可以注入 Startup.ConfigureServices can be injected into Startup.Configure:

public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
{
    ...
}

有关详细信息,请参阅 ASP.NET Core 中的应用启动For more information, see ASP.NET Core 中的应用启动.

框架提供的服务Framework-provided services

Startup.ConfigureServices 方法负责定义应用使用的服务,包括 Entity Framework Core 和 ASP.NET Core MVC 等平台功能。The Startup.ConfigureServices method is responsible for defining the services that the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. 最初,提供给 ConfigureServicesIServiceCollection 具有框架定义的服务(具体取决于主机配置方式)。Initially, the IServiceCollection provided to ConfigureServices has services defined by the framework depending on how the host was configured. 基于 ASP.NET Core 模板的应用程序具有框架注册的数百个服务的情况并不少见。It's not uncommon for an app based on an ASP.NET Core template to have hundreds of services registered by the framework. 下表列出了框架注册的服务的一小部分。A small sample of framework-registered services is listed in the following table.

服务类型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

使用扩展方法注册附加服务Register additional services with extension methods

当服务集合扩展方法可用于注册服务(及其依赖服务,如果需要)时,约定使用单个 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>AddIdentityCore 向容器添加附加服务的示例:The following code is an example of how to add additional services to the container using the extension methods AddDbContext<TContext> and AddIdentityCore:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

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

    ...
}

有关详细信息,请参阅 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.

在处理请求的应用中,在请求结束时会释放暂时服务。In apps that process requests, transient services are disposed at the end of the request.

范围内Scoped

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

在处理请求的应用中,在请求结束时会释放有作用域的服务。In apps that process requests, scoped services are disposed at the end of the request.

警告

在中间件内使用有作用域的服务时,请将该服务注入至 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.

在处理请求的应用中,当应用关闭并释放 ServiceProvider 时,会释放单一实例服务。In apps that process requests, singleton services are disposed when the ServiceProvider is disposed at app shutdown.

警告

从单一实例解析有作用域的服务很危险。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

服务注册扩展方法提供适用于特定场景的重载。Service registration extension methods offer overloads that are useful in specific scenarios.

方法Method 自动Automatic
对象 (object)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("A string!"));
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("A string!"));
No Yes Yes
AddSingleton(new {IMPLEMENTATION})
示例:Examples:
services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep("A string!"));
No 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.

仅使用实现类型注册服务等效于使用相同的实现和服务类型注册该服务。Registering a service with only an implementation type is equivalent to registering that service with the same implementation and service type. 因此,我们不能使用捕获显式服务类型的方法来注册服务的多个实现。This is why multiple implementations of a service cannot be registered using the methods that don't take an explicit service type. 这些方法可以注册服务的多个实例,但它们都具有相同的实现类型 。These methods can register multiple instances of a service, but they will all have the same implementation type.

上述任何服务注册方法都可用于注册同一服务类型的多个服务实例。Any of the above service registration methods can be used to register multiple service instances of the same service type. 下面的示例以 IMyDependency 作为服务类型调用 AddSingleton 两次。In the following example, AddSingleton is called twice with IMyDependency as the service type. 第二次对 AddSingleton 的调用在解析为 IMyDependency 时替代上一次调用,在通过 IEnumerable<IMyDependency> 解析多个服务时添加到上一次调用。The second call to AddSingleton overrides the previous one when resolved as IMyDependency and adds to the previous one when multiple services are resolved via IEnumerable<IMyDependency>. 通过 IEnumerable<{SERVICE}> 解析服务时,服务按其注册顺序显示。Services appear in the order they were registered when resolved via IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

框架还提供 TryAdd{LIFETIME} 扩展方法,只有当尚未注册某个实现时,才注册该服务。The framework also provides TryAdd{LIFETIME} extension methods, which register the service only if there isn't already an implementation registered.

在下面的示例中,对 AddSingleton 的调用会将 MyDependency 注册为 IMyDependency的实现。In the following example, the call to AddSingleton registers MyDependency as an implementation for IMyDependency. TryAddSingleton 的调用没有任何作用,因为 IMyDependency 已有一个已注册的实现。The call to TryAddSingleton has no effect because IMyDependency already has a registered implementation.

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

public class MyService
{
    public MyService(IMyDependency myDependency, 
        IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is MyDependency);
        Trace.Assert(myDependencies.Single() is MyDependency);
    }
}

有关详情,请参阅: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 with the correct service lifetime to run initialization tasks. 以下示例演示如何在 Program.Main 中获取 MyScopedService 的上下文:The following example shows how to obtain a context for the MyScopedService in Program.Main:

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>();
}

不需要为暂时性服务创建范围,其中包括前面示例中的 ILogger(请参阅:.NET Core 和 ASP.NET Core 中的日志记录)。It isn't necessary to create a scope for transient services, including for the ILogger in the preceding example (see: .NET Core 和 ASP.NET Core 中的日志记录). 当从根解析时,暂时性服务并不会像限定范围的服务那样无意地解析为单一实例。Transients don't resolve inadvertantly as singletons when resolved from the root, as scoped services would. 暂时性服务是在请求时创建的。Transients are created when requested. 如果暂时性服务碰巧是可处置的,则在处置之前它都将以容器为根。If a transient service happens to be disposable, then it's rooted by the container until disposal. 有关示例,请参阅 在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求For example, see 在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求.

作用域验证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 classes and members. 将应用设计为改用单一实例服务,可避免创建全局状态。Design apps to use singleton services instead, which avoid creating global state.
  • 避免在服务中直接实例化依赖类。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.

在下面的示例中,服务由服务容器创建,并自动释放:In the following example, the services are created by the service container and disposed automatically:

public class Service1 : IDisposable {}
public class Service2 : IDisposable {}

public interface IService3 {}
public class Service3 : IService3, IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<IService3>(sp => new Service3());
}

如下示例中:In the following example:

  • 服务实例不是由服务容器创建的。The service instances aren't created by the service container.
  • 框架不知道预期的服务生存期。The intended service lifetimes aren't known by the framework.
  • 框架不会自动释放服务。The framework doesn't dispose of the services automatically.
  • 服务如果未通过开发者代码显式释放,则会一直保留,直到应用关闭。If the services aren't explicitly disposed in developer code, they persist until the app shuts down.
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<Service1>(new Service1());
    services.AddSingleton(new Service2());
}

暂时和共享实例的 IDisposable 指南IDisposable guidance for Transient and shared instances

暂时、有限的生存期Transient, limited lifetime

方案Scenario

应用需要一个 IDisposable 实例,该实例在以下任一情况下具有暂时性生存期:The app requires an IDisposable instance with a transient lifetime for either of the following scenarios:

  • 在根作用域内解析实例。The instance is resolved in the root scope.
  • 应在作用域结束之前释放实例。The instance should be disposed before the scope ends.

解决方案Solution

使用工厂模式在父作用域外创建实例。 Use the factory pattern to create an instance outside of the parent scope. 在这种情况下,应用通常会使用一个 Create 方法,该方法直接调用最终类型的构造函数。In this situation, the app would generally have a Create method that calls the final type's constructor directly. 如果最终类型具有其他依赖项,则工厂可以:If the final type has other dependencies, the factory can:

共享实例,有限的生存期Shared Instance, limited lifetime

方案Scenario

应用需要跨多个服务的共享 IDisposable 实例,但 IDisposable 应具有有限的生存期。The app requires a shared IDisposable instance across multiple services, but the IDisposable should have a limited lifetime.

解决方案Solution

为实例注册作用域生存期。 Register the instance with a Scoped lifetime. 使用 IServiceScopeFactory.CreateScope 启动并创建新的 IServiceScopeUse IServiceScopeFactory.CreateScope to start and create a new IServiceScope. 使用作用域的 IServiceProvider 获取所需的服务。Use the scope's IServiceProvider to get required services. 在生存期应结束时释放作用域。Dispose the scope when the lifetime should end.

通用准则General Guidelines

  • 不要为IDisposable 实例注册暂时作用域。Don't register IDisposable instances with a Transient scope. 请改用工厂模式。Use the factory pattern instead.
  • 不要在根作用域内解析暂时或作用域 IDisposable 实例。Don't resolve Transient or Scoped IDisposable instances in the root scope. 唯一的一般性例外是应用创建/重建和释放 IServiceProvider 的情况,这不是理想的模式。The only general exception is when the app creates/recreates and disposes the IServiceProvider, which isn't an ideal pattern.
  • 通过 DI 接收 IDisposable 依赖项不要求接收方自行实现 IDisposableReceiving an IDisposable dependency via DI doesn't require that the receiver implement IDisposable itself. IDisposable 依赖项的接收方不能对该依赖项调用 DisposeThe receiver of the IDisposable dependency shouldn't call Dispose on that dependency.
  • 作用域应该用于控制服务的生存期。Scopes should be used to control lifetimes of services. 作用域不区分层次,并且在各作用域之间没有特定联系。Scopes aren't hierarchical, and there's no special connection among scopes.

默认服务容器替换Default service container replacement

内置的服务容器旨在满足框架和大多数消费者应用的需求。The built-in service container is designed 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 the built-in container doesn't support, such as:

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

以下第三方容器可用于 ASP.NET Core 应用:The following third-party containers can be used with ASP.NET Core apps:

线程安全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.

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

  • 避免使用服务定位器模式,该模式混合了控制反转策略。Avoid using the service locator pattern, which mixes Inversion of Control strategies.

    • 可以使用 DI 代替时,不要调用 GetService 来获取服务实例: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;
      
              ...
          }
      }
      
  • 避免注入一个使用 GetService 在运行时解析依赖项的中心。Avoid injecting a factory that resolves dependencies at runtime using GetService.

  • 避免静态访问 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