Quickstart: Basisprincipes van afhankelijkheidsinjectie in .NET

In deze snelstart maakt u een .NET-console-app waarmee u handmatig een ServiceCollection en de bijbehorende ServiceProvider maakt. U leert hoe u services registreert en oplost met behulp van afhankelijkheidsinjectie (DI). In dit artikel wordt het NuGet-pakket Microsoft.Extensions.DependencyInjection gebruikt om de basisprincipes van DI in .NET te demonstreren.

Opmerking

Dit artikel maakt geen gebruik van de algemene hostfuncties . Zie Afhankelijkheidsinjectie gebruiken in .NET voor een uitgebreidere handleiding.

Get started

Maak eerst een nieuwe .NET-consoletoepassing met de naam DI.Basics. Kies in Visual Studio Bestand > Nieuw > Project of gebruik de .NET CLI om dotnet new console.

Voeg vervolgens een pakketverwijzing toe aan Microsoft.Extensions.DependencyInjection in het projectbestand. Nadat u het pakket hebt toegevoegd, controleert u of het project lijkt op de volgende XML van het bestand DI.Basics.csproj :

<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>

Basisbeginselen van afhankelijkheidsinjectie

Afhankelijkheidsinjectie is een ontwerppatroon dat u kunt gebruiken om in code vastgelegde afhankelijkheden te verwijderen en uw toepassing beter te onderhouden en te testen. DI is een techniek voor het bereiken van Inversion of Control (IoC) tussen klassen en hun afhankelijkheden.

Het NuGet-pakket Microsoft.Extensions.DependencyInjection.Abstractions definieert de abstracties voor DI in .NET:

  • IServiceCollection: Definieert een contract voor een verzameling servicedescriptors.
  • IServiceProvider: Definieert een mechanisme voor het ophalen van een serviceobject.
  • ServiceDescriptor: Beschrijft een service met het servicetype, de implementatie en de levensduur.

In .NET beheert u DI door services toe te voegen en te configureren in een IServiceCollection. Nadat u services hebt geregistreerd, roept u de BuildServiceProvider methode aan om een IServiceProvider exemplaar te bouwen. De IServiceProvider fungeert als een container voor alle geregistreerde services en u gebruikt deze om services op te lossen.

Voorbeeldservices maken

Niet alle services worden op dezelfde manier gecreëerd. Voor sommige services is telkens een nieuw exemplaar vereist wanneer de servicecontainer deze ophaalt (tijdelijk), terwijl andere moeten worden gedeeld tussen aanvragen (scoped) of gedurende de gehele levensduur van de app (singleton). Zie Servicelevensduur voor meer informatie over de levensduur van de service.

Op dezelfde manier maken sommige services alleen een concreet type beschikbaar, terwijl andere worden uitgedrukt als een contract tussen een interface en een implementatietype. U maakt verschillende variaties van services om deze concepten te demonstreren.

Maak een nieuw C#-bestand met de naam IConsole.cs en voeg de volgende code toe:

public interface IConsole
{
    void WriteLine(string message);
}

Dit bestand definieert een IConsole interface die één methode beschikbaar maakt. WriteLine Maak vervolgens een nieuw C#-bestand met de naam DefaultConsole.cs en voeg de volgende code toe:

internal sealed class DefaultConsole : IConsole
{
    public bool IsEnabled { get; set; } = true;

    void IConsole.WriteLine(string message)
    {
        if (IsEnabled is false)
        {
            return;
        }

        Console.WriteLine(message);
    }
}

De voorgaande code vertegenwoordigt de standaard implementatie van de IConsole interface. De WriteLine methode schrijft voorwaardelijk naar de console op basis van de IsEnabled eigenschap.

Aanbeveling

De naamgeving van een implementatie is een keuze waar uw ontwikkelteam het mee eens moet zijn. Het Default voorvoegsel is een algemene conventie om een standaard implementatie van een interface aan te geven, maar dit is niet vereist.

Maak vervolgens een IGreetingService.cs bestand en voeg de volgende C#-code toe:

public interface IGreetingService
{
    string Greet(string name);
}

Voeg vervolgens een nieuw C#-bestand toe met de naam DefaultGreetingService.cs en voeg de volgende code toe:

internal sealed class DefaultGreetingService(
    IConsole console) : IGreetingService
{
    public string Greet(string name)
    {
        var greeting = $"Hello, {name}!";

        console.WriteLine(greeting);

        return greeting;
    }
}

De voorgaande code vertegenwoordigt de standaard implementatie van de IGreetingService interface. Voor de service-implementatie is een IConsole primaire constructorparameter vereist. De methode Greet:

  • Hiermee maakt u een greeting op basis van de name.
  • Roept de WriteLine methode op het IConsole exemplaar aan.
  • Retourneert greeting aan de aanroeper.

