在 ASP.NET Core 依賴(lài)注入

2019-04-17 08:58 更新

ASP.NET Core 支持依賴(lài)關(guān)系注入 (DI) 軟件設(shè)計(jì)模式,這是一種在類(lèi)及其依賴(lài)關(guān)系之間實(shí)現(xiàn)控制反轉(zhuǎn) (IoC) 的技術(shù)。

有關(guān)特定于 MVC 控制器中依賴(lài)關(guān)系注入的詳細(xì)信息,請(qǐng)參閱 在 ASP.NET Core 中將依賴(lài)項(xiàng)注入到控制器。

查看或下載示例代碼如何下載

依賴(lài)關(guān)系注入概述

依賴(lài)項(xiàng)是另一個(gè)對(duì)象所需的任何對(duì)象。 使用應(yīng)用中其他類(lèi)所依賴(lài)的 WriteMessage 方法檢查以下 MyDependency 類(lèi):

C#

public class MyDependency
{
    public MyDependency()
    {
    }

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

        return Task.FromResult(0);
    }
}

可以創(chuàng)建 MyDependency 類(lèi)的實(shí)例以使 WriteMessage 方法可用于類(lèi)。 MyDependency 類(lèi)是 IndexModel 類(lèi)的依賴(lài)項(xiàng):

C#

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

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

該類(lèi)創(chuàng)建并直接依賴(lài)于 MyDependency 實(shí)例。 代碼依賴(lài)關(guān)系(如前面的示例)存在問(wèn)題,應(yīng)該避免使用,原因如下:

  • 要用不同的實(shí)現(xiàn)替換 MyDependency,必須修改類(lèi)。
  • 如果 MyDependency 具有依賴(lài)關(guān)系,則必須由類(lèi)對(duì)其進(jìn)行配置。 在具有多個(gè)依賴(lài)于 MyDependency 的類(lèi)的大型項(xiàng)目中,配置代碼在整個(gè)應(yīng)用中會(huì)變得分散。
  • 這種實(shí)現(xiàn)很難進(jìn)行單元測(cè)試。 應(yīng)用應(yīng)使用模擬或存根 MyDependency 類(lèi),該類(lèi)不能使用此方法。

依賴(lài)關(guān)系注入通過(guò)以下方式解決了這些問(wèn)題:

  • 使用接口抽象化依賴(lài)關(guān)系實(shí)現(xiàn)。
  • 注冊(cè)服務(wù)容器中的依賴(lài)關(guān)系。 ASP.NET Core 提供了一個(gè)內(nèi)置的服務(wù)容器 IServiceProvider。 服務(wù)已在應(yīng)用的 Startup.ConfigureServices 方法中注冊(cè)。
  • 將服務(wù)注入到使用它的類(lèi)的構(gòu)造函數(shù)中。 框架負(fù)責(zé)創(chuàng)建依賴(lài)關(guān)系的實(shí)例,并在不再需要時(shí)對(duì)其進(jìn)行處理。

示例應(yīng)用中,IMyDependency 接口定義了服務(wù)為應(yīng)用提供的方法:

C#

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

此接口由具體類(lèi)型 MyDependency 實(shí)現(xiàn):

C#

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 在其構(gòu)造函數(shù)中請(qǐng)求 ILogger<TCategoryName>。 以鏈?zhǔn)椒绞绞褂靡蕾?lài)關(guān)系注入并不罕見(jiàn)。 每個(gè)請(qǐng)求的依賴(lài)關(guān)系相應(yīng)地請(qǐng)求其自己的依賴(lài)關(guān)系。 容器解析圖中的依賴(lài)關(guān)系并返回完全解析的服務(wù)。 必須被解析的依賴(lài)關(guān)系的集合通常被稱(chēng)為“依賴(lài)關(guān)系樹(shù)”、“依賴(lài)關(guān)系圖”或“對(duì)象圖”。

必須在服務(wù)容器中注冊(cè) IMyDependency 和 ILogger<TCategoryName>。 IMyDependency 已在 Startup.ConfigureServices 中注冊(cè)。 ILogger<TCategoryName> 由日志記錄抽象基礎(chǔ)結(jié)構(gòu)注冊(cè),因此它是框架默認(rèn)注冊(cè)的框架提供的服務(wù)。

容器通過(guò)利用(泛型)開(kāi)放類(lèi)型解析 ILogger<TCategoryName>,而無(wú)需注冊(cè)每個(gè)(泛型)構(gòu)造類(lèi)型

