Förstå grunderna för beroendeinmatning i .NET
I den här artikeln skapar du en .NET-konsolapp som manuellt skapar en ServiceCollection och motsvarande ServiceProvider. Du lär dig hur du registrerar tjänster och löser dem med hjälp av beroendeinmatning (DI). Den här artikeln använder NuGet-paketet Microsoft.Extensions.DependencyInjection för att demonstrera grunderna i DI i .NET.
Kommentar
Den här artikeln drar inte nytta av funktionerna för generisk värd. En mer omfattande guide finns i Använda beroendeinmatning i .NET.
Kom igång
Kom igång genom att skapa ett nytt .NET-konsolprogram med namnet DI.Basics. Några av de vanligaste metoderna för att skapa ett konsolprojekt refereras i följande lista:
- Visual Studio: Menyn Nytt > > projekt.
- Visual Studio Code och menyalternativet för C# Dev Kit-tillägget: Solution Explorer .
- .NET CLI:
dotnet new console
kommando i terminalen.
Du måste lägga till paketreferensen till Microsoft.Extensions.DependencyInjection i projektfilen. Oavsett metod bör du se till att projektet liknar följande XML för FILEN 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="8.0.0" />
</ItemGroup>
</Project>
Grunderna för beroendeinmatning
Beroendeinmatning är ett designmönster som gör att du kan ta bort hårdkodade beroenden och göra programmet mer underhållsbart och testbart. DI är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden.
Abstraktionerna för DI i .NET definieras i NuGet-paketet Microsoft.Extensions.DependencyInjection.Abstractions :
- IServiceCollection: Definierar ett kontrakt för en samling tjänstbeskrivningar.
- IServiceProvider: Definierar en mekanism för att hämta ett tjänstobjekt.
- ServiceDescriptor: Beskriver en tjänst med dess tjänsttyp, implementering och livslängd.
I .NET hanteras DI genom att lägga till tjänster och konfigurera dem i en IServiceCollection
. När tjänsterna har registrerats skapas en IServiceProvider
instans genom att metoden anropas BuildServiceProvider . Fungerar IServiceProvider
som en container för alla registrerade tjänster och används för att lösa tjänster.
Skapa example tjänster
Alla tjänster skapas inte lika. Vissa tjänster kräver en ny instans varje gång som tjänstcontainern hämtar dem (transient), medan andra ska delas mellan begäranden (scoped) eller under hela appens livslängd (singleton). Mer information om tjänstlivslängder finns i Tjänstlivslängder.
På samma sätt exponerar vissa tjänster bara en konkret typ, medan andra uttrycks som ett kontrakt mellan ett gränssnitt och en implementeringstyp. Du skapar flera varianter av tjänster för att demonstrera dessa begrepp.
Skapa en ny C#-fil med namnet IConsole.cs och lägg till följande kod:
public interface IConsole
{
void WriteLine(string message);
}
Den här filen definierar ett IConsole
gränssnitt som exponerar en enda metod, WriteLine
. Skapa sedan en ny C#-fil med namnet DefaultConsole.cs och lägg till följande kod:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
Föregående kod representerar standardimplementeringen av IConsole
gränssnittet. Metoden WriteLine
skriver villkorligt till konsolen baserat på IsEnabled
egenskapen .
Dricks
Namngivning av en implementering är ett val som utvecklingsteamet bör komma överens om. Prefixet Default
är en vanlig konvention som anger en standardimplementering av ett gränssnitt, men det krävs inte .
Skapa sedan en IGreetingService.cs fil och lägg till följande C#-kod:
public interface IGreetingService
{
string Greet(string name);
}
Lägg sedan till en ny C#-fil med namnet DefaultGreetingService.cs och lägg till följande kod:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
Föregående kod representerar standardimplementeringen av IGreetingService
gränssnittet. Tjänstimplementeringen kräver en IConsole
som primär konstruktorparameter. Metoden Greet
:
- Skapar en
greeting
angivenname
. WriteLine
Anropar metoden på instansenIConsole
.greeting
Returnerar till anroparen.
Den sista tjänsten som ska skapas är FarewellService.cs-filen, lägg till följande C#-kod innan du fortsätter:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
Representerar FarewellService
en konkret typ, inte ett gränssnitt. Den bör deklareras så public
att den blir tillgänglig för konsumenterna. Till skillnad från andra tjänstimplementeringstyper som deklarerades som internal
och sealed
visar den här koden att inte alla tjänster behöver vara gränssnitt. Det visar också att tjänstimplementeringar kan vara sealed
att förhindra arv och internal
begränsa åtkomsten till sammansättningen.
Program
Uppdatera klassen
Öppna filen Program.cs och ersätt den befintliga koden med följande C#-kod:
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");
Föregående uppdaterade kod visar instruktioner:
- Skapa en ny
ServiceCollection
instans. - Registrera och konfigurera tjänster i
ServiceCollection
:- När
IConsole
du använder överlagringen av implementeringsfabrikenIsEnabled
returnerar du enDefaultConsole
typ med värdet "true". IGreetingService
Läggs till med en motsvarande implementeringstyp avDefaultGreetingService
typen.FarewellService
Läggs till som en betongtyp.
- När
ServiceProvider
Skapa frånServiceCollection
.- Lös tjänsterna
IGreetingService
ochFarewellService
. - Använd de lösta tjänsterna för att hälsa och säga adjö till en person med namnet
David
.
Om du uppdaterar IsEnabled
egenskapen DefaultConsole
för till false
utelämnar Greet
metoderna och SayGoodbye
skrivningen till de resulterande meddelandena till konsolen. En ändring som den här hjälper till att visa att IConsole
tjänsten matas in i IGreetingService
tjänsterna och FarewellService
som ett beroende som påverkar apparnas beteende.
Alla dessa tjänster är registrerade som singletons, men för det här exemplet fungerar de identiskt om de har registrerats som transient eller scoped tjänster.
Viktigt!
I den här contrived examplespelar tjänstlivslängden ingen roll, men i ett verkligt program bör du noga överväga livslängden för varje tjänst.
Kör exempelappen
Om du vill köra exempelappen trycker du antingen på F5 i Visual Studio, Visual Studio Code eller kör dotnet run
kommandot i terminalen. När appen är klar bör du se följande utdata:
Hello, David!
Goodbye, David!
Tjänstbeskrivningar
De vanligaste API:erna för att lägga till tjänster i ServiceCollection
är livslängdsnamnsnamns generiska tilläggsmetoder, till exempel:
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
Dessa metoder är bekvämlighetsmetoder som skapar en ServiceDescriptor instans och lägger till den i ServiceCollection
. ServiceDescriptor
är en enkel klass som beskriver en tjänst med dess tjänsttyp, implementeringstyp och livslängd. Det kan också desribe implementeringsfabriker och instanser.
För var och en av de tjänster som du registrerade i kan du i ServiceCollection
stället anropa Add
metoden med en ServiceDescriptor
instans direkt. Föreställ dig följande exempel:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
Föregående kod motsvarar hur IConsole
tjänsten registrerades i ServiceCollection
. Metoden Add
används för att lägga till en ServiceDescriptor
instans som beskriver IConsole
tjänsten. Den statiska metoden ServiceDescriptor.Describe
delegerar till olika ServiceDescriptor
konstruktorer. Överväg motsvarande kod för IGreetingService
tjänsten:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
Föregående kod beskriver tjänsten IGreetingService
med dess tjänsttyp, implementeringstyp och livslängd. Tänk slutligen på motsvarande kod för FarewellService
tjänsten:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
Föregående kod beskriver den konkreta FarewellService
typen som både tjänst- och implementeringstyper. Tjänsten är registrerad som en singleton tjänst.