共用方式為


ASP.NET Core 中的相依性注入

Note

這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本

Warning

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 關於目前版本,請參閱 本文的 .NET 10 版本

作者:Kirk LarkinSteve SmithBrandon Dahler

ASP.NET Core 支援相依性插入 (DI) 軟體設計模式,這是用來在類別與其相依性之間達成控制權反轉 (IoC) \(英文\) 的技術。

本文提供 ASP.NET Core 網頁應用程式中 DI 的相關資訊。 關於所有類型應用程式中的 DI 資訊,包括非網頁應用程式的應用程式,請參見 .NET 中的相依注入

補充或取代本文指引的指引,請參閱以下條目:

本文中的程式碼範例基於 Blazor。 欲查看 Razor 頁面範例,請參閱 本文 7.0 版本

檢視或下載範例程式碼 \(英文\) (如何下載)

在本地使用本篇 Blazor Web App 範例程式碼作示範時,請採用 互動式渲染模式

依賴注入概觀

相依物件是另一個物件所依賴的物件。 考慮以下 MyDependency 類別,其中包含一個 WriteMessage 方法:

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

類別可以建立該 MyDependency 類別的實例來呼叫其 WriteMessage 方法。 以下範例中,MyDependency 類是 Razor 元件的依賴關係。

Pages/DependencyExample1.razor

@page "/dependency-example-1"

<button @onclick="WriteMessage">Write message</button>

@code {
    private readonly MyDependency dependency = new MyDependency();

    private void WriteMessage() =>
        dependency.WriteMessage("DependencyExample1.WriteMessage called");
}

類別可以建立該 MyDependency 類別的實例來呼叫其 WriteMessage 方法。 以下範例中,該 MyDependency 類別是頁面類別的 IndexModel 相依關係。

Pages/Index.cshtml.cs

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

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet called");
    }
}

消費類別會創造並直接依賴該 MyDependency 類別。 直接依賴,如前述例子,存在問題,應避免,原因如下:

  • 若要以不同實作取代 MyDependency ,必須修改消費類別。
  • 如果 MyDependency 有依賴關係,則消費類別也必須配置它們。 在一個有多個類別的大型專案中,配置 MyDependency程式碼會分散在應用程式各處。
  • 實作很難進行 單元測試

DI 透過以下方式解決這些問題:

  • 使用介面或基底類別來將相依性實作抽象化。
  • 將相依性註冊到 服務容器,也稱為 DI 容器。 ASP.NET Core 提供內建服務容器 IServiceProvider。 服務通常註冊在應用程式 Program 檔案(.NET 6 或更新版本)或應用程式 Startup 檔案(.NET 5 或更早版本)中。
  • 將服務注入 到使用該服務的類別中。 框架會建立依賴實例,並在不再需要時將其處理。

在以下範例中,介面 IMyDependency 定義了 WriteMessage 方法簽名。

Interfaces/IMyDependency.cs

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

前述介面由以下具體型態 MyDependency實作。

Services/MyDependency.cs

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

應用程式會將服務以IMyDependency具體類型MyDependency註冊到服務容器中,通常是在Program檔案(.NET 6 或更新版本)或Startup.ConfigureServices方法(.NET 5 或更早版本)中加入。 此AddScoped方法會以具範圍的生命週期註冊服務,這取決於是.NET 8 或更新版本中的Blazor電路,還是 MVC 或 Pages 應用程式中的單一請求的Razor壽命。 本文稍後會說明 服務存留期

builder.Services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency, MyDependency>();

IMyDependency服務在需要時被請求並用於呼叫WriteMessage方法,正如以下Razor元件所示。

Pages/DependencyExample2.razor

@page "/dependency-example-2"
@inject IMyDependency Dependency

<button @onclick="WriteMessage">Write message</button>

@code {
    private void WriteMessage() =>
        Dependency.WriteMessage("DependencyExample2.WriteMessage called");
}

