.NET-afhankelijkheidsinjectie

.NET ondersteunt het ontwerppatroon voor afhankelijkheidsinjectie (DI), een techniek voor het bereiken van Inversion of Control (IoC) tussen klassen en hun afhankelijkheden. Afhankelijkheidsinjectie in .NET is een ingebouwd onderdeel van het framework, samen met configuratie, logboekregistratie en het optiespatroon.

Een afhankelijkheid is een object waarop een ander object afhankelijk is. Bekijk de volgende MessageWriter klasse met een Write methode die afhankelijk is van andere klassen:

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

Een klasse kan een exemplaar van de MessageWriter klasse maken om gebruik te maken van de Write methode. In het volgende voorbeeld is de MessageWriter klasse een afhankelijkheid van de Worker klasse:

public class Worker : BackgroundService
{
    private readonly MessageWriter _messageWriter = new();

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

De klasse maakt en is rechtstreeks afhankelijk van de MessageWriter klasse. In code vastgelegde afhankelijkheden, zoals in het vorige voorbeeld, zijn problematisch en moeten om de volgende redenen worden vermeden:

  • Als u wilt vervangen door MessageWriter een andere implementatie, moet de Worker klasse worden gewijzigd.
  • Als MessageWriter er afhankelijkheden zijn, moeten ze ook worden geconfigureerd door de Worker klasse. In een groot project met meerdere klassen, afhankelijk van, MessageWriterwordt de configuratiecode verspreid over de app.
  • Deze implementatie is moeilijk te testen. De app moet een mock- of stubklasse MessageWriter gebruiken, wat niet mogelijk is met deze benadering.

Afhankelijkheidsinjectie lost deze problemen op via:

  • Het gebruik van een interface of basisklasse om de implementatie van afhankelijkheden te abstraheren.
  • Registratie van de afhankelijkheid in een servicecontainer. .NET biedt een ingebouwde servicecontainer. IServiceProvider Services worden doorgaans geregistreerd bij het opstarten van de app en toegevoegd aan een IServiceCollection. Zodra alle services zijn toegevoegd, gebruikt BuildServiceProvider u om de servicecontainer te maken.
  • Injectie van de service in de constructor van de klasse waar deze wordt gebruikt. Het framework neemt de verantwoordelijkheid op zich voor het maken van een exemplaar van de afhankelijkheid en het verwijderen ervan wanneer dit niet meer nodig is.

Als voorbeeld definieert de IMessageWriter interface de Write methode:

namespace DependencyInjection.Example;

public interface IMessageWriter
{
    void Write(string message);
}

Deze interface wordt geïmplementeerd door een concreet type, MessageWriter:

namespace DependencyInjection.Example;

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

De voorbeeldcode registreert de IMessageWriter service met het betontype MessageWriter. De AddSingleton methode registreert de service met een singleton-levensduur, de levensduur van de app. Servicelevensduur wordt verderop in dit artikel beschreven.

using DependencyInjection.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

In de voorgaande code is de voorbeeld-app:

  • Hiermee maakt u een exemplaar van de maker van een host-app.

  • Hiermee configureert u de services door het volgende te registreren:

    • De Worker als een gehoste service. Zie Worker Services in .NET voor meer informatie.
    • De IMessageWriter interface als singleton-service met een bijbehorende implementatie van de MessageWriter klasse.
  • Bouwt de host en voert deze uit.

De host bevat de provider van de afhankelijkheidsinjectieservice. Het bevat ook alle andere relevante services die nodig zijn om de Worker implementatie automatisch te instantiëren en de bijbehorende IMessageWriter implementatie als argument te verstrekken.

namespace DependencyInjection.Example;

public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Met behulp van het DI-patroon, de werkrolservice:

