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
, IExampleScopedService
en 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 deProgram
- ofStartup
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
ExemplifyScoping
aan 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.