Share via


Zelfstudie: Afhankelijkheidsinjectie gebruiken in .NET

In deze handleiding leert u hoe u afhankelijkheidsinjectie (DI) gebruikt in .NET. Met Microsoft Extensionswordt DI beheerd door services toe te voegen en te configureren in een IServiceCollection. De IHost-interface maakt het IServiceProvider-exemplaar beschikbaar, dat fungeert als een container van alle geregistreerde services.

In deze zelfstudie leert u het volgende:

  • Een .NET-console-app maken die gebruikmaakt van afhankelijkheidsinjectie
  • Een Generic Host- bouwen en configureren
  • Verschillende interfaces en bijbehorende implementaties schrijven
  • Levensduur en scoping van services gebruiken voor DI

Voorwaarden

  • .NET Core 3.1 SDK of hoger.
  • Bekendheid met het maken van nieuwe .NET-toepassingen en het installeren van NuGet-pakketten.

Een nieuwe consoletoepassing maken

Maak met de opdracht dotnet new of een nieuwe IDE-projectwizard een nieuwe .NET-consoletoepassing met de naam ConsoleDI.Example. Voeg het Microsoft.Extensions.Hosting NuGet-pakket toe aan het project.

Het nieuwe projectbestand van de console-app moet er ongeveer als volgt uitzien:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ConsoleDI.Example</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.3" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
  </ItemGroup>

</Project>

Belangrijk

In dit voorbeeld is het Microsoft.Extensions.Hosting NuGet-pakket vereist om de app te bouwen en uit te voeren. Sommige metapackages kunnen het Microsoft.Extensions.Hosting-pakket bevatten. In dat geval is er geen expliciete pakketverwijzing vereist.

Interfaces toevoegen

In deze voorbeeld-app leert u hoe afhankelijkheidsinjectie de levensduur van de service afhandelt. U maakt verschillende interfaces die verschillende levensduur van de service vertegenwoordigen. Voeg de volgende interfaces toe aan de hoofdmap van het project:

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

De IReportServiceLifetime-interface definieert:

  • Een Guid Id-eigenschap die de unieke identificator van de service vertegenwoordigt.
  • Een ServiceLifetime eigenschap die de levensduur van de service vertegenwoordigt.

IExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

IExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

IExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

Alle subinterfaces van IReportServiceLifetime de IReportServiceLifetime.Lifetime expliciet implementeren met een standaardinstelling. IExampleTransientService implementeert bijvoorbeeld expliciet IReportServiceLifetime.Lifetime met de ServiceLifetime.Transient waarde.

Standaard implementaties toevoegen

In de voorbeeldimplementaties worden al hun Id-eigenschappen geïnitialiseerd met het resultaat van Guid.NewGuid(). Voeg de volgende standaard implementatieklassen voor de verschillende services toe aan de hoofdmap van het project:

ExampleTransientService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleTransientService : IExampleTransientService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleScopedService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleScopedService : IExampleScopedService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleSingletonService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleSingletonService : IExampleSingletonService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

Elke implementatie wordt gedefinieerd als internal sealed en implementeert de bijbehorende interface. Ze hoeven niet te worden internal of sealed, maar het is gebruikelijk om implementaties te behandelen als internal om te voorkomen dat implementatietypen naar externe consumenten worden gelekt. Bovendien, omdat elk type niet wordt uitgebreid, wordt het gemarkeerd als sealed. ExampleSingletonService implementeert bijvoorbeeld IExampleSingletonService.

Een service toevoegen waarvoor DI is vereist

Voeg de volgende rapportageklasse voor de levensduur van de service toe, die fungeert als een service voor de consoletoepassing:

ServiceLifetimeReporter.cs

namespace ConsoleDI.Example;

internal sealed class ServiceLifetimeReporter(
    IExampleTransientService transientService,
    IExampleScopedService scopedService,
    IExampleSingletonService singletonService)
{
    public void ReportServiceLifetimeDetails(string lifetimeDetails)
    {
        Console.WriteLine(lifetimeDetails);

        LogService(transientService, "Always different");
        LogService(scopedService, "Changes only with lifetime");
        LogService(singletonService, "Always the same");
    }

    private static void LogService<T>(T service, string message)
        where T : IReportServiceLifetime =>
        Console.WriteLine(
            $"    {typeof(T).Name}: {service.Id} ({message})");
}

De ServiceLifetimeReporter definieert een constructor waarvoor elk van de bovengenoemde serviceinterfaces is vereist, namelijk IExampleTransientService, IExampleScopedServiceen IExampleSingletonService. Het object maakt één methode beschikbaar waarmee de consument over de service kan rapporteren met een bepaalde lifetimeDetails parameter. Wanneer deze wordt aangeroepen, registreert de ReportServiceLifetimeDetails methode de unieke identificatie van elke service met het levensduurbericht voor de service. De logboekberichten helpen bij het visualiseren van de levensduur van de service.

Services registreren voor DI

Werk Program.cs bij met de volgende code:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();

using IHost host = builder.Build();

ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");

await host.RunAsync();

static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
    using IServiceScope serviceScope = hostProvider.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;
    ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine();
}

Met elke services.Add{LIFETIME}<{SERVICE}> extensiemethode worden services toegevoegd (en mogelijk geconfigureerd). Het is raadzaam dat apps deze conventie volgen. Plaats extensiemethoden niet in de Microsoft.Extensions.DependencyInjection naamruimte, tenzij u een officieel Microsoft-pakket maakt. Extensiemethoden die zijn gedefinieerd in de Microsoft.Extensions.DependencyInjection-naamruimte:

  • Worden weergegeven in IntelliSense- zonder dat er aanvullende using instructies vereist zijn.
  • Verminder het aantal vereiste using instructies in de Program- of Startup klassen waarin deze uitbreidingsmethoden doorgaans worden aangeroepen.

De app:

  • Hiermee maakt u een IHostBuilder-exemplaar met instellingen voor de hostconfigurator .
  • Hiermee configureert u services en voegt u ze toe met de bijbehorende levensduur van de service.
  • Roept Build() aan en wijst een instantie van IHosttoe.
  • Roept ExemplifyScopingaan en geeft de IHost.Servicesdoor.

Conclusie

In deze voorbeeld-app hebt u verschillende interfaces en bijbehorende implementaties gemaakt. Elk van deze services wordt uniek geïdentificeerd en gekoppeld aan een ServiceLifetime. De voorbeeld-app demonstreert het registreren van service-implementaties op basis van een interface en het registreren van pure klassen zonder back-upinterfaces. De voorbeeld-app laat vervolgens zien hoe afhankelijkheden die zijn gedefinieerd als constructorparameters tijdens runtime worden opgelost.

Wanneer u de app uitvoert, wordt uitvoer weergegeven die er ongeveer als volgt uitziet:

// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// 
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)

In de uitvoer van de app ziet u dat:

  • De Transient services zijn altijd verschillend, er wordt een nieuw exemplaar gemaakt bij het ophalen van de service.
  • Scoped-services worden alleen gewijzigd met een nieuw scope, maar zijn hetzelfde exemplaar binnen een scope.
  • Singleton-services blijven altijd hetzelfde; er wordt slechts één keer een nieuw exemplaar aangemaakt.

Zie ook