  • Maakt geen gebruik van het betontype MessageWriter, alleen de IMessageWriter interface die het implementeert. Hierdoor kunt u eenvoudig de implementatie wijzigen die door de werkrolservice wordt gebruikt zonder de werkrolservice te wijzigen.
  • Er wordt geen exemplaar van MessageWriter. Het exemplaar wordt gemaakt door de DI-container.

De implementatie van de IMessageWriter interface kan worden verbeterd met behulp van de ingebouwde API voor logboekregistratie:

namespace DependencyInjection.Example;

public class LoggingMessageWriter(
    ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
    public void Write(string message) =>
        logger.LogInformation("Info: {Msg}", message);
}

De bijgewerkte AddSingleton methode registreert de nieuwe IMessageWriter implementatie:

builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

Het HostApplicationBuilder (builder) type maakt deel uit van het Microsoft.Extensions.Hosting NuGet-pakket.

LoggingMessageWriter is afhankelijk ILogger<TCategoryName>van , welke het aanvraagt in de constructor. ILogger<TCategoryName> is een door framework geleverde service.

Het is niet ongebruikelijk om afhankelijkheidsinjectie op een keten te gebruiken. Elke aangevraagde afhankelijkheid vraagt op zijn beurt zijn eigen afhankelijkheden aan. De container lost de afhankelijkheden in de grafiek op en retourneert de volledig opgeloste service. De collectieve set afhankelijkheden die moeten worden opgelost, wordt meestal aangeduid als een afhankelijkheidsstructuur, afhankelijkheidsgrafiek of objectgrafiek.

De container wordt omgezet ILogger<TCategoryName> door gebruik te maken van (algemene) open typen, waardoor het niet meer nodig is om elk (algemeen) samengesteld type te registreren.

Met terminologie voor afhankelijkheidsinjectie, een service:

  • Is meestal een object dat een service aan andere objecten levert, zoals de IMessageWriter service.
  • Is niet gerelateerd aan een webservice, hoewel de service een webservice kan gebruiken.

Het framework biedt een robuust systeem voor logboekregistratie. De IMessageWriter implementaties die in de voorgaande voorbeelden worden weergegeven, zijn geschreven om basis-DI te demonstreren, niet om logboekregistratie te implementeren. De meeste apps hoeven geen logboekregistraties te schrijven. De volgende code laat zien hoe u de standaardlogboekregistratie gebruikt, waarvoor alleen de Worker registratie als een gehoste service AddHostedServiceis vereist:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger) =>
        _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Als u de voorgaande code gebruikt, hoeft u Program.cs niet bij te werken, omdat logboekregistratie wordt geleverd door het framework.

Detectieregels voor meerdere constructors

Wanneer een type meer dan één constructor definieert, heeft de serviceprovider logica om te bepalen welke constructor moet worden gebruikt. De constructor met de meeste parameters waarin de typen DI-omzetbaar zijn, is geselecteerd. Bekijk de volgende C#-voorbeeldservice:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService)
    {
        // omitted for brevity
    }
}

In de voorgaande code wordt ervan uitgegaan dat logboekregistratie is toegevoegd en kan worden omgezet van de serviceprovider, maar dat de FooService typen BarService niet zijn. De constructor met de ILogger<ExampleService> parameter wordt gebruikt om het ExampleService exemplaar op te lossen. Hoewel er een constructor is die meer parameters definieert, zijn de FooService en BarService typen niet di-omzetbaar.

Als er dubbelzinnigheid is bij het detecteren van constructors, wordt er een uitzondering gegenereerd. Bekijk de volgende C#-voorbeeldservice:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Waarschuwing

De ExampleService code met dubbelzinnige DI-omzetbare typeparameters zou een uitzondering genereren. Doe dit niet , het is bedoeld om te laten zien wat wordt bedoeld door 'dubbelzinnige DI-omzetbare typen'.

In het voorgaande voorbeeld zijn er drie constructors. De eerste constructor is parameterloos en vereist geen services van de serviceprovider. Stel dat zowel logboekregistratie als opties zijn toegevoegd aan de DI-container en dat dit di-omzetbare services zijn. Wanneer de DI-container probeert het ExampleService type op te lossen, wordt er een uitzondering gegenereerd, omdat de twee constructors dubbelzinnig zijn.