C#

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

在示例應(yīng)用中,使用具體類(lèi)型 MyDependency 注冊(cè) IMyDependency 服務(wù)。 注冊(cè)將服務(wù)生存期的范圍限定為單個(gè)請(qǐng)求的生存期。 本主題后面將介紹服務(wù)生存期。

C#

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

 備注

每個(gè) services.Add{SERVICE_NAME} 擴(kuò)展方法添加(并可能配置)服務(wù)。 例如,services.AddMvc() 添加 Razor Pages 和 MVC 需要的服務(wù)。 我們建議應(yīng)用遵循此約定。 將擴(kuò)展方法置于 Microsoft.Extensions.DependencyInjection 命名空間中以封裝服務(wù)注冊(cè)的組。

如果服務(wù)的構(gòu)造函數(shù)需要內(nèi)置類(lèi)型(如 string),則可以使用配置選項(xiàng)模式注入該類(lèi)型:

C#

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

        // Use myStringValue
    }

    ...
}

通過(guò)使用服務(wù)并分配給私有字段的類(lèi)的構(gòu)造函數(shù)請(qǐng)求服務(wù)的實(shí)例。 該字段用于在整個(gè)類(lèi)中根據(jù)需要訪(fǎng)問(wèn)服務(wù)。

在示例應(yīng)用中,請(qǐng)求 IMyDependency 實(shí)例并用于調(diào)用服務(wù)的 WriteMessage 方法:

C#

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

框架提供的服務(wù)

Startup.ConfigureServices 方法負(fù)責(zé)定義應(yīng)用使用的服務(wù),包括 Entity Framework Core 和 ASP.NET Core MVC 等平臺(tái)功能。 最初,提供給 ConfigureServices 的 IServiceCollection 定義了以下服務(wù)(具體取決于配置主機(jī)的方式):

服務(wù)類(lèi)型生存期
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory暫時(shí)
Microsoft.AspNetCore.Hosting.IApplicationLifetime單例
Microsoft.AspNetCore.Hosting.IHostingEnvironment單例
Microsoft.AspNetCore.Hosting.IStartup單例
Microsoft.AspNetCore.Hosting.IStartupFilter暫時(shí)
Microsoft.AspNetCore.Hosting.Server.IServer單例
Microsoft.AspNetCore.Http.IHttpContextFactory暫時(shí)
Microsoft.Extensions.Logging.ILogger<T>單例
Microsoft.Extensions.Logging.ILoggerFactory單例
Microsoft.Extensions.ObjectPool.ObjectPoolProvider單一實(shí)例
Microsoft.Extensions.Options.IConfigureOptions<T>暫時(shí)
Microsoft.Extensions.Options.IOptions<T>單一實(shí)例
System.Diagnostics.DiagnosticSource單一實(shí)例
System.Diagnostics.DiagnosticListener單例

當(dāng)服務(wù)集合擴(kuò)展方法可用于注冊(cè)服務(wù)(及其依賴(lài)服務(wù),如果需要)時(shí),約定使用單個(gè) Add{SERVICE_NAME} 擴(kuò)展方法來(lái)注冊(cè)該服務(wù)所需的所有服務(wù)。 以下代碼是如何使用擴(kuò)展方法 AddDbContextAddIdentity 和 AddMvc 向容器添加其他服務(wù)的示例:

C#

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

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

    services.AddMvc();
}

有關(guān)詳細(xì)信息,請(qǐng)參閱 API 文檔中的 ServiceCollection 類(lèi)。

服務(wù)生存期

為每個(gè)注冊(cè)的服務(wù)選擇適當(dāng)?shù)纳嫫凇?nbsp;可以使用以下生存期配置 ASP.NET Core 服務(wù):

暫時(shí)

暫時(shí)生存期服務(wù)是每次從服務(wù)容器進(jìn)行請(qǐng)求時(shí)創(chuàng)建的。 這種生存期適合輕量級(jí)、 無(wú)狀態(tài)的服務(wù)。

作用域(Scoped)

作用域生存期服務(wù)以每個(gè)客戶(hù)端請(qǐng)求(連接)一次的方式創(chuàng)建。

 警告

在中間件內(nèi)使用有作用域的服務(wù)時(shí),請(qǐng)將該服務(wù)注入至 Invoke 或 InvokeAsync 方法。 請(qǐng)不要通過(guò)構(gòu)造函數(shù)注入進(jìn)行注入,因?yàn)樗鼤?huì)強(qiáng)制服務(wù)的行為與單一實(shí)例類(lèi)似。 有關(guān)更多信息,請(qǐng)參見(jiàn)ASP.NET Core 中間件