IMyDependency 服務被請求並用來呼叫 WriteMessage 方法,正如以下頁面模型類所示。

Pages/Index.cshtml.cs

public class IndexModel(IMyDependency dependency) : PageModel
{
    public void OnGet()
    {
        dependency.WriteMessage("IndexModel.OnGet called");
    }
}

透過使用 DI 模式,消耗依賴的類別:

  • 不會使用具體類型 MyDependency,只使用其所實作的 IMyDependency 介面。 這讓你在不修改使用者端的情況下,輕鬆地更改實作。
  • 不會直接產生 MyDependency 或處理它。 依賴項由服務容器建立並處置。

IMyDependency介面實作可以透過使用內建的日誌 API來改進,在以下範例中,這個 API 作為相依性被注入。

Services/MyDependency.cs

public class MyDependency(ILogger<MyDependency> logger) : IMyDependency
{
    public void WriteMessage(string message)
    {
        logger.LogInformation($"MyDependency.WriteMessage: {message}");
    }
}

MyDependency 依賴於ILogger<TCategoryName>,這是一項由框架提供的服務

DI 通常以連鎖方式使用。 每個要求的相依性接著會要求其自己的相依性。 容器會解決圖形中的相依性,並傳回完全解析的服務。 必須先解析的相依性集合組通常稱為「相依性樹狀結構」、「相依性圖形」或「物件圖形」

容器透過利用(泛型)的開放型別來解析,從而免去了註冊每個(泛型)已建構型別的必要。

在DI術語中,服務指的是:

  • 通常是為其他物件提供服務的物件,例如前一個 IMyDependency 服務。
  • 與 Web 服務無關,但服務可能會使用 Web 服務。

IMyDependency前述範例中展示的實作是為了展示一般的 DI 原則,而非實作日誌記錄。 大多數應用程式不應該需要建立記錄器,如前述範例所示。 以下程式碼直接示範使用 框架內建的日誌 API,該 API 不需註冊自訂服務(IMyDependency)。

Pages/LoggingExample.razor

@page "/logging-example"
@inject ILogger<LoggingExample> Logger

<button @onclick="WriteMessage">Write message</button>

@code {
    private void WriteMessage() => 
        Logger.LogInformation("LoggingExample.WriteMessage called");
}

Pages/IndexModel.cshtml.cs

public class IndexModel(ILogger<IndexModel> logger) : PageModel
{ 
    public void OnGet()
    {
        logger.LogInformation("IndexModel.OnGet called");
    }
}

注入至 Startup 的服務

服務可以插入 Startup 建構函式和 Startup.Configure 方法。

ASP.NET Core 3.0 或更高版本的通用主機(IHostBuilder),在暫時的「root」服務提供者使用必要服務來啟動主機並設定應用程式的主要服務容器後,會在應用程式的整個生命週期中使用單一服務容器。 大多數服務,包括不涉及主機啟動的自訂服務和框架服務,在Startup建構子被呼叫時,尚未在服務容器中被設定或可用。 使用通用主機時,僅能注入以下服務到 Startup 建構子中:

透過限制類別建構子中 Startup 可用的服務,Generic Host 防止你在服務尚未建立或可用前嘗試使用該服務,也避免建立多個自訂或框架單例服務,避免在臨時服務容器中建立的單例服務與最終服務容器中建立的服務不同。

任何在服務容器註冊的服務都可以注入到該 Startup.Configure 方法中。 以下範例中,一個 ILogger<TCategoryName> 被注入:

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

如需詳細資訊,請參閱在 ASP.NET Core 中啟動應用程式在啟動中存取設定

服務註冊方法

關於服務登記的一般指引,請參見 服務登記

在進行測試時,通常會使用多種實作來模擬型別。 如需詳細資訊,請參閱 ASP.NET Core中的整合測試

僅註冊一個實作類型的服務,等同於註冊具有相同實作與服務類型的服務:

builder.Services.AddSingleton<MyDependency>();
services.AddSingleton<MyDependency>();

