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:
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 ExampleSingletonService
IExampleSingletonService
.
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
, IExampleScopedService
en 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 deProgram
ofStartup
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.