單例

單一實(shí)例生存期服務(wù)是在第一次請(qǐng)求時(shí)(或者在運(yùn)行 ConfigureServices 并且使用服務(wù)注冊(cè)指定實(shí)例時(shí))創(chuàng)建的。 每個(gè)后續(xù)請(qǐng)求都使用相同的實(shí)例。 如果應(yīng)用需要單一實(shí)例行為,建議允許服務(wù)容器管理服務(wù)的生存期。 不要實(shí)現(xiàn)單一實(shí)例設(shè)計(jì)模式并提供用戶(hù)代碼來(lái)管理對(duì)象在類(lèi)中的生存期。

 警告

從單一實(shí)例解析有作用域的服務(wù)很危險(xiǎn)。 當(dāng)處理后續(xù)請(qǐng)求時(shí),它可能會(huì)導(dǎo)致服務(wù)處于不正確的狀態(tài)。

構(gòu)造函數(shù)注入行為

服務(wù)可以通過(guò)兩種機(jī)制來(lái)解析:

  • IServiceProvider
  • ActivatorUtilities – 允許在依賴(lài)關(guān)系注入容器中創(chuàng)建沒(méi)有服務(wù)注冊(cè)的對(duì)象。 ActivatorUtilities 用于面向用戶(hù)的抽象,例如標(biāo)記幫助器、MVC 控制器和模型綁定器。

構(gòu)造函數(shù)可以接受依賴(lài)關(guān)系注入不提供的參數(shù),但參數(shù)必須分配默認(rèn)值。

當(dāng)服務(wù)由 IServiceProvider 或 ActivatorUtilities 解析時(shí),構(gòu)造函數(shù)注入需要 public 構(gòu)造函數(shù)。

當(dāng)服務(wù)由 ActivatorUtilities 解析時(shí),構(gòu)造函數(shù)注入要求只存在一個(gè)適用的構(gòu)造函數(shù)。 支持構(gòu)造函數(shù)重載,但其參數(shù)可以全部通過(guò)依賴(lài)注入來(lái)實(shí)現(xiàn)的重載只能存在一個(gè)。

實(shí)體框架上下文

通常使用設(shè)置了范圍的生存期將實(shí)體框架上下文添加到服務(wù)容器中,因?yàn)?Web 應(yīng)用數(shù)據(jù)庫(kù)操作通常將范圍設(shè)置為客戶(hù)端請(qǐng)求。 如果在注冊(cè)數(shù)據(jù)庫(kù)上下文時(shí),AddDbContext<TContext> 重載未指定生存期,則設(shè)置默認(rèn)生存期范圍。 給定生存期的服務(wù)不應(yīng)使用生存期比服務(wù)短的數(shù)據(jù)庫(kù)上下文。

生存期和注冊(cè)選項(xiàng)

為了演示生存期和注冊(cè)選項(xiàng)之間的差異,請(qǐng)考慮以下接口,將任務(wù)表示為具有唯一標(biāo)識(shí)符 OperationId 的操作。 根據(jù)為以下接口配置操作服務(wù)的生存期的方式,容器在類(lèi)請(qǐng)求時(shí)提供相同或不同的服務(wù)實(shí)例:

C#

public interface IOperation
{
    Guid OperationId { get; }
}

public interface IOperationTransient : IOperation
{
}

public interface IOperationScoped : IOperation
{
}

public interface IOperationSingleton : IOperation
{
}

public interface IOperationSingletonInstance : IOperation
{
}

接口在 Operation 類(lèi)中實(shí)現(xiàn)。 Operation 構(gòu)造函數(shù)將生成一個(gè) GUID(如果未提供):

C#

public class Operation : IOperationTransient, 
    IOperationScoped, 
    IOperationSingleton, 
    IOperationSingletonInstance
{
    public Operation() : this(Guid.NewGuid())
    {
    }

    public Operation(Guid id)
    {
        OperationId = id;
    }

    public Guid OperationId { get; private set; }
}