De DefaultGreetingService klasse laat zien dat u implementaties kunt seal uitvoeren om overname te voorkomen en internal te gebruiken om de toegang tot de assembly te beperken.

De laatste service die moet worden gemaakt, is het FarewellService.cs-bestand . Voeg de volgende C#-code toe voordat u doorgaat:

public class FarewellService(IConsole console)
{
    public string SayGoodbye(string name)
    {
        var farewell = $"Goodbye, {name}!";

        console.WriteLine(farewell);

        return farewell;
    }
}

Het FarewellService vertegenwoordigt een concreet type, geen interface. U moet deze public declareren om deze toegankelijk te maken voor consumenten. In tegenstelling tot andere typen service-implementatie die u declareert als internal en sealed, laat deze code zien dat niet alle services interfaces hoeven te zijn.

De Program klasse bijwerken

Open het Program.cs-bestand en vervang de bestaande code door de volgende C#-code:

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");

De voorgaande code laat zien hoe u het volgende kunt doen:

  • Maak een nieuwe ServiceCollection instantie.
  • Services registreren en configureren in het ServiceCollectionvolgende:
    • De IConsole service door gebruik te maken van de overload van de implementatiefabriek. Hiermee wordt een DefaultConsole type geretourneerd waarop de IsEnabled eigenschap is ingesteld op true.
    • De IGreetingService service met een bijbehorend implementatietype van DefaultGreetingService.
    • De FarewellService service als een concreet type.
  • Bouw de ServiceProvider van de ServiceCollection.
  • Los de IGreetingService en FarewellService diensten op.
  • Gebruik de opgeloste services om een persoon met de naam Davidte begroeten en afscheid te nemen.

Als u de IsEnabled eigenschap van de DefaultConsole bijwerkt naar false, laten de Greet en SayGoodbye methoden het wegschrijven van de resulterende berichten naar de console achterwege. Deze wijziging helpt aantonen dat de IConsole service wordt geïnjecteerd in de IGreetingService service en FarewellService services als een afhankelijkheid die van invloed is op het gedrag van de app.

Al deze services worden geregistreerd als singletons. Voor dit voorbeeld werkt het identiek als u ze registreert als tijdelijke of scoped services.

Belangrijk

In dit voorbeeld maakt de levensduur van de service niet uit. Houd in een echte toepassing zorgvuldig rekening met de levensduur van elke service.

De voorbeeld-app uitvoeren

Als u de voorbeeld-app wilt uitvoeren, drukt u op F5 in Visual Studio of Visual Studio Code of voert u de dotnet run opdracht uit in de terminal. Wanneer de app is voltooid, ziet u de volgende uitvoer:

Hello, David!
Goodbye, David!

Servicedescriptors

De meest gebruikte API's voor het toevoegen van services aan de ServiceCollection zijn lifetime-genaamde generieke extensiemethoden, zoals:

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

Deze methoden zijn handige hulpmethoden die een ServiceDescriptor-exemplaar maken en aan de ServiceCollection toevoegen. De ServiceDescriptor is een eenvoudige klasse die een service beschrijft met het servicetype, het implementatietype en de levensduur. Het kan ook implementatiefabrieken en instanties beschrijven.

Voor elke service die u hebt geregistreerd in de ServiceCollection, kunt u in plaats daarvan de Add-methode rechtstreeks aanroepen met een ServiceDescriptor-exemplaar. Bekijk de volgende voorbeelden:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IConsole),
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    },
    lifetime: ServiceLifetime.Singleton));

De voorgaande code is gelijk aan de wijze waarop de IConsole service is geregistreerd in de ServiceCollection. Met Add de methode wordt een ServiceDescriptor exemplaar toegevoegd dat de IConsole service beschrijft. De statische methode ServiceDescriptor.Describe delegeert naar verschillende ServiceDescriptor constructoren. Overweeg de equivalente code voor de IGreetingService service:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IGreetingService),
    implementationType: typeof(DefaultGreetingService),
    lifetime: ServiceLifetime.Singleton));

In de voorgaande code wordt de service beschreven met het servicetype, het implementatietype en de IGreetingService levensduur. Overweeg ten slotte de equivalente code voor de FarewellService service:

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(FarewellService),
    implementationType: typeof(FarewellService),
    lifetime: ServiceLifetime.Singleton));

In de voorgaande code wordt het concrete FarewellService type beschreven als zowel de service- als de implementatietypen. De service is geregistreerd als een singleton service.

Zie ook