在這個快速入門中,您將建立一個 .NET 控制台應用程式,並手動建立 ServiceCollection 及其對應的 ServiceProvider。 你學會如何註冊服務並透過依賴注入(DI)來解決服務。 本文使用 Microsoft.Extensions.DependencyInjection NuGet 套件來示範 .NET 中 DI 的基本操作。
備註
本文未利用 Generic 主機 的功能。 欲獲得更完整的指南,請參閱 在 .NET 中使用相依性注入。
開始
要開始,請建立一個新的 .NET 主控台應用程式,名為 DI.Basics。 在 Visual Studio 中,選擇 「檔案 > 新 > 專案」,或使用 .NET CLI 輸入 dotnet new console。
接著,在專案檔案中新增 Microsoft.Extensions.DependencyInjection 的套件參考。 新增套件後,請確保專案與以下 DI.Basics.csproj 檔案的 XML 相似:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
</ItemGroup>
</Project>
依賴注入基礎知識
相依注入是一種設計模式,可以用來移除硬編碼的相依,並使應用程式更易維護與測試。 DI 是一種用於在類別及其相依關係間實現 控制反轉(IoC )的技術。
Microsoft.Extensions.DependencyInjection.Abstractions NuGet 套件定義了 .NET 中 DI 的抽象:
- IServiceCollection定義了一組服務描述符的合約。
- IServiceProvider定義了一種取回服務物件的機制。
- ServiceDescriptor: 描述一項服務及其服務類型、實作方式及生命週期。
在 .NET 中,你透過新增服務並在 IServiceCollection 中配置它們。 註冊服務後,呼叫 BuildServiceProvider 方法來建立實 IServiceProvider 例。
IServiceProvider 作為所有註冊服務的容器,你使用它來解析這些服務。
建立範例服務
並非所有服務都一樣。 有些服務在每次服務容器接收時都需要一個新實例(暫時型),而有些則應該在請求間共享(範圍型) 或應用程式的整個生命週期(單例型)。 欲了解更多服役壽命資訊,請參閱 《服役服役期》。
同樣地,有些服務只暴露一個具體型別,而有些則以介面與實作型別之間的契約形式表達。 你會創造多種服務版本來幫助展示這些概念。
建立一個名為 IConsole.cs 的新 C# 檔案,並加入以下程式碼:
public interface IConsole
{
void WriteLine(string message);
}
此檔案定義了一個IConsole介面,該介面會暴露單一方法。 WriteLine 接著,建立一個名為 DefaultConsole.cs 的新 C# 檔案,並加入以下程式碼:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
前述程式碼代表介面的預設實作 IConsole 。 該 WriteLine 方法根據 IsEnabled 屬性有條件地寫入主控台。
小提示
實作的命名是開發團隊應該同意的選擇。
Default前綴是常用來表示介面預設實作的慣例,但並非必須。
接著建立 一個IGreetingService.cs 檔案,並加入以下 C# 程式碼:
public interface IGreetingService
{
string Greet(string name);
}
接著新增一個名為 DefaultGreetingService.cs 的新 C# 檔案,並加入以下程式碼:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
前述程式碼代表介面的預設實作 IGreetingService 。 服務實作需要一個 IConsole 作為主要建構參數。
Greet 方法:
- 產生
greeting給定的name。 - 在
WriteLine實例上呼叫IConsole方法。 - 回傳
greeting給來電者。
這個 DefaultGreetingService 類別展示了你可以 seal 服務實作,以防止繼承,並使用 internal 來限制對程序集的存取。
最後一個要建立的服務是 FarewellService.cs 檔案。 在繼續之前,請加入以下 C# 程式碼:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
代表 FarewellService 的是具體型態,而非介面。 你應該宣告為 public 以使其對消費者可取得。 與你宣告為 internal 和 sealed的其他服務實作類型不同,這段程式碼顯示並非所有服務都需要是介面。
更新類別Program
打開 Program.cs 檔案,將現有程式碼替換成以下 C# 程式碼:
using Microsoft.Extensions.DependencyInjection;
// 1. Create the service collection.
var services = new ServiceCollection();
// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
});
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();
// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();
// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();
// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");
上述程式碼示範如何:
- 建立一個新的
ServiceCollection實例。 - 在以下
ServiceCollection區域註冊並配置服務:- 透過使用實作工廠過載來提供
IConsole服務。 回傳一個型別DefaultConsole並設置其屬性IsEnabled為true。 - 服務的
IGreetingService實作類型為DefaultGreetingService。 -
FarewellService服務作為具體類型。
- 透過使用實作工廠過載來提供
- 從
ServiceProvider建構ServiceCollection。 - 解決〈c0〉服務與〈c1〉服務。
- 利用已解決的服務向一位名叫
David的人問候並道別。
如果你將IsEnabled的DefaultConsole屬性更新為false,Greet和SayGoodbye方法就不會將生成的消息寫入主控台。 此變更有助於證明IConsole服務被注入到IGreetingService及FarewellService服務中,作為一種依賴項,影響應用程式的行為。
所有這些服務都註冊為單例。 在這個範例中,將它們註冊為 暫態 或 有範圍 的服務,運作方式相同。
這很重要
在這個刻意的例子中,服役壽命並不重要。 在實際應用中,請仔細考慮每項服務的使用壽命。
執行範例應用程式
要執行範例應用程式,請在 Visual Studio 或 Visual Studio Code 中按 F5 ,或在終端機執行該 dotnet run 指令。 應用程式完成後,你會看到以下輸出:
Hello, David!
Goodbye, David!
服務描述符
ServiceCollection 最常用的 API 是具生命週期名稱的泛型擴充方法,例如:
AddSingleton<TService>AddTransient<TService>AddScoped<TService>
這些方法是方便方法,建立一個 ServiceDescriptor 實例並將其加入 ServiceCollection。 這是一個 ServiceDescriptor 簡單類別,描述服務及其服務類型、實作類型及生命週期。 它也可以描述實作工廠和實例。
在ServiceCollection中註冊的每個服務,你可以使用ServiceDescriptor實例直接呼叫Add方法。 請考慮下列範例:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
前述代碼的作用等同於在 IConsole 中註冊 ServiceCollection 服務的方式。 此 Add 方法會新增一個 ServiceDescriptor 描述服務的 IConsole 實例。 靜態方法 ServiceDescriptor.Describe 則委派給各種 ServiceDescriptor 建構子。 考慮該服務的等效程式碼 IGreetingService :
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
前述程式碼描述 IGreetingService 服務及其服務類型、實作類型及生命週期。 最後,考慮該服務的 FarewellService 等效程式碼:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
前述程式碼將具體 FarewellService 型別描述為服務型與實作型態。 該服務註冊為單例服務。