.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. 應用程式應該使用模擬 (Mock) 或虛設常式 (Stub) 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 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>));

在範例應用程式中,IMyDependency 服務是使用具象型別 MyDependency 所註冊。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.IApplicationBuilderFactoryMicrosoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory 暫時性Transient
Microsoft.AspNetCore.Hosting.IApplicationLifetimeMicrosoft.AspNetCore.Hosting.IApplicationLifetime 單一Singleton
Microsoft.AspNetCore.Hosting.IHostingEnvironmentMicrosoft.AspNetCore.Hosting.IHostingEnvironment 單一Singleton
Microsoft.AspNetCore.Hosting.IStartupMicrosoft.AspNetCore.Hosting.IStartup 單一Singleton
Microsoft.AspNetCore.Hosting.IStartupFilterMicrosoft.AspNetCore.Hosting.IStartupFilter 暫時性Transient
Microsoft.AspNetCore.Hosting.Server.IServerMicrosoft.AspNetCore.Hosting.Server.IServer 單一Singleton
Microsoft.AspNetCore.Http.IHttpContextFactoryMicrosoft.AspNetCore.Http.IHttpContextFactory 暫時性Transient
Microsoft.Extensions.Logging.ILogger<T>Microsoft.Extensions.Logging.ILogger<T> 單一Singleton
Microsoft.Extensions.Logging.ILoggerFactoryMicrosoft.Extensions.Logging.ILoggerFactory 單一Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProviderMicrosoft.Extensions.ObjectPool.ObjectPoolProvider 單一Singleton
Microsoft.Extensions.Options.IConfigureOptions<T>Microsoft.Extensions.Options.IConfigureOptions<T> 暫時性Transient
Microsoft.Extensions.Options.IOptions<T>Microsoft.Extensions.Options.IOptions<T> 單一Singleton
System.Diagnostics.DiagnosticSourceSystem.Diagnostics.DiagnosticSource 單一Singleton
System.Diagnostics.DiagnosticListenerSystem.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. 下列程式碼範例示範如何使用擴充方法 AddDbContextAddIdentityAddMvc 將額外服務新增到容器中:The following code is an example of how to add additional services to the container using the extension methods AddDbContext, AddIdentity, 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 ClassFor 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

每次從服務容器要求暫時性存留期服務時都會建立它們。Transient lifetime services are created each time they're requested from the service container. 此存留期最適合用於輕量型的無狀態服務。This lifetime works best for lightweight, stateless services.

具範圍Scoped

具範圍存留期服務會在每次用戶端要求 (連線) 時建立一次。Scoped lifetime services 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

當第一次收到有關單一資料庫存留期服務的要求時 (或是當執行 ConfigureServices 而且隨著服務註冊指定執行個體時),即會建立單一資料庫存留期服務。Singleton lifetime services are created the first time they're requested (or when 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.

建構函式插入行為Constructor injection behavior

服務可以透過兩個機制來解析:Services can be resolved by two mechanisms:

  • IServiceProvider
  • ActivatorUtilities – 允許建立物件,而不需要在相依性插入容器中註冊服務。ActivatorUtilities – Permits object creation without service registration in the dependency injection container. 搭配使用者面向抽象 (例如標籤協助程式、MVC 控制器與模型繫結器) 使用 ActivatorUtilitiesActivatorUtilities 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 內容Entity Framework contexts

因為一般會將 Web 應用程式資料庫作業範圍設定為用戶端要求,所以通常會使用具範圍存留期將 Entity Framework 內容新增至服務容器。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. 若未提供 GUID,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; }
}

會註冊相依於每種其他 Operation 類型的 OperationServiceAn 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 服務的 OperationId 會與 OperationServiceOperationId 不同。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 服務與用戶端要求中 OperationServiceOperationId 皆相同。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.
  • 當建立一次 singleton 與 singleton 執行個體服務,並在所有用戶端要求與所有服務中使用時,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 服務使用具有 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.
  • 「單一資料庫」物件對於每個物件與每個要求都相同 (不論 Operation 執行個體是否提供於 ConfigureServices)。Singleton objects are the same for every object and every request regardless of whether an Operation instance is provided in ConfigureServices.

從主要呼叫服務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 中取得 MyScopedServiceThe 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 中找到的類型來滿足,而非 ApplicationServicesWhen 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 控制器類別應該專注於 UI 考量。Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns. 商務規則和資料存取實作詳細資料應該保存在適合這些分開考量 (Separation of 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

如需支援配接器的一些容器清單,請參閱 DependencyInjection readme.md 檔案See the Dependency Injection readme.md file for a list of some of the containers that support adapters.

下列範例會以 Autofac 取代內建容器:The 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. 若要深入了解如何搭配 ASP.NET Core 使用 Autofac,請參閱 Autofac 文件 (英文)。To learn more about using Autofac with ASP.NET Core, see the Autofac documentation.

執行緒安全Thread safety

建立具備執行緒安全性的 singleton 服務。Create thread-safe singleton services. 如果 singleton 服務相依於暫時性服務,暫時性服務可能也需要具備執行緒安全性,取決於 singleton 如何使用它。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.

單一服務的 Factory 方法 (例如 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 void MyMethod()
    {
        var options = 
            _services.GetService<IOptionsMonitor<MyOptions>>();
        var option = options.CurrentValue.Option;
    
        ...
    }
    

    正確Correct:

    private readonly MyOptions _options;
    
    public MyClass(IOptionsMonitor<MyOptions> options)
    {
        _options = options.CurrentValue;
    }
    
    public void MyMethod()
    {
        var option = _options.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 與靜態物件存取混合,則可能無法實現 DI 的優點。You may not be able to realize the benefits of DI if you mix it with static object access.

其他資源Additional resources