.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. MyDependency使用其他類別所依存的方法檢查下列類別 WriteMessageExamine 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 類別是類別的相依性 IndexModelIn 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 有相依性,則也必須由類別設定 IndexModelIf 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. 應用程式應該使用模擬 (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 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.ConfigureServicesServices 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}");
    }
}

範例應用程式會 IMyDependency 使用具象類型來註冊服務 MyDependencyThe 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.

IMyDependency 可以使用內建的記錄 API 來改善介面的實作為: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:

  • 通常是為其他物件(例如服務)提供服務的物件 IMyDependencyIs 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. 下列程式碼示範如何使用預設記錄,這不需要在中註冊任何服務 ConfigureServicesThe 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.

插入啟動的服務Services injected into Startup

服務可以插入至函式 StartupStartup.Configure 方法。Services can be injected into the Startup constructor and the Startup.Configure method.

Startup使用泛型主機 () 時,只可將下列服務插入至函式 IHostBuilderOnly the following services can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

任何向 DI 容器註冊的服務都可以插入方法中 Startup.ConfigureAny 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 中的應用程式啟動Access Configuration in StartupFor 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 使用個別使用者帳戶的 Pages 範本所產生,並示範如何使用擴充方法和來將其他服務新增至容器 AddDbContext AddDefaultIdentityThe 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 新增具有 views 所需的服務 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. 使用函式 插入 會擲回執行時間例外狀況,因為它會強制範圍服務的行為就像 singleton 一樣。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.
  • 使用以 Factory 為基礎的中介軟體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. 在下列範例中, AddSingleton 使用 IMyDependency 做為服務類型呼叫兩次。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 內容Entity Framework contexts

根據預設,Entity Framework 內容會使用限 域存留期 新增至服務容器,因為 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. 若要使用不同的存留期,請使用多載來指定存留期 AddDbContextTo 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

若要示範服務存留期與其註冊選項之間的差異,請考慮使用下列介面,將工作表示為具有識別碼的作業 OperationIdTo 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方法會根據指定的存留期,建立類別的多個註冊 OperationThe 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 類型,並記錄 OperationId 每個類型的:The 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>();
    }
}

您必須在方法中解析已設定範圍的服務 InvokeAsyncScoped 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. OperationId 中介軟體的和中,暫時性值是不同的 IndexModelThe 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.js 中設定 "記錄: 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"
    }
  }
}

從主要呼叫服務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 服務,並在中呼叫其 WriteMessage 方法 Program.MainThe 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 要求內可用的服務會透過 RequestServices 集合公開。The services available within an ASP.NET Core request are exposed through the HttpContext.RequestServices collection. 從要求內要求服務時,會從集合解析服務及其相依性 RequestServicesWhen 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.

注意

偏好將相依性要求為函式參數,以解析集合中的服務 RequestServicesPrefer 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 頁面頁面模型類別和 MVC 控制器類別應該專注于 UI 的考慮。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. 如果類型或 factory 註冊為 singleton,則容器會自動處置 singleton。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");
    }
}

在每次重新整理 [索引] 頁面之後,debug 主控台會顯示下列輸出: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).

  • 避免 BuildServiceProvider 在中呼叫 ConfigureServicesAvoid calls to BuildServiceProvider in ConfigureServices. 呼叫 BuildServiceProvider 通常會在開發人員想要解析中的服務時發生 ConfigureServicesCalling BuildServiceProvider typically happens when the developer wants to resolve a service in ConfigureServices. 例如,請考慮從設定載入的情況 LoginPathFor 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:

    從應用程式程式碼呼叫 ' BuildServiceProvider ' ASP0000 時,會產生另一個要建立的單一服務複本。ASP0000 Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. 請考慮將相依性插入服務作為「設定」參數的替代方案。Consider alternatives such as dependency injecting services as parameters to 'Configure'.

    呼叫 BuildServiceProvider 會建立第二個容器,它可以建立損毀的 singleton,並導致跨多個容器的物件圖形參考。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.

  • 啟用範圍驗證,以確定應用程式沒有可捕獲範圍服務的 singleton。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 與靜態物件存取混合,則可能無法實現 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 Framework 來建立模組化和多租使用者應用程式的範例,請參閱 Orchard core 範例 ,而不需要任何 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. 一開始, IServiceCollection 提供給的是 ConfigureServices 由架構定義的服務,視 主機的設定方式而定。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. 應用程式應該使用模擬 (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 or base class to abstract the dependency implementation.
  • 在服務容器中註冊相依性。Registration of the dependency in a service container. ASP.NET Core 提供內建服務容器 IServiceProviderASP.NET Core provides a built-in service container, IServiceProvider. 服務會在應用程式的 Startup.ConfigureServices 方法中註冊。Services are registered in the app's Startup.ConfigureServices method.
  • 將服務「插入」到服務使用位置之類別的建構函式。Injection of the service into the constructor of the class where it's used. 架構會負責建立相依性的執行個體,並在不再需要時將它捨棄。The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

範例應用程式中,IMyDependency 介面定義了服務提供給應用程式的方法:In the sample app, the IMyDependency interface defines a method that the service provides to the app:

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

這個介面是由具象型別 MyDependency 所實作:This interface is implemented by a concrete type, MyDependency:

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

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

    public Task WriteMessage(string message)
    {
        _logger.LogInformation(
            "MyDependency.WriteMessage called. Message: {Message}", 
            message);

        return Task.FromResult(0);
    }
}