服務註冊方法可用於註冊多個相同服務類型的服務實例。 在下列範例中,系統兩次呼叫 AddSingleton,其服務型別為 IMyDependency。 第二次的 AddSingleton 呼叫在解析為 IMyDependency 時覆寫了第一次的呼叫,而在多個服務透過 IEnumerable<IMyDependency> 解析時,會加入到第一次的呼叫中。

builder.Services.AddSingleton<IMyDependency, MyDependency>();
builder.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);
    }
}
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);
    }
}

使用擴充方法註冊服務群組

ASP.NET 核心框架中註冊相關服務群組的慣例是使用單一 Add{GROUP NAME} 擴充方法來註冊框架功能所需的所有服務, {GROUP NAME} 佔位符為描述性群組名稱。 例如,AddRazorComponents 擴充方法會註冊用於伺服器端渲染 Razor 元件所需的服務。

請考慮以下範例,該範例用於配置選項並註冊服務:

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();
services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

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

相關註冊群組可移至擴充方法,以註冊服務。 在以下範例中:

  • AddConfig擴充方法將設定資料綁定到強型別的 C# 類別,並將這些類別登錄在服務容器中。
  • AddDependencyGroup擴充方法則增加了額外的類別(服務)相依關係。
namespace Microsoft.Extensions.DependencyInjection;

public static class ConfigServiceCollectionExtensions
{
    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;
    }

    public static IServiceCollection AddDependencyGroup(
        this IServiceCollection services)
    {
        services.AddScoped<IMyDependency, MyDependency>();
        services.AddScoped<IMyDependency2, MyDependency2>();

        return services;
    }
}

以下程式碼呼叫 AddConfig 前述及 AddDependencyGroup 擴充方法來註冊服務:

builder.Services
    .AddConfig(builder.Configuration)
    .AddDependencyGroup();
services
    .AddConfig(builder.Configuration)
    .AddDependencyGroup();

我們建議應用程式遵循命名慣例,在命名空間中建立擴充方法 Microsoft.Extensions.DependencyInjection ,這些方法包括:

  • 封裝服務註冊群組。
  • 提供便利的 IntelliSense 存取來使用服務。

使用壽命

關於服務生命週期的一般指引,請參見 《服務期限》。 關於適用於 Blazor 應用程式的額外服務生命週期指南,請參見 ASP.NET Core Blazor 相依性注入

若要在中介軟體中使用限定範圍服務,請使用下列其中一種方法:

  • 將服務插入中介軟體的 InvokeInvokeAsync 方法。 使用建構函式注入會拋出運行時例外,因為它會強制限定範圍的服務行為像單例一樣。 存留期和註冊選項一節中的範例示範 InvokeAsync 方法。
  • 使用 基於工廠的中介軟體。 使用此方法註冊的中介軟體會根據用戶端要求 (連線) 啟動,以允許將限定範圍服務插入中介軟體的建構函式中。

如需詳細資訊,請參閱以下資源:

金鑰服務

有鑰匙的服務 會用鑰匙註冊和檢索服務。 透過呼叫以下任一擴充方法進行服務註冊,可將服務與金鑰關聯:

以下範例展示了以下 API 的鍵控服務:

  • IStringCache 是帶有 Get 方法簽章的服務介面。
  • StringCache1StringCache2 是 的 IStringCache具體服務實作。

Interfaces/IStringCache.cs

public interface IStringCache
{
    string Get(int key);
}

Services/StringCache1.cs

public class StringCache1 : IStringCache
{
    public string Get(int key) => $"Resolving {key} from StringCache1.";
}

Services/StringCache2.cs

public class StringCache2 : IStringCache
{
    public string Get(int key) => $"Resolving {key} from StringCache2.";
}

透過指定帶有 [FromKeyedServices] 屬性的金鑰來存取註冊服務。

檔案中的 Program 鍵控服務與範例 Minimal API 端點:

builder.Services.AddKeyedSingleton<IStringCache, StringCache1>("cache1");
builder.Services.AddKeyedSingleton<IStringCache, StringCache2>("cache2");

