教學課程:在 .NET 中使用相依性插入

本教學課程示範如何在 .NET 中使用相依性插入 (DI) 。 使用 Microsoft Extensions時,DI 是一個第一級公民,其中會在 中 IServiceCollection 新增和設定服務。 介面 IHostIServiceProvider 公開 實例,該實例會作為所有已註冊服務的容器。

在本教學課程中,您會了解如何:

  • 建立使用相依性插入的 .NET 主控台應用程式
  • 建置和設定 泛型主機
  • 撰寫數個介面和對應的實作
  • 使用 DI 的服務存留期和範圍

必要條件

  • .NET Core 3.1 SDK 或更新版本。
  • 熟悉建立新的 .NET 應用程式和安裝 NuGet 套件。

建立新的主控台應用程式

使用dotnet new命令或 IDE 新專案精靈,建立名為ConsoleDI 的新 Example .NET 主控台應用程式。 將 Microsoft.Extensions.Hosting NuGet 套件新增至專案。

新增介面

將下列介面新增至專案根目錄:

IOperation.cs

namespace ConsoleDI.Example;

public interface IOperation
{
    string OperationId { get; }
}

介面 IOperation 會定義單 OperationId 一屬性。

I Transient Operation.cs

namespace ConsoleDI.Example;

public interface ITransientOperation : IOperation
{
}

I Scoped Operation.cs

namespace ConsoleDI.Example;

public interface IScopedOperation : IOperation
{
}

I Singleton Operation.cs

namespace ConsoleDI.Example;

public interface ISingletonOperation : IOperation
{
}

所有名稱其預定服務存留期的 IOperation 子介面。 例如,「 Transient 」 或 「 Singleton 」。

新增預設實作

為各種作業新增下列預設實作:

DefaultOperation.cs

using static System.Guid;

namespace ConsoleDI.Example;

public class DefaultOperation :
    ITransientOperation,
    IScopedOperation,
    ISingletonOperation
{
    public string OperationId { get; } = NewGuid().ToString()[^4..];
}

DefaultOperation 實作所有具名標記介面,並將 屬性初始化 OperationId 為新全域唯一識別碼的最後四個字元, (GUID) 。

新增需要 DI 的服務

將下列作業記錄器物件新增為主控台應用程式的服務:

OperationLogger.cs

namespace ConsoleDI.Example;

public class OperationLogger
{
    private readonly ITransientOperation _transientOperation;
    private readonly IScopedOperation _scopedOperation;
    private readonly ISingletonOperation _singletonOperation;

    public OperationLogger(
        ITransientOperation transientOperation,
        IScopedOperation scopedOperation,
        ISingletonOperation singletonOperation) =>
        (_transientOperation, _scopedOperation, _singletonOperation) =
            (transientOperation, scopedOperation, singletonOperation);

    public void LogOperations(string scope)
    {
        LogOperation(_transientOperation, scope, "Always different");
        LogOperation(_scopedOperation, scope, "Changes only with scope");
        LogOperation(_singletonOperation, scope, "Always the same");
    }


    private static void LogOperation<T>(T operation, string scope, string message)
        where T : IOperation =>
        Console.WriteLine(
            $"{scope}: {typeof(T).Name,-19} [ {operation.OperationId}...{message,-23} ]");
}

OperationLogger會定義一個建構函式,此建構函式需要上述每個標記介面,也就是 ITransientOperationIScopedOperationISingletonOperation 。 物件會公開單一方法,讓取用者能夠使用指定的 scope 參數來記錄作業。 叫用時,方法會 LogOperations 以範圍字串和訊息記錄每個作業的唯一識別碼。

註冊 DI 的服務

使用下列程式碼更新 Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
        services.AddTransient<ITransientOperation, DefaultOperation>()
            .AddScoped<IScopedOperation, DefaultOperation>()
            .AddSingleton<ISingletonOperation, DefaultOperation>()
            .AddTransient<OperationLogger>())
    .Build();

ExemplifyScoping(host.Services, "Scope 1");
ExemplifyScoping(host.Services, "Scope 2");

await host.RunAsync();

static void ExemplifyScoping(IServiceProvider services, string scope)
{
    using IServiceScope serviceScope = services.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;

    OperationLogger logger = provider.GetRequiredService<OperationLogger>();
    logger.LogOperations($"{scope}-Call 1 .GetRequiredService<OperationLogger>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<OperationLogger>();
    logger.LogOperations($"{scope}-Call 2 .GetRequiredService<OperationLogger>()");

    Console.WriteLine();
}

每個 services.Add{LIFETIME}<{SERVICE}> 擴充方法都會新增 (並且可能會設定) 服務。 建議應用程式遵循此慣例。 在 Microsoft.Extensions.DependencyInjection 命名空間中放置擴充方法,以封裝服務註冊群組。 也包括 DI 擴充方法的命名空間部分 Microsoft.Extensions.DependencyInjection

  • 允許它們顯示在 IntelliSense 中,而不需新增其他 using 區塊。
  • 防止 或 Startup 類別中 Program 呼叫這些擴充方法的過多 using 語句。

應用程式:

結論

應用程式會顯示類似下列範例的輸出:

Scope 1-Call 1 .GetRequiredService<OperationLogger>(): ITransientOperation [ 80f4...Always different        ]
Scope 1-Call 1 .GetRequiredService<OperationLogger>(): IScopedOperation    [ c878...Changes only with scope ]
Scope 1-Call 1 .GetRequiredService<OperationLogger>(): ISingletonOperation [ 1586...Always the same         ]
...
Scope 1-Call 2 .GetRequiredService<OperationLogger>(): ITransientOperation [ f3c0...Always different        ]
Scope 1-Call 2 .GetRequiredService<OperationLogger>(): IScopedOperation    [ c878...Changes only with scope ]
Scope 1-Call 2 .GetRequiredService<OperationLogger>(): ISingletonOperation [ 1586...Always the same         ]

Scope 2-Call 1 .GetRequiredService<OperationLogger>(): ITransientOperation [ f9af...Always different        ]
Scope 2-Call 1 .GetRequiredService<OperationLogger>(): IScopedOperation    [ 2bd0...Changes only with scope ]
Scope 2-Call 1 .GetRequiredService<OperationLogger>(): ISingletonOperation [ 1586...Always the same         ]
...
Scope 2-Call 2 .GetRequiredService<OperationLogger>(): ITransientOperation [ fa65...Always different        ]
Scope 2-Call 2 .GetRequiredService<OperationLogger>(): IScopedOperation    [ 2bd0...Changes only with scope ]
Scope 2-Call 2 .GetRequiredService<OperationLogger>(): ISingletonOperation [ 1586...Always the same         ]

從應用程式輸出中,您可以看到:

  • Transient 作業一律不同,每次擷取服務時都會建立新的實例。
  • Scoped 作業只會隨著新的範圍而變更,但在範圍內是相同的實例。
  • Singleton 作業一律相同,只會建立一次新的實例。

請參閱