U kunt dubbelzinnigheid voorkomen door een constructor te definiëren die in plaats daarvan beide DI-oplossingstypen accepteert:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(
        ILogger<ExampleService> logger,
        IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Groepen services registreren met extensiemethoden

Microsoft Extensions maakt gebruik van een conventie voor het registreren van een groep gerelateerde services. De conventie is om één Add{GROUP_NAME} uitbreidingsmethode te gebruiken om alle services te registreren die zijn vereist voor een frameworkfunctie. Met de AddOptions extensiemethode worden bijvoorbeeld alle services geregistreerd die vereist zijn voor het gebruik van opties.

Door framework geleverde services

Wanneer u een van de beschikbare patronen voor host- of app-opbouwfuncties gebruikt, worden standaardwaarden toegepast en worden services geregistreerd door het framework. Bekijk enkele van de populairste patronen voor host- en app-opbouwfuncties:

Nadat u een opbouwfunctie hebt gemaakt op basis van een van deze API's, zijn de IServiceCollection services gedefinieerd door het framework, afhankelijk van de manier waarop de host is geconfigureerd. Voor apps op basis van de .NET-sjablonen kan het framework honderden services registreren.

De volgende tabel bevat een klein voorbeeld van deze framework-geregistreerde services:

Servicetype Levenslang
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Singleton
IHostApplicationLifetime Singleton
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Tijdelijk
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticListener Singleton
System.Diagnostics.DiagnosticSource Singleton

Levensduur van de service

Services kunnen worden geregistreerd met een van de volgende levensduur:

In de volgende secties wordt elk van de voorgaande levensduur beschreven. Kies een geschikte levensduur voor elke geregistreerde service.

Tijdelijk

Tijdelijke levensduurservices worden telkens gemaakt wanneer ze worden aangevraagd vanuit de servicecontainer. Als u een service wilt registreren als tijdelijk, roept u het aan AddTransient.

In apps die aanvragen verwerken, worden tijdelijke services aan het einde van de aanvraag verwijderd. Voor deze levensduur worden toewijzingen per aanvraag in rekening gebracht, omdat services telkens worden opgelost en samengesteld. Zie Richtlijnen voor afhankelijkheidsinjectie voor meer informatie: IDisposable guidance for transient and shared instances.

Scoped

Voor webtoepassingen geeft een levensduur binnen het bereik aan dat services eenmaal per clientaanvraag (verbinding) worden gemaakt. Registreer scoped services bij AddScoped.

In apps die aanvragen verwerken, worden scoped services aan het einde van de aanvraag verwijderd.

Wanneer u Entity Framework Core gebruikt, registreert DbContext de AddDbContext extensiemethode standaard typen met een levensduur binnen het bereik.

Notitie

Los een scoped service niet op van een singleton en wees voorzichtig dat u dit niet indirect doet, bijvoorbeeld via een tijdelijke service. Dit kan ertoe leiden dat de service een onjuiste status heeft bij het verwerken van volgende aanvragen. Het is prima om:

  • Los een singleton-service op vanuit een bereik of tijdelijke service.
  • Een scoped service oplossen vanuit een andere scoped of tijdelijke service.

Standaard genereert het omzetten van een service uit een andere service met een langere levensduur een uitzondering in de ontwikkelomgeving. Zie Bereikvalidatie voor meer informatie.

Singleton

Singleton-levensduurservices worden gemaakt:

  • De eerste keer dat ze worden aangevraagd.
  • Door de ontwikkelaar wordt een implementatie-exemplaar rechtstreeks aan de container verstrekt. Deze aanpak is zelden nodig.

Elke volgende aanvraag van de service-implementatie van de afhankelijkheidsinjectiecontainer maakt gebruik van hetzelfde exemplaar. Als voor de app singleton-gedrag is vereist, kan de servicecontainer de levensduur van de service beheren. Implementeer het singleton-ontwerppatroon niet en geef code op om de singleton te verwijderen. Services mogen nooit worden verwijderd door code waarmee de service uit de container is omgezet. Als een type of factory is geregistreerd als een singleton, wordt de singleton automatisch verwijderd door de container.

Registreer singleton-services bij AddSingleton. Singleton-services moeten thread-veilig zijn en worden vaak gebruikt in staatloze services.

In apps die aanvragen verwerken, worden singleton-services verwijderd wanneer de ServiceProvider toepassing wordt afgesloten. Omdat het geheugen pas wordt vrijgegeven nadat de app is afgesloten, kunt u overwegen geheugen te gebruiken met een singleton-service.

Serviceregistratiemethoden

Het framework biedt methoden voor serviceregistratie-extensies die nuttig zijn in specifieke scenario's:

Methode Automatisch
object
verwijderen
Meerdere
Implementaties
Pass args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

Voorbeeld:

services.AddSingleton<IMyDep, MyDep>();
Ja Ja Nr.
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

Voorbeelden:

services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
Ja Ja Ja
Add{LIFETIME}<{IMPLEMENTATION}>()

Voorbeeld:

services.AddSingleton<MyDep>();
Ja No Nr.
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

Voorbeelden:

services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
Nr. Ja Ja
AddSingleton(new {IMPLEMENTATION})

Voorbeelden:

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
Nee No Ja

Zie de sectie Verwijdering van diensten voor meer informatie over type verwijdering.

Het registreren van een service met alleen een implementatietype is gelijk aan het registreren van die service met hetzelfde implementatie- en servicetype. Daarom kunnen meerdere implementaties van een service niet worden geregistreerd met behulp van de methoden die geen expliciet servicetype gebruiken. Deze methoden kunnen meerdere exemplaren van een service registreren, maar ze hebben allemaal hetzelfde implementatietype .

Elk van de bovenstaande serviceregistratiemethoden kan worden gebruikt om meerdere service-exemplaren van hetzelfde servicetype te registreren. In het volgende voorbeeld AddSingleton wordt twee keer aangeroepen met IMessageWriter als servicetype. De tweede aanroep om de vorige te AddSingleton overschrijven wanneer deze is omgezet als IMessageWriter en wordt toegevoegd aan de vorige wanneer meerdere services worden omgezet via IEnumerable<IMessageWriter>. Services worden weergegeven in de volgorde waarin ze zijn geregistreerd wanneer ze zijn omgezet via IEnumerable<{SERVICE}>.

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();

using IHost host = builder.Build();

_ = host.Services.GetService<ExampleService>();

await host.RunAsync();

De voorgaande voorbeeldbroncode registreert twee implementaties van de IMessageWriter.

using System.Diagnostics;

namespace ConsoleDI.IEnumerableExample;

public sealed class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is LoggingMessageWriter);

        var dependencyArray = messageWriters.ToArray();
        Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
        Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
    }
}