...

app.MapGet("/cache1", ([FromKeyedServices("cache1")] IStringCache stringCache1) => 
    stringCache1.Get(1));
app.MapGet("/cache2", ([FromKeyedServices("cache2")] IStringCache stringCache2) =>
    stringCache2.Get(2));

在 Razor 組件(Pages/KeyedServicesExample.razor)中使用 [Inject] 屬性的範例。 使用 InjectAttribute.Key 屬性來指定服務要插入的索引鍵:

@page "/keyed-services-example"

@Cache?.Get(3)

@code {
    [Inject(Key = "cache1")]
    public IStringCache? Cache { get; set; }
}

欲了解更多關於將 Razor 元件與鍵控服務一起使用的資訊,請參見 ASP.NET Core Blazor 相依性注入

在帶有主要建構子注入的 SignalR 集線器(Hubs/MyHub1.cs)中範例使用:

using Microsoft.AspNetCore.SignalR;

public class MyHub1([FromKeyedServices("cache2")] IStringCache cache) : Hub
{
    public void Method()
    {
        Console.WriteLine(cache.Get(4));
    }
}

在集線器 SignalR 中以方法注入的範例(Hubs/MyHub2.cs):

using Microsoft.AspNetCore.SignalR;

public class MyHub2 : Hub
{
    public void Method([FromKeyedServices("cache2")] IStringCache cache)
    {
        Console.WriteLine(cache.Get(5));
    }
}

中介軟體在中介軟體的構造子及其 Invoke/InvokeAsync 方法中支援有鍵化服務:

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("cache1")] IStringCache cache)
    {
        _next = next;

        Console.WriteLine(cache.Get(6));
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("cache2")] IStringCache cache)
        {
            Console.WriteLine(cache.Get(7));

            return _next(context);
        } 
}

在檔案(.NET 6 或更新版本)或Program方法(.NET 5 或更早版本)的Startup.Configure應用程式處理流程中:

app.UseMiddleware<MyMiddleware>();

欲了解更多關於建立中介軟體的資訊,請參閱 撰寫自訂 ASP.NET 核心中介軟體

建構函式插入行為

欲了解更多關於建構子注入行為的資訊,請參閱以下資源:

Entity Framework 內容

關於伺服器端應用程式的指引EF Core,請參見 EF Core。

根據預設,因為會將 Web 應用程式資料庫作業範圍設定為用戶端要求,所以通常會使用具範圍存留期將 Entity Framework 內容新增至服務容器。 若要使用不同的存留期,請使用 AddDbContext 多載來指定存留期。 指定存留期的服務不應該使用存留期比服務存留期還短的資料庫內容。

留期和註冊選項

為了示範這些服務存留期與註冊選項之間的差異,請考慮下列介面,以具有識別碼 OperationId 的「作業」代表工作。 根據以下介面的操作的服務壽命配置,當類別請求服務時,容器會提供相同或不同的服務實例。

IOperation.cs

public interface IOperation
{
    string OperationId { get; }
}

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

下列 Operation 類別會實作上述所有介面。 Operation建構子產生一個 GUID,並將屬性中OperationId最後四個字元儲存。

Operation.cs

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

    public string OperationId { get; }
}

以下程式碼會根據命名的生命週期為Operation類別建立多個註冊。

服務註冊地點:

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();

以下範例展示了物件生命週期,涵蓋請求內及請求間的變化。 Operation元件和中介軟體會請求每種IOperation類型,並為每個類型記錄OperationId

Pages/OperationExample.razor

@page "/operation-example"
@inject IOperationTransient TransientOperation
@inject IOperationScoped ScopedOperation
@inject IOperationSingleton SingletonOperation

<ul>
    <li>Transient: @TransientOperation.OperationId</li>
    <li>Scoped: @ScopedOperation.OperationId</li>
    <li>Singleton: @SingletonOperation.OperationId</li>
