Vysvětlení základů injektáže závislostí v .NET
V tomto článku vytvoříte konzolovou aplikaci .NET, která ručně vytvoří ServiceCollection a k tomu odpovídající ServiceProvider. Naučíte se registrovat služby a řešit je pomocí injektáže závislostí (DI). Tento článek používá balíček NuGet Microsoft.Extensions.DependencyInjection k předvedení základů DI v .NET.
Poznámka:
Tento článek nevyužívá funkce obecného hostitele . Podrobnější průvodce najdete v tématu Použití injektáže závislostí v .NET.
Začínáme
Začněte vytvořením nové konzolové aplikace .NET s názvem DI.Basics. Na některé z nejběžnějších přístupů k vytvoření projektu konzoly se odkazuje v následujícím seznamu:
- Visual Studio: Nabídka Soubor > Nový > Projekt
- Visual Studio Code a rozšíření C# Dev Kit: možnost nabídky Průzkumník řešení.
-
.NET CLI:
dotnet new console
příkaz v terminálu.
Do souboru projektu je potřeba přidat odkaz na balíček Microsoft.Extensions.DependencyInjection . Bez ohledu na přístup se ujistěte, že se projekt podobá následujícímu kódu XML souboru DI.Basics.csproj :
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
</ItemGroup>
</Project>
Základy injektování závislostí
Injektáž závislostí je vzor návrhu, který umožňuje odebrat pevně zakódované závislosti a zajistit, aby byla vaše aplikace lépe udržovatelná a testovatelná. DI je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.
Abstrakce DI v .NET jsou definovány v balíčku NuGet Microsoft.Extensions.DependencyInjection.Abstractions:
- IServiceCollection: Definuje smlouvu pro kolekci popisovačů služeb.
- IServiceProvider: Definuje mechanismus pro načtení objektu služby.
- ServiceDescriptor: Popisuje službu s jejím typem služby, implementací a životností.
V .NET se di spravuje přidáním služeb a jejich konfigurací v objektu IServiceCollection
. Po registraci IServiceProvider
služeb je instance vytvořena voláním BuildServiceProvider metody. Funguje IServiceProvider
jako kontejner všech registrovaných služeb a slouží k řešení služeb.
Vytvoření ukázkových služeb
Ne všechny služby se vytvářejí stejně. Některé služby vyžadují novou instanci pokaždé, když je kontejner služby získává (dočasná), zatímco jiné by měly být sdíleny v rámci požadavků (omezené na požadavky) nebo po celou dobu životnosti aplikace (singleton). Další informace o životnostech služeb najdete v tématu Životnost služby.
Podobně některé služby zpřístupňují pouze konkrétní typ, zatímco jiné jsou vyjádřeny jako kontrakt mezi rozhraním a typem implementace. Vytvoříte několik variant služeb, které vám pomůžou tyto koncepty předvést.
Vytvořte nový soubor C# s názvem IConsole.cs a přidejte následující kód:
public interface IConsole
{
void WriteLine(string message);
}
Tento soubor definuje IConsole
rozhraní, které zveřejňuje jednu metodu , WriteLine
. Dále vytvořte nový soubor C# s názvem DefaultConsole.cs a přidejte následující kód:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
Předchozí kód představuje výchozí implementaci IConsole
rozhraní. Metoda WriteLine
podmíněně zapisuje do konzoly na základě vlastnosti IsEnabled
.
Tip
Pojmenování implementace je volba, na které by se měl váš vývojový tým shodnout. Předpona Default
je běžnou konvencí označující výchozí implementaci rozhraní, ale nevyžaduje se.
Dále vytvořte soubor IGreetingService.cs a přidejte následující kód jazyka C#:
public interface IGreetingService
{
string Greet(string name);
}
Pak přidejte nový soubor C# s názvem DefaultGreetingService.cs a přidejte následující kód:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
Předchozí kód představuje výchozí implementaci IGreetingService
rozhraní. Implementace služby vyžaduje IConsole
jako primární parametr konstruktoru. Metoda Greet
:
- Vytvoří
greeting
podle danéhoname
. - Volá metodu
WriteLine
na instanciIConsole
. - Vrátí
greeting
volajícímu.
Poslední službou , která se má vytvořit, je soubor FarewellService.cs a před pokračováním přidejte následující kód jazyka C#:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
Představuje FarewellService
konkrétní typ, nikoli rozhraní. Měla by být deklarována tak public
, aby byla přístupná uživatelům. Na rozdíl od jiných typů implementace služby, které byly deklarovány jako internal
a sealed
, tento kód ukazuje, že ne všechny služby musí být rozhraní. Ukazuje také, že implementace služeb mohou bránit sealed
dědičnosti a internal
omezit přístup k sestavení.
Aktualizujte Program
třídu
Otevřete soubor Program.cs a nahraďte stávající kód následujícím kódem jazyka 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");
Předchozí aktualizovaný kód ukazuje postupy:
- Vytvořte novou
ServiceCollection
instanci. - Registrace a konfigurace služeb v
ServiceCollection
.-
IConsole
pomocí přetížení implementační továrny vrátí typDefaultConsole
sIsEnabled
nastaveným natrue
. -
IGreetingService
je přidán s odpovídajícím typem implementaceDefaultGreetingService
. - Je
FarewellService
přidán jako konkrétní typ.
-
- Sestavte
ServiceProvider
zServiceCollection
. - Řešení problémů
IGreetingService
aFarewellService
služeb - Pomocí vyřešených služeb pozdravte a rozloučíte se s osobou s názvem
David
.
Pokud aktualizujete IsEnabled
vlastnost DefaultConsole
na false
, Greet
a SayGoodbye
metody vynechají zapsání výsledných zpráv do konzole. Taková změna pomůže ukázat, že je služba IConsole
vložená do služeb IGreetingService
a FarewellService
jako závislost, která ovlivňuje chování aplikace.
Všechny tyto služby jsou registrovány jako singletony, i když pro tuto ukázku by fungovaly stejně, pokud by byly registrovány jako přechodné nebo jako vymezené služby.
Důležité
V tomto nepochybovaném příkladu nezáleží na životnosti služby, ale v reálné aplikaci byste měli pečlivě zvážit životnost každé služby.
Spuštění ukázkové aplikace
Ukázkovou aplikaci spustíte stisknutím klávesy F5 v sadě Visual Studio, editoru Visual Studio Code nebo spuštěním dotnet run
příkazu v terminálu. Po dokončení aplikace by se měl zobrazit následující výstup:
Hello, David!
Goodbye, David!
Popisovače služeb
Nejčastěji používaná rozhraní API pro přidávání služeb do ServiceCollection
jsou obecné rozšiřující metody s pojmenovanou dobou trvání, jako například:
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
Tyto metody jsou praktické metody, které vytvářejí instanci ServiceDescriptor a přidávají ji do ServiceCollection
. Jedná se ServiceDescriptor
o jednoduchou třídu, která popisuje službu s jejím typem služby, typem implementace a životností. Může také popsat implementační továrny a instance.
Pro každou službu, kterou jste zaregistrovali v nástroji ServiceCollection
, můžete místo toho použít metodu Add
k volání instance ServiceDescriptor
přímo. Zvažte následující příklady:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
Předchozí kód odpovídá tomu, jak byla služba IConsole
zaregistrována v ServiceCollection
. Metoda Add
se používá k přidání ServiceDescriptor
instance, která popisuje IConsole
službu. Statická metoda ServiceDescriptor.Describe
deleguje na různé ServiceDescriptor
konstruktory. Vezměte v úvahu ekvivalentní kód pro IGreetingService
službu:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
Předchozí kód popisuje IGreetingService
službu s jejím typem služby, typem implementace a životností. Nakonec zvažte ekvivalentní kód pro FarewellService
službu:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
Předchozí kód popisuje konkrétní FarewellService
typ jako typ služby i implementace. Služba je zaregistrovaná jako jednoúčelová služba.