Hiermee ExampleService worden twee constructorparameters gedefinieerd: één IMessageWriter, en een IEnumerable<IMessageWriter>. De single IMessageWriter is de laatste implementatie die moet zijn geregistreerd, terwijl de IEnumerable<IMessageWriter> implementatie alle geregistreerde implementaties vertegenwoordigt.

Het framework biedt TryAdd{LIFETIME} ook uitbreidingsmethoden, waarmee de service alleen wordt geregistreerd als er nog geen implementatie is geregistreerd.

In het volgende voorbeeld wordt de aanroep om te AddSingleton registreren ConsoleMessageWriter als een implementatie voor IMessageWriter. De aanroep heeft TryAddSingleton geen effect omdat IMessageWriter er al een geregistreerde implementatie is:

services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();

Het TryAddSingleton heeft geen effect, omdat deze al is toegevoegd en het 'proberen' mislukt. Dit ExampleService zou het volgende bevestigen:

public class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is ConsoleMessageWriter);
        Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
    }
}

Zie voor meer informatie:

De TryAddEnumerable(ServiceDescriptor) -methoden registreren de service alleen als er nog geen implementatie van hetzelfde type is. Meerdere services worden omgezet via IEnumerable<{SERVICE}>. Voeg bij het registreren van services een exemplaar toe als nog geen van dezelfde typen is toegevoegd. Bibliotheekauteurs gebruiken TryAddEnumerable om te voorkomen dat er meerdere kopieën van een implementatie in de container worden geregistreerd.

In het volgende voorbeeld wordt de eerste aanroep om te TryAddEnumerable registreren MessageWriter als een implementatie voor IMessageWriter1. De tweede oproep registreert zich MessageWriter voor IMessageWriter2. De derde aanroep heeft geen effect omdat IMessageWriter1 er al een geregistreerde implementatie van MessageWriter:

public interface IMessageWriter1 { }
public interface IMessageWriter2 { }

public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());

Serviceregistratie is over het algemeen orderonafhankelijk, behalve bij het registreren van meerdere implementaties van hetzelfde type.

IServiceCollection is een verzameling ServiceDescriptor objecten. In het volgende voorbeeld ziet u hoe u een service registreert door een ServiceDescriptorservice te maken en toe te voegen:

string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
    typeof(IMessageWriter),
    _ => new DefaultMessageWriter(secretKey),
    ServiceLifetime.Transient);

services.Add(descriptor);

De ingebouwde Add{LIFETIME} methoden gebruiken dezelfde benadering. Zie bijvoorbeeld de broncode AddScoped.

Gedrag van constructorinjectie

Services kunnen worden omgezet met behulp van:

Constructors kunnen argumenten accepteren die niet worden geleverd door afhankelijkheidsinjectie, maar de argumenten moeten standaardwaarden toewijzen.

Wanneer services worden opgelost door IServiceProvider of ActivatorUtilities, vereist constructorinjectie een openbare constructor.

Wanneer services worden opgelost door ActivatorUtilities, vereist constructorinjectie dat er slechts één toepasselijke constructor bestaat. Overbelasting van constructors wordt ondersteund, maar er kan slechts één overbelasting bestaan waarvan alle argumenten kunnen worden vervuld door afhankelijkheidsinjectie.