</ul>

以下範例展示了物件生命週期,涵蓋請求內及請求間的變化。 IndexModel和中介軟體會請求每種類型IOperation,並為每個類型記錄OperationId

IndexModel.cshtml.cs

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

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

中介軟體也能解析並使用相同的服務。 範圍化服務與暫態服務必須在InvokeAsync方法中解析。

MyMiddleware.cs

public class MyMiddleware(ILogger<IndexModel> logger,
    IOperationSingleton singletonOperation)
{
    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        logger.LogInformation($"Transient: {transientOperation.OperationId}");
        logger.LogInformation($"Scoped: {scopedOperation.OperationId}");
        logger.LogInformation($"Singleton: {singletonOperation.OperationId}");

        await _next(context);
    }
}
public class MyMiddleware
{
    private readonly ILogger<IndexModel> _logger;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(ILogger<IndexModel> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation($"Transient: {transientOperation.OperationId}");
        _logger.LogInformation($"Scoped: {scopedOperation.OperationId}");
        _logger.LogInformation($"Singleton: {_singletonOperation.OperationId}");

        await _next(context);
    }
}

Program檔案(.NET 6 或更新版本)或Startup.Configure方法(.NET 5 或更早版本)的應用程式處理流程中:

app.UseMiddleware<MyMiddleware>();

欲了解更多關於建立中介軟體的資訊,請參閱 撰寫自訂 ASP.NET 核心中介軟體

前述範例的輸出結果如下:

  • 「暫時性」 物件一律不同。 元件 Razor 和中介軟體的瞬態 OperationId 值不同。
  • 有作用域 的物件對特定請求相同,但在新 Blazor 電路間有所不同。
  • 單例 物件對每個請求或 Blazor 電路都是相同的。
  • 「暫時性」 物件一律不同。 頁面和中介軟體的瞬態 OperationId 值是不同的。
  • 作用域 物件對特定請求相同,但不同新請求之間有所不同。
  • 對於每個請求而言,Singleton物件都是相同的。

解決應用程式啟動時的服務

下列程式碼示範如何在應用程式啟動時,在有限的持續時間內解析限定範圍服務:

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;
    var dependency = services.GetRequiredService<IMyDependency>();
    dependency.WriteMessage("Call services from main");
}

範圍驗證

有關範疇驗證的指引,請參閱以下資源:

申請服務

服務及其在 ASP.NET Core 要求內的相依性會透過 HttpContext.RequestServices 公開。

架構會為每個要求建立一個範圍,且 RequestServices 會公開限定範圍的服務提供者。 只要要求為使用中,所有限定範圍服務就都有效。

Note

偏好要求相依性做為建構函式參數,而不是從 RequestServices 解析服務。 要求相依性做為建構函式參數會產生更容易測試的類別。

相依注入的設計服務

設計DI服務時:

  • 避免具有狀態的靜態類別和成員。 避免設計應用程式改用 singleton 服務來建立全域狀態。
  • 避免直接在服務內具現化相依類別。 直接具現化會將程式碼耦合到特定實作。
  • 讓服務維持在小型、情況良好且可輕鬆測試的狀態。

如果類別有許多插入的相依性,則可能表示類別有太多責任,並違反 單一責任原則 (SRP)。 嘗試通過將某些責任移到新類別中來重構類別。 請記住,Razor Pages 頁面模型類別與 MVC 控制器類別應該專注於 UI 考量。

服務的處置

容器會為它建立的 Dispose 類型呼叫 IDisposable。 開發人員永遠不會處置從容器所解析的服務。 如果類型或工廠已註冊為 "singleton",則容器會自動處置該 "singleton"。

在以下範例中,服務由服務容器建立並自動處置。

Services/Service1.cs

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;

        GC.SuppressFinalize(this);
    }
}

Services/Service2.cs

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;

        GC.SuppressFinalize(this);
    }
}

Services/Service3.cs

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

public class Service3(string myKey) : IService3, IDisposable
{
    private bool _disposed;

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

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