注冊(cè) OperationService 取決于,每個(gè)其他 Operation 類(lèi)型。 當(dāng)通過(guò)依賴(lài)關(guān)系注入請(qǐng)求 OperationService 時(shí),它將接收每個(gè)服務(wù)的新實(shí)例或基于從屬服務(wù)的生存期的現(xiàn)有實(shí)例。

  • 如果從容器請(qǐng)求時(shí)創(chuàng)建了臨時(shí)服務(wù),則 IOperationTransient 服務(wù)的 OperationId 與 OperationService 的 OperationId 不同。 OperationService 將接收 IOperationTransient 類(lèi)的新實(shí)例。 新實(shí)例將生成一個(gè)不同的 OperationId。
  • 如果按客戶(hù)端請(qǐng)求創(chuàng)建有作用域的服務(wù),則 IOperationScoped 服務(wù)的 OperationId 與客戶(hù)端請(qǐng)求中 OperationService 的該 ID 相同。 在客戶(hù)端請(qǐng)求中,兩個(gè)服務(wù)共享不同的 OperationId 值。
  • 如果單一數(shù)據(jù)庫(kù)和單一實(shí)例服務(wù)只創(chuàng)建一次并在所有客戶(hù)端請(qǐng)求和所有服務(wù)中使用,則 OperationId 在所有服務(wù)請(qǐng)求中保持不變。

C#

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 中,根據(jù)其指定的生存期,將每個(gè)類(lèi)型添加到容器中:

C#

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 服務(wù)正在使用已知 ID 為 Guid.Empty 的特定實(shí)例。 此類(lèi)型在使用時(shí)很明顯(其 GUID 全部為零)。

示例應(yīng)用演示了各個(gè)請(qǐng)求中和之間的對(duì)象生存期。 示例應(yīng)用的 IndexModel 請(qǐng)求每種 IOperation 類(lèi)型和 OperationService。 然后,頁(yè)面通過(guò)屬性分配顯示所有頁(yè)面模型類(lèi)和服務(wù)的 OperationId 值:

C#

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

以下兩個(gè)輸出顯示了兩個(gè)請(qǐng)求的結(jié)果:

第一個(gè)請(qǐng)求:

控制器操作:

暫時(shí)性:d233e165-f417-469b-a866-1cf1935d2518作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19單一實(shí)例:01271bc1-9e31-48e7-8f7c-7261b040ded9實(shí)例:00000000-0000-0000-0000-000000000000

OperationService 操作:

暫時(shí)性:c6b049eb-1318-4e31-90f1-eb2dd849ff64作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19單一實(shí)例:01271bc1-9e31-48e7-8f7c-7261b040ded9實(shí)例:00000000-0000-0000-0000-000000000000

第二個(gè)請(qǐng)求:

控制器操作:

暫時(shí)性:b63bd538-0a37-4ff1-90ba-081c5138dda0作用域:31e820c5-4834-4d22-83fc-a60118acb9f4單一實(shí)例:01271bc1-9e31-48e7-8f7c-7261b040ded9實(shí)例:00000000-0000-0000-0000-000000000000

OperationService 操作:

暫時(shí)性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf作用域:31e820c5-4834-4d22-83fc-a60118acb9f4單一實(shí)例:01271bc1-9e31-48e7-8f7c-7261b040ded9實(shí)例:00000000-0000-0000-0000-000000000000

觀察哪個(gè) OperationId 值會(huì)在一個(gè)請(qǐng)求之內(nèi)和不同請(qǐng)求之間變化:

  • 暫時(shí)性對(duì)象始終不同。 第一個(gè)和第二個(gè)客戶(hù)端請(qǐng)求的暫時(shí)性 OperationId 值對(duì)于 OperationService 操作和在客戶(hù)端請(qǐng)求內(nèi)都是不同的。 為每個(gè)服務(wù)請(qǐng)求和客戶(hù)端請(qǐng)求提供了一個(gè)新實(shí)例。
  • 作用域?qū)ο笤谝粋€(gè)客戶(hù)端請(qǐng)求中是相同的,但在多個(gè)客戶(hù)端請(qǐng)求中是不同的。
  • 單一實(shí)例對(duì)象對(duì)每個(gè)對(duì)象和每個(gè)請(qǐng)求都是相同的(不管 ConfigureServices 中是否提供 Operation 實(shí)例)。

從 main 調(diào)用服務(wù)

采用 IServiceScopeFactory.CreateScope 創(chuàng)建 IServiceScope ,以解析應(yīng)用作用域中有作用域的服務(wù)。此方法可以用于在啟動(dòng)時(shí)訪(fǎng)問(wèn)有作用域的服務(wù)以便運(yùn)行初始化任務(wù)。 以下示例演示如何在 Program.Main 中獲取 MyScopedService 的上下文:

