Share via


Zelfstudie: Afhankelijkheidsinjectie gebruiken in .NET

Deze zelfstudie laat zien hoe u afhankelijkheidsinjectie (DI) gebruikt in .NET. Met Microsoft Extensions wordt 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 algemene host bouwen en configureren
  • Verschillende interfaces en bijbehorende implementaties schrijven
  • Levensduur en bereik van service gebruiken voor DI

Vereisten

  • .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 behulp van de nieuwe dotnet-opdracht of een IDE-wizard voor een nieuw project een nieuwe .NET-consoletoepassing met de naam ConsoleDI.Example Voeg het NuGet-pakket Microsoft.Extensions.Hosting 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.Hosting" Version="8.0.0" />
  </ItemGroup>

</Project>

Belangrijk

In dit voorbeeld is het NuGet-pakket Microsoft.Extensions.Hosting 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 id van de service vertegenwoordigt.
  • Een ServiceLifetime eigenschap die de levensduur van de service vertegenwoordigt.

IkExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

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

IkExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

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

IkExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

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

Alle subinterfaces van IReportServiceLifetime het expliciet implementeren van de IReportServiceLifetime.Lifetime standaardinterface. Implementeert bijvoorbeeld IExampleTransientService expliciet IReportServiceLifetime.Lifetime met de ServiceLifetime.Transient waarde.

Standaard implementaties toevoegen

In het voorbeeld worden alle eigenschappen geïnitialiseerd Id 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. Implementeert bijvoorbeeld ExampleSingletonServiceIExampleSingletonService.

Een service toevoegen waarvoor DI is vereist

Voeg de volgende rapportklasse voor levensduur van de service toe, die als een service fungeert voor de console-app:

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

Hiermee ServiceLifetimeReporter definieert u een constructor waarvoor elk van de bovengenoemde service-interfaces, dat wil gezegd, IExampleTransientService, IExampleScopedServiceen IExampleSingletonService. Het object maakt één methode beschikbaar waarmee de consument kan rapporteren over de service met een bepaalde lifetimeDetails parameter. Wanneer deze wordt aangeroepen, registreert de ReportServiceLifetimeDetails methode de unieke id van elke service met het servicelevensbericht. 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 extra using blokken.
  • Verminder het aantal vereiste using instructies in de Program of Startup klassen waarin deze extensiemethoden doorgaans worden aangeroepen.

De app:

  • Hiermee maakt u een IHostBuilder exemplaar met instellingen voor hostbouwer.
  • Hiermee configureert u services en voegt u ze toe met de bijbehorende levensduur van de service.
  • Roept Build() een exemplaar aan en wijst een exemplaar van IHost.
  • Oproepen ExemplifyScoping, doorgeven in de IHost.Services.

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

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:

  • Transient services zijn altijd verschillend, er wordt een nieuw exemplaar gemaakt bij elke ophaalbewerking van de service.
  • Scoped services worden alleen gewijzigd met een nieuw bereik, maar zijn hetzelfde exemplaar binnen een bereik.
  • Singleton services zijn altijd hetzelfde, er wordt slechts één keer een nieuw exemplaar gemaakt.

Zie ook