Bereikvalidatie

Wanneer de app wordt uitgevoerd in de Development omgeving en CreateApplicationBuilder aanroept om de host te bouwen, voert de standaardserviceprovider controles uit om te controleren of:

  • Scoped services worden niet omgezet vanuit de hoofdserviceprovider.
  • Scoped-services worden niet in singletons geïnjecteerd.

De hoofdserviceprovider wordt gemaakt wanneer BuildServiceProvider deze wordt aangeroepen. De levensduur van de hoofdserviceprovider komt overeen met de levensduur van de app wanneer de provider begint met de app en wordt verwijderd wanneer de app wordt afgesloten.

Scoped services worden verwijderd door de container die ze heeft gemaakt. Als een scoped service wordt gemaakt in de hoofdcontainer, wordt de levensduur van de service effectief gepromoveerd tot singleton, omdat deze alleen wordt verwijderd door de hoofdcontainer wanneer de app wordt afgesloten. Als u servicebereiken valideert, worden deze situaties onderschept wanneer BuildServiceProvider deze worden aangeroepen.

Bereikscenario's

De IServiceScopeFactory is altijd geregistreerd als een singleton, maar de IServiceProvider kan variëren op basis van de levensduur van de betreffende klasse. Als u bijvoorbeeld services van een bereik oplost en een van deze services een IServiceProviderbereik neemt, is dit een instantie binnen het bereik.

Voor het bereiken van bereikservices binnen implementaties van IHostedService, zoals de BackgroundService, injecteer de serviceafhankelijkheden niet via constructorinjectie. Injecteer IServiceScopeFactoryin plaats daarvan, maak een bereik en los vervolgens afhankelijkheden van het bereik op om de juiste levensduur van de service te gebruiken.

namespace WorkerScope.Example;

public sealed class Worker(
    ILogger<Worker> logger,
    IServiceScopeFactory serviceScopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (IServiceScope scope = serviceScopeFactory.CreateScope())
            {
                try
                {
                    logger.LogInformation(
                        "Starting scoped work, provider hash: {hash}.",
                        scope.ServiceProvider.GetHashCode());

                    var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
                    var next = await store.GetNextAsync();
                    logger.LogInformation("{next}", next);

                    var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
                    await processor.ProcessAsync(next);
                    logger.LogInformation("Processing {name}.", next.Name);

                    var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
                    await relay.RelayAsync(next);
                    logger.LogInformation("Processed results have been relayed.");

                    var marked = await store.MarkAsync(next);
                    logger.LogInformation("Marked as processed: {next}", marked);
                }
                finally
                {
                    logger.LogInformation(
                        "Finished scoped work, provider hash: {hash}.{nl}",
                        scope.ServiceProvider.GetHashCode(), Environment.NewLine);
                }
            }
        }
    }
}

In de voorgaande code, terwijl de app wordt uitgevoerd, wordt de achtergrondservice uitgevoerd:

  • Afhankelijk van de IServiceScopeFactory.
  • Hiermee maakt u een IServiceScope voor het omzetten van aanvullende services.
  • Hiermee worden scoped services voor verbruik omgezet.
  • Werkt aan het verwerken van objecten en geeft ze vervolgens door en markeert ze ten slotte als verwerkt.

In de voorbeeldbroncode kunt u zien hoe implementaties van IHostedService de scoped servicelevensduur kunnen profiteren.

Sleutelservices

Vanaf .NET 8 is er ondersteuning voor serviceregistraties en zoekacties op basis van een sleutel, wat betekent dat het mogelijk is om meerdere services met een andere sleutel te registreren en deze sleutel te gebruiken voor de zoekactie.

Denk bijvoorbeeld aan het geval waarin u verschillende implementaties van de interface IMessageWriterhebt: MemoryMessageWriter en QueueMessageWriter.

U kunt deze services registreren met behulp van de overbelasting van de serviceregistratiemethoden (eerder gezien) die ondersteuning bieden voor een sleutel als parameter:

services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");

De key is niet beperkt tot string, het kan elke object gewenste zijn, zolang het type correct wordt geïmplementeerd Equals.

In de constructor van de klasse die gebruikmaakt IMessageWriter, voegt u de FromKeyedServicesAttribute klasse toe om de sleutel van de service op te geven die u wilt oplossen:

public class ExampleService
{
    public ExampleService(
        [FromKeyedServices("queue")] IMessageWriter writer)
    {
        // Omitted for brevity...
    }
}

Zie ook