MyDependency 在其建構函式中會要求 ILogger<TCategoryName>MyDependency requests an ILogger<TCategoryName> in its constructor. 以鏈結方式使用相依性插入並非不尋常。It's not unusual to use dependency injection in a chained fashion. 每個要求的相依性接著會要求其自己的相依性。Each requested dependency in turn requests its own dependencies. 容器會解決圖形中的相依性,並傳回完全解析的服務。The container resolves the dependencies in the graph and returns the fully resolved service. 必須先解析的相依性集合組通常稱為「相依性樹狀結構」、「相依性圖形」或「物件圖形」。The collective set of dependencies that must be resolved is typically referred to as a dependency tree , dependency graph , or object graph.

IMyDependencyILogger<TCategoryName> 必須在服務容器中註冊。IMyDependency and ILogger<TCategoryName> must be registered in the service container. IMyDependency 是在 Startup.ConfigureServices 中註冊。IMyDependency is registered in Startup.ConfigureServices. ILogger<TCategoryName> 是由記錄抽象基礎結構所註冊,所以它是預設由架構所註冊的架構提供的服務ILogger<TCategoryName> is registered by the logging abstractions infrastructure, so it's a framework-provided service registered by default by the framework.

容器會利用 (泛型) 開放類型 解析 ILogger<TCategoryName>,讓您不需註冊每個 (泛型) 建構的型別The container resolves ILogger<TCategoryName> by taking advantage of (generic) open types, eliminating the need to register every (generic) constructed type:

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

在範例應用程式中,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.AddControllersWithViews services.AddRazorPages 和會 services.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.DependencyInjection 也包括:Including the namespace portion Microsoft.Extensions.DependencyInjection for DI extension methods also:

  • 可讓它們在 IntelliSense 中顯示,而不需要新增其他 using 區塊。Allows them to be displayed in IntelliSense without adding additional using blocks.
  • 防止類別中過多的 using 語句 Startup ,通常會從這些擴充方法呼叫這些擴充方法。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.");
    }
}

插入啟動的服務Services injected into Startup

Startup使用泛型主機 () 時,只可將下列服務類型插入至函式 IHostBuilderOnly 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. 一開始, IServiceCollection 提供給的是 ConfigureServices 由架構定義的服務,視 主機的設定方式而定。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> 和來將其他服務新增至容器 AddIdentityCoreThe 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. 不要插入 via 函式 插入 ,因為它會強制服務的行為就像 singleton 一樣。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

當第一次收到有關單一資料庫存留期服務的要求時 (或是當執行 Startup.ConfigureServices 且隨著服務註冊指定執行個體時),即會建立單一資料庫存留期服務 (AddSingleton)。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.

在處理要求的應用程式中,會在應用程式關閉時處置單一服務 ServiceProviderIn 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. 在下列範例中, AddSingleton 使用 IMyDependency 做為服務類型呼叫兩次。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 實作為的 IMyDependencyIn 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. 搭配使用者面向抽象 (例如標籤協助程式、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.

當或解決服務時 IServiceProvider ActivatorUtilities ,必須使用 公用 的函式來 插入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 Startup.ConfigureServicesSingleton objects are the same for every object and every request regardless of whether an Operation instance is provided in Startup.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:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

public class Program
{
    public static async Task Main(string[] args)
    {
        var host = CreateWebHostBuilder(args).Build();

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

            try
            {
                var serviceContext = services.GetRequiredService<MyScopedService>();
                // Use the context here
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        await host.RunAsync();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

範圍驗證Scope validation

當應用程式在開發環境中執行時,預設服務提供者會執行檢查以確認: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 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 頁面頁面模型類別和 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.

在下列範例中,服務是由服務容器建立並自動處置: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

使用 factory 模式,在父範圍之外建立實例。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. 如果最終類型有其他相依性,factory 可以: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. 請改用 factory 模式。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 不需要接收者 IDisposable 自行執行。Receiving an IDisposable dependency via DI doesn't require that the receiver implement IDisposable itself. 相依性的接收者不 IDisposable 應呼叫 Dispose 該相依性。The 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 apps 使用:The following third-party containers can be used with ASP.NET Core apps:

執行緒安全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.

  • 避免靜態存取服務。Avoid static access to services. 例如,請避免以靜態方式輸入 IApplicationBuilder >iapplicationbuilder.applicationservices ,以便在其他地方使用。For example, avoid statically-typing IApplicationBuilder.ApplicationServices for use elsewhere.

  • 請避免使用 服務定位器模式 ,其混合 了控制策略的反轉Avoid using the service locator pattern , which mixes Inversion of Control strategies.

    • GetService當您可以改用 DI 時,請勿叫用來取得服務實例: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;
      
              ...
          }
      }
      
  • 避免插入在執行時間使用來解析相依性的 factory GetServiceAvoid 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 與靜態物件存取混合,則可能無法實現 DI 的優點。You may not be able to realize the benefits of DI if you mix it with static object access.

其他資源Additional resources