C#

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

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

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

    host.Run();
}

作用域驗(yàn)證

如果在開(kāi)發(fā)環(huán)境中運(yùn)行應(yīng)用,默認(rèn)的服務(wù)提供程序會(huì)執(zhí)行檢查,從而確認(rèn)以下內(nèi)容:

  • 沒(méi)有從根服務(wù)提供程序直接或間接解析到有作用域的服務(wù)。
  • 未將有作用域的服務(wù)直接或間接注入到單一實(shí)例。

調(diào)用 BuildServiceProvider 時(shí),會(huì)創(chuàng)建根服務(wù)提供程序。 在啟動(dòng)提供程序和應(yīng)用時(shí),根服務(wù)提供程序的生存期對(duì)應(yīng)于應(yīng)用/服務(wù)的生存期,并在關(guān)閉應(yīng)用時(shí)釋放。

有作用域的服務(wù)由創(chuàng)建它們的容器釋放。 如果作用域創(chuàng)建于根容器,則該服務(wù)的生存會(huì)有效地提升至單一實(shí)例,因?yàn)楦萜髦粫?huì)在應(yīng)用/服務(wù)關(guān)閉時(shí)將其釋放。 驗(yàn)證服務(wù)作用域,將在調(diào)用 BuildServiceProvider 時(shí)收集這類(lèi)情況。

有關(guān)更多信息,請(qǐng)參見(jiàn)ASP.NET Core Web 主機(jī)。

請(qǐng)求服務(wù)

來(lái)自 HttpContext 的 ASP.NET Core 請(qǐng)求中可用的服務(wù)通過(guò) HttpContext.RequestServices 集合公開(kāi)。

請(qǐng)求服務(wù)表示作為應(yīng)用的一部分配置和請(qǐng)求的服務(wù)。 當(dāng)對(duì)象指定依賴(lài)關(guān)系時(shí),RequestServices(而不是 ApplicationServices)中的類(lèi)型將滿(mǎn)足這些要求。

通常,應(yīng)用不應(yīng)直接使用這些屬性。 相反,通過(guò)類(lèi)構(gòu)造函數(shù)請(qǐng)求類(lèi)所需的類(lèi)型,并允許框架注入依賴(lài)關(guān)系。 這樣生成的類(lèi)更易于測(cè)試。

 備注

與訪(fǎng)問(wèn) RequestServices 集合相比,以構(gòu)造函數(shù)參數(shù)的形式請(qǐng)求依賴(lài)項(xiàng)是更優(yōu)先的選擇。

設(shè)計(jì)能夠進(jìn)行依賴(lài)關(guān)系注入的服務(wù)

最佳做法是:

  • 設(shè)計(jì)服務(wù)以使用依賴(lài)關(guān)系注入來(lái)獲取其依賴(lài)關(guān)系。
  • 避免進(jìn)行有狀態(tài)的靜態(tài)方法調(diào)用。
  • 避免在服務(wù)中直接實(shí)例化依賴(lài)類(lèi)。 直接實(shí)例化將代碼耦合到特定實(shí)現(xiàn)。
  • 不在應(yīng)用類(lèi)中包含過(guò)多內(nèi)容,確保設(shè)計(jì)規(guī)范,并易于測(cè)試。

如果一個(gè)類(lèi)似乎有過(guò)多的注入依賴(lài)關(guān)系,這通常表明該類(lèi)擁有過(guò)多的責(zé)任并且違反了單一責(zé)任原則 (SRP)。 嘗試通過(guò)將某些職責(zé)移動(dòng)到一個(gè)新類(lèi)來(lái)重構(gòu)類(lèi)。 請(qǐng)記住,Razor Pages 頁(yè)模型類(lèi)和 MVC 控制器類(lèi)應(yīng)關(guān)注用戶(hù)界面問(wèn)題。 業(yè)務(wù)規(guī)則和數(shù)據(jù)訪(fǎng)問(wèn)實(shí)現(xiàn)細(xì)節(jié)應(yīng)保留在適用于這些分離的關(guān)注點(diǎn)的類(lèi)中。

服務(wù)處理

容器為其創(chuàng)建的 IDisposable 類(lèi)型調(diào)用 Dispose。 如果通過(guò)用戶(hù)代碼將實(shí)例添加到容器中,則不會(huì)自動(dòng)處理該實(shí)例。

C#

