在這個快速入門中,您將建立一個 .NET 控制台應用程式,並手動建立 ServiceCollection 及其對應的 ServiceProvider。 你會學會如何註冊服務並利用依賴注入(DI)來解決服務。 本文使用 Microsoft.Extensions.DependencyInjection NuGet 套件來示範 .NET 中 DI 的基本操作。
備註
本文未利用 Generic 主機 的功能。 欲獲得更完整的指南,請參閱 在 .NET 中使用相依性注入。
開始
要開始,請建立一個新的 .NET 主控台應用程式,名為 DI.Basics。 以下列出了一些最常見的主控台專案創建方法:
- Visual Studio: 檔案 > 新增 > 專案 選單。
- Visual Studio Code 以及 C# 開發套件擴充套件的解決方案總管選單選項。
-
.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.2" />
</ItemGroup>
</Project>
依賴注入基礎知識
相依注入是一種設計模式,讓你能移除硬編碼的相依關係,讓你的應用程式更易維護且可測試。 DI 是一種用於在類別及其相依關係間實現 控制反轉(IoC )的技術。
.NET 中 DI 的抽象定義在 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 套件中:
- IServiceCollection定義了一組服務描述符的合約。
- IServiceProvider定義了一種取回服務物件的機制。
- ServiceDescriptor: 描述一項服務及其服務類型、實作方式及生命週期。
在 .NET 中,DI 是透過在 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。 - 在
IConsole實例上呼叫WriteLine方法。 - 回傳
greeting給來電者。
最後一個要建立的服務是 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的服務實作類型不同,這段程式碼顯示並非所有服務都需要是介面。 同時也顯示服務實作可以用 sealed 來防止繼承並 internal 限制對組裝的存取。
更新類別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。 - 將
DefaultGreetingService類型的對應實作類型加入IGreetingService。 -
FarewellService被添加為具體類型。
- 利用
- 從
ServiceCollection建構ServiceProvider。 - 解決〈c0〉服務與〈c1〉服務。
- 利用已解決的服務向一位名叫
David的人問候並道別。
如果你將DefaultConsole的IsEnabled屬性更新為false,則Greet和SayGoodbye方法會省略將結果訊息寫入控制台。 這樣的變更有助於證明該IConsole服務作為一個相依性被注入到IGreetingService 和 FarewellService服務中,從而影響應用程式的行為。
所有這些服務都註冊為 singleton,雖然在這個範例中,即使它們註冊為 transient 或 scoped 服務,運作效果也是相同的。
這很重要
在這個刻意的例子中,服務壽命並不重要,但在實際應用中,你應該仔細考慮每個服務的使用壽命。
執行範例應用程式
要執行範例應用程式,請在 Visual Studio 或 Visual Studio Code 中按 F5 ,或在終端機執行該 dotnet run 指令。 應用程式完成後,你應該會看到以下輸出:
Hello, David!
Goodbye, David!
服務描述符
ServiceCollection 最常用的 API 是具生命週期名稱的泛型擴充方法,例如:
AddSingleton<TService>AddTransient<TService>AddScoped<TService>
這些方法是方便方法,建立一個 ServiceDescriptor 實例並將其加入 ServiceCollection。 這是一個 ServiceDescriptor 簡單類別,描述服務及其服務類型、實作類型及生命週期。 它也可以描述實作工廠和實例。
對於你在 註冊的 ServiceCollection每個服務,你可以直接用實例呼叫該 Add 方法 ServiceDescriptor 。 請考慮下列範例:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
前述代碼的作用等同於在 ServiceCollection 中註冊 IConsole 服務的方式。 此 Add 方法用於新增一個描述 IConsole 服務的 ServiceDescriptor 實例。 靜態方法 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 型別描述為服務型與實作型態。 該服務註冊為單例服務。