        Console.WriteLine("Service3.Dispose");
        _disposed = true;

        GC.SuppressFinalize(this);
    }
}

appsettings.Development.json 中:

"Key": "Value from appsettings.Development.json"

應用程式註冊服務的地方:

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var key = builder.Configuration["Key"] ?? string.Empty;
builder.Services.AddSingleton<IService3>(sp => new Service3(key));
services.AddScoped<Service1>();
services.AddSingleton<Service2>();

var myKey = builder.Configuration["Key"] ?? string.Empty;
services.AddSingleton<IService3>(sp => new Service3(myKey));

Pages/DisposalExample.razor

@page "/disposal-example"
@inject Service1 Service1
@inject Service2 Service2
@inject IService3 Service3

@code {
    protected override void OnInitialized()
    {
        Service1.Write("DisposalExample.OnInitialized");
        Service2.Write("DisposalExample.OnInitialized");
        Service3.Write("DisposalExample.OnInitialized");
    }
}

偵錯控制台在每次重新載入索引頁面之後會顯示下列輸出:

Service1: DisposalExample.OnInitialized
Service2: DisposalExample.OnInitialized
Service3: DisposalExample.OnInitialized, Key = Value from appsettings.Development.json
Service1.Dispose

要查看處置條目,請從 Service1 元件移出以觸發其處置。

Pages/Index.cshtml.cs

public class IndexModel(
    Service1 service1, Service2 service2, IService3 service3) 
    : PageModel
{
    public void OnGet()
    {
        service1.Write("IndexModel.OnGet");
        service2.Write("IndexModel.OnGet");
        service3.Write("IndexModel.OnGet");
    }
}

偵錯控制台在每次重新載入索引頁面之後會顯示下列輸出:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, Key = Value from appsettings.Development.json
Service1.Dispose

服務容器未建立的服務

請考慮下列程式碼:

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
services.AddSingleton(new Service1());
services.AddSingleton(new Service2());

針對上述程式碼:

  • 服務執行個體不是由服務容器建立的。
  • 架構不會自動處置服務。
  • 開發商負責處理這些服務。

IDisposable 對於暫態與共享實例的指導方針

欲了解更多資訊,請參閱 .NET 中的依賴注入:針對瞬態與共享實例的 IDisposable 指引

預設服務容器的替代

更多資訊請參閱 .NET 中的相依注入:預設服務容器替換

Recommendations

欲了解更多資訊,請參閱 依賴注入指引:建議

避免使用「服務定位器模式」。 例如,當您可以改用 DI 時,請勿叫用 GetService 來取得服務執行個體:

Incorrect:

代碼不正確

Correct:

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

        ...
    }
}

另一個要避免的服務定位器變化是插入一個在執行階段解析相依性的工廠。 這兩種做法都會混用控制反轉策略。

避免靜態存取( HttpContext 例如, IHttpContextAccessor.HttpContext)。

DI 是靜態/全域物件存取模式的「替代」選項。 如果您將其與靜態物件存取混合,可能無法實現 DI 的優點。

Orchard Core 是一個用於在 ASP.NET Core 上建構模組化多租戶應用程式的應用程式框架。 如需詳細資訊,請參閱 Orchard Core 文件

關於如何僅使用 Orchard Core Framework 而非其 CMS 專屬功能,建立模組化及多租戶應用程式的範例,請參閱 Orchard Core 範例

架構提供的服務

檔案 Program (.NET 6 或更新版本)或 Startup 檔案(.NET 5 或更早版本)會登錄應用程式所使用的服務,包括平台功能,例如 Entity Framework Core 及支援 RazorBlazor 元件的服務(.NET 8 或更新版本)。 最初,框架 IServiceCollection 會根據 主機的設定方式定義服務。 針對以 ASP.NET Core 範本為基礎的應用程式,架構會註冊超過 250 個服務。

下表描述了一小部分框架註冊服務:

服務類型 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

其他資源