// Services that implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    // The container creates the following instances and disposes them automatically:
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation());

    // The container doesn't create the following instances, so it doesn't dispose of
    // the instances automatically:
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

默認(rèn)服務(wù)容器替換

內(nèi)置的服務(wù)容器旨在滿(mǎn)足框架和大多數(shù)消費(fèi)者應(yīng)用的需求。 我們建議使用內(nèi)置容器,除非你需要的特定功能不受它支持。 內(nèi)置容器中找不到第三方容器支持的某些功能:

  • 屬性注入
  • 基于名稱(chēng)的注入
  • 子容器
  • 自定義生存期管理
  • 對(duì)遲緩初始化的 Func<T> 支持

有關(guān)支持適配器的部分容器列表,請(qǐng)參閱依賴(lài)關(guān)系注入 readme.md 文件。

以下示例將內(nèi)置容器替換為 Autofac

  • 安裝適當(dāng)?shù)娜萜靼?a rel="external nofollow" target="_blank" >AutofacAutofac.Extensions.DependencyInjection
  • 在 Startup.ConfigureServices 中配置容器并返回 IServiceProvider:C#復(fù)制public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); // Add other framework services // Add Autofac var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterModule<DefaultModule>(); containerBuilder.Populate(services); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); } 要使用第三方容器,Startup.ConfigureServices 必須返回 IServiceProvider。
  • 在 DefaultModule 中配置 Autofac:C#復(fù)制public class DefaultModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<CharacterRepository>().As<ICharacterRepository>(); } }

在運(yùn)行時(shí),使用 Autofac 來(lái)解析類(lèi)型,并注入依賴(lài)關(guān)系。 要了解有關(guān)結(jié)合使用 Autofac 和 ASP.NET Core 的詳細(xì)信息,請(qǐng)參閱 Autofac 文檔

線(xiàn)程安全

創(chuàng)建線(xiàn)程安全的單一實(shí)例服務(wù)。 如果單例服務(wù)依賴(lài)于一個(gè)瞬時(shí)服務(wù),那么瞬時(shí)服務(wù)可能也需要線(xiàn)程安全,具體取決于單例使用它的方式。

單個(gè)服務(wù)的工廠方法,例如 AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>) 的第二個(gè)參數(shù),不需要是線(xiàn)程安全的。 像類(lèi)型 (static) 構(gòu)造函數(shù)一樣,它保證由單個(gè)線(xiàn)程調(diào)用一次。

建議

  • 不支持基于 async/await 和 Task 的服務(wù)解析。 C# 不支持異步構(gòu)造函數(shù),因此推薦的模式是在同步解析服務(wù)后使用異步方法。
  • 避免在服務(wù)容器中直接存儲(chǔ)數(shù)據(jù)和配置。 例如,用戶(hù)的購(gòu)物車(chē)通常不應(yīng)添加到服務(wù)容器中。 配置應(yīng)使用 選項(xiàng)模型。 同樣,避免"數(shù)據(jù)持有者"對(duì)象,也就是僅僅為實(shí)現(xiàn)對(duì)某些其他對(duì)象的訪(fǎng)問(wèn)而存在的對(duì)象。 最好通過(guò) DI 請(qǐng)求實(shí)際項(xiàng)目。
  • 避免靜態(tài)訪(fǎng)問(wèn)服務(wù)(例如,靜態(tài)鍵入 IApplicationBuilder.ApplicationServices 以便在其他地方使用)。
  • 避免使用服務(wù)定位器模式。 例如,可以改為使用 DI 時(shí),不要調(diào)用 GetService 來(lái)獲取服務(wù)實(shí)例。 要避免的另一個(gè)服務(wù)定位器變體是注入可在運(yùn)行時(shí)解析依賴(lài)項(xiàng)的工廠。 這兩種做法混合了控制反轉(zhuǎn)策略。
  • 避免靜態(tài)訪(fǎng)問(wèn) HttpContext(例如,IHttpContextAccessor.HttpContext)。

像任何一組建議一樣,你可能會(huì)遇到需要忽略某建議的情況。 例外情況很少見(jiàn) — 主要是框架本身內(nèi)部的特殊情況。

DI 是靜態(tài)/全局對(duì)象訪(fǎng)問(wèn)模式的替代方法。 如果將其與靜態(tài)對(duì)象訪(fǎng)問(wèn)混合使用,則可能無(wú)法實(shí)現(xiàn) DI 的優(yōu)點(diǎn)。


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)