Delen via


Afhankelijkheidsinjectie in ASP.NET Core

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

Door Kirk Larkin, Steve Smithen Brandon Dahler

ASP.NET Core ondersteunt het ontwerppatroon voor afhankelijkheidsinjectie (DI), een techniek voor het bereiken van IoC- (Inversion of Control) tussen klassen en hun afhankelijkheden.

Voor Blazor DI-richtlijnen, die de richtlijnen in dit artikel aanvullen of vervangen, zie ASP.NET Core Blazor-afhankelijkheidsinjectie.

Zie Afhankelijkheidsinjectie in controllers in ASP.NET Corevoor informatie die specifiek is voor afhankelijkheidsinjectie in MVC-controllers.

Zie Afhankelijkheidsinjectie in .NETvoor informatie over het gebruik van afhankelijkheidsinjectie in andere toepassingen dan web-apps.

Zie Options-patroon in ASP.NET Corevoor informatie over afhankelijkheidsinjectie van opties.

Dit artikel bevat informatie over afhankelijkheidsinjectie in ASP.NET Core. De primaire documentatie over het gebruik van afhankelijkheidsinjectie is opgenomen in Afhankelijkheidsinjectie in .NET.

voorbeeldcode weergeven of downloaden (hoe te downloaden)

Overzicht van afhankelijkheidsinjectie

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

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Een klasse kan een exemplaar van de MyDependency-klasse maken om gebruik te maken van de WriteMessage methode. In het volgende voorbeeld is de MyDependency klasse een afhankelijkheid van de IndexModel-klasse:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

De klasse maakt en is rechtstreeks afhankelijk van de MyDependency klasse. Codeafhankelijkheden, zoals in het vorige voorbeeld, zijn problematisch en moeten om de volgende redenen worden vermeden:

  • Als u MyDependency wilt vervangen door een andere implementatie, moet de IndexModel klasse worden gewijzigd.
  • Als MyDependency afhankelijkheden heeft, moeten ze ook worden geconfigureerd door de IndexModel-klasse. In een groot project met meerdere klassen, afhankelijk van MyDependency, wordt de configuratiecode verspreid over de app.
  • Deze implementatie is moeilijk te testen.

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. ASP.NET Core biedt een ingebouwde servicecontainer, IServiceProvider. Services worden doorgaans geregistreerd in het Program.cs-bestand van de app.
  • Injectie van de service in de constructor van de klasse waarin deze wordt gebruikt. Het framework neemt de verantwoordelijkheid om een instantie van de afhankelijkheid te creëren en het af te breken wanneer dat niet meer nodig is.

In de voorbeeld-appdefinieert de IMyDependency interface de WriteMessage methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

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

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

De voorbeeld-app registreert de IMyDependency-dienst met het concrete type MyDependency. De methode AddScoped registreert de service met een omvangsgebonden levensduur, de levensduur van één aanvraag. Servicelijftijden wordt verderop in dit artikel beschreven.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

In de voorbeeld-app wordt de IMyDependency-service aangevraagd en gebruikt om de WriteMessage methode aan te roepen:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Met behulp van het DI-patroon, de controller en Razor-pagina:

  • Maakt geen gebruik van het betontype MyDependency, alleen van de IMyDependency interface die het implementeert. Hierdoor kunt u de implementatie eenvoudig wijzigen zonder de controller of Razor Pagina te wijzigen.
  • Er wordt geen exemplaar van MyDependencygemaakt; dit wordt gedaan door de DI-container.

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

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

De bijgewerkte Program.cs registreert de nieuwe IMyDependency-implementatie:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 is afhankelijk van ILogger<TCategoryName>, die bij de constructor wordt aangevraagd. ILogger<TCategoryName> is een service die door het framework wordt geleverd.

Het is niet ongebruikelijk om afhankelijkheidsinjectie op een gekoppelde manier 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, afhankelijkheidsgrafiekof objectgrafiek.

De container lost ILogger<TCategoryName> op door gebruik te maken van (algemene) open typen, waardoor het niet meer nodig is om elk (algemeen) samengestelde typete registreren.

In de terminologie van afhankelijkheidsinjectie, een service:

  • Is meestal een object dat een service aan andere objecten levert, zoals de IMyDependency-service.
  • Is niet gerelateerd aan een webservice, hoewel de service mogelijk een webservice gebruikt.

Het framework biedt een robuust logboekregistratie systeem. De IMyDependency 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 geen services hoeven te worden geregistreerd:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

De voorgaande code werkt correct zonder iets in Program.cste wijzigen, omdat logboekregistratie wordt geleverd door het framework.

Registreren van groepen services met extensiemethoden

Het ASP.NET Core-framework maakt gebruik van een conventie voor het registreren van een groep gerelateerde services. De conventie is om één Add{GROUP_NAME} extensiemethode te gebruiken om alle services te registreren die zijn vereist voor een frameworkfunctie. De extensiemethode AddControllers registreert bijvoorbeeld de services die vereist zijn voor MVC-controllers.

De volgende code wordt gegenereerd door de sjabloon Razor Pages met behulp van afzonderlijke gebruikersaccounts en laat zien hoe u aanvullende services toevoegt aan de container met behulp van de extensiemethoden AddDbContext en AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Houd rekening met het volgende waarmee services worden geregistreerd en opties worden geconfigureerd:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Gerelateerde groepen registraties kunnen worden verplaatst naar een extensiemethode om services te registreren. De configuratieservices worden bijvoorbeeld toegevoegd aan de volgende klasse:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

De resterende services worden geregistreerd in een vergelijkbare klasse. De volgende code maakt gebruik van de nieuwe extensiemethoden om de services te registreren:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Opmerking: Elke services.Add{GROUP_NAME} extensiemethode voegt services toe en configureert deze mogelijk. AddControllersWithViews voegt bijvoorbeeld de services toe die vereist zijn door MVC-controllers met weergaven, en AddRazorPages voegt de services toe die vereist zijn door Razor Pagina's.

Levensduur van de service

Zie Dienstenlevensduur in afhankelijkheidsinjectie in .NET

Als u scoped services in middleware wilt gebruiken, gebruikt u een van de volgende methoden:

  • Injecteer de service in de methode Invoke of InvokeAsync van de middleware. Het gebruik van constructorinjectie genereert een runtime-uitzondering omdat de scoped service zich als een singleton gedraagt. Het voorbeeld in de sectie Levensduur en registratieopties laat de InvokeAsync benadering zien.
  • Gebruik Factory gebaseerde middleware. Middleware die is geregistreerd met deze benadering wordt geactiveerd per clientaanvraag (verbinding), waardoor scoped services kunnen worden geïnjecteerd in de constructor van de middleware.

Zie Aangepaste ASP.NET Core-middleware schrijvenvoor meer informatie.

Serviceregistratiemethoden

Zie Service-registratiemethoden in Afhankelijkheidsinjectie in .NET

Het is gebruikelijk om meerdere implementaties te gebruiken wanneer mockingtypen voor het testen van.

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 aannemen. Met deze methoden kunnen meerdere exemplaren van een service worden geregistreerd, maar ze hebben allemaal hetzelfde type implementatie.

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

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Sleutelservices

De term keyed services verwijst naar een mechanisme voor het registreren en ophalen van Dependency Injection (DI)-services met behulp van sleutels. Een service is gekoppeld aan een sleutel door AddKeyedSingleton (of AddKeyedScoped of AddKeyedTransient) aan te roepen om deze te registreren. Open een geregistreerde service door de sleutel op te geven met het kenmerk [FromKeyedServices]. De volgende code laat zien hoe u sleutelservices gebruikt:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Sleutelservices in Middleware

Middleware ondersteunt Keyed-services in zowel de constructor als de Invoke/InvokeAsync methode:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");

var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("test")] MySingletonClass service)
    {
        _next = next;
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("test2")]
            MyScopedClass scopedService) => _next(context);
}

Zie voor meer informatie over het maken van middleware Schrijf de aangepaste ASP.NET Core-middleware.

Gedrag van constructorinjectie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Entity Framework-contexten

Entity Framework-contexten worden standaard aan de servicecontainer toegevoegd met behulp van de scoped levensduur omdat webapplicatie-databasebewerkingen meestal gericht zijn op het clientverzoek. Als u een andere levensduur wilt gebruiken, geeft u de levensduur op met behulp van een AddDbContext overbelasting. Services van een bepaalde levensduur mogen geen databasecontext gebruiken met een levensduur die korter is dan de levensduur van de service.

Opties voor levensduur en registratie

Bekijk de volgende interfaces die een taak vertegenwoordigen als een bewerking met een id, OperationIdom het verschil tussen de levensduur van de service en de bijbehorende registratieopties te demonstreren. Afhankelijk van hoe de levensduur van de service van een bewerking is geconfigureerd voor de volgende interfaces, biedt de container dezelfde of verschillende exemplaren van de service wanneer deze door een klasse wordt aangevraagd:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Met de volgende Operation klasse worden alle voorgaande interfaces geïmplementeerd. De Operation constructor genereert een GUID en slaat de laatste 4 tekens op in de eigenschap OperationId:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Met de volgende code worden meerdere registraties van de Operation-klasse gemaakt op basis van de benoemde levensduur:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

De voorbeeld-app demonstreert de levensduur van objecten zowel binnen als tussen aanvragen. De IndexModel en de middleware vragen elk type IOperation aan en loggen de OperationId voor elk type.

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Net als bij de IndexModellost de middleware dezelfde services op.

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Bereik- en tijdelijke services moeten worden opgelost in de methode InvokeAsync:

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

De uitvoer van de log toont:

  • tijdelijke objecten verschillen altijd. De tijdelijke OperationId waarde verschilt in de IndexModel en in de middleware.
  • scoped objecten zijn hetzelfde voor een specifiek verzoek, maar verschillen voor elk nieuw verzoek.
  • Singleton--objecten zijn voor elke aanvraag hetzelfde.

Als u de uitvoer van logboekregistratie wilt verminderen, stelt u 'Logging:LogLevel:Microsoft:Error' in het appsettings.Development.json bestand in:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Een service bij het opstarten van de app oplossen

De volgende code laat zien hoe u een scoped service voor een beperkte duur kunt oplossen wanneer de app wordt gestart:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Bereikvalidatie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Zie Bereikvalidatievoor meer informatie.

Services aanvragen

Services en hun afhankelijkheden binnen een ASP.NET Core-aanvraag worden weergegeven via HttpContext.RequestServices.

Het framework creëert een bereik per aanvraag en RequestServices stelt de serviceprovider met bereik beschikbaar. Alle scoped services zijn geldig zolang de aanvraag actief is.

Notitie

Geef de voorkeur aan het aanvragen van afhankelijkheden als constructorparameters boven het afleiden van services van RequestServices. Het aanvragen van afhankelijkheden als constructorparameters levert klassen op die gemakkelijker te testen zijn.

Ontwerpdiensten voor afhankelijkheidsinjectie

Bij het ontwerpen van services voor afhankelijkheidsinjectie:

  • Vermijd stateful, statische klassen en leden. Vermijd het maken van een globale status door apps te ontwerpen om in plaats daarvan singleton-services te gebruiken.
  • Vermijd directe instantiëring van afhankelijke klassen binnen services. Directe instantiëring koppelt de code aan een bepaalde implementatie.
  • Maak services klein, goed gefactoreerd en eenvoudig getest.

Als een klasse veel geïnjecteerde afhankelijkheden heeft, kan het een teken zijn dat de klasse te veel verantwoordelijkheden heeft en de SRP (Single Responsibility Principle)schendt. Probeer de klasse te herstructureren door een deel van de verantwoordelijkheden naar nieuwe klassen te verplaatsen. Houd er rekening mee dat Razor paginamodelklassen en MVC-controllerklassen zich moeten richten op problemen met de gebruikersinterface.

Verwijdering van diensten

De container roept Dispose op voor de IDisposable typen die het maakt. Services die zijn omgezet vanuit de container, mogen nooit worden verwijderd door de ontwikkelaar. Als een type of factory is geregistreerd als een singleton, wordt de singleton automatisch opgeruimd door de container.

In het volgende voorbeeld worden de services gemaakt door de servicecontainer en automatisch verwijderd: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

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

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

De console voor foutopsporing toont de volgende uitvoer na elke vernieuwing van de indexpagina:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Services die niet zijn gemaakt door de servicecontainer

Houd rekening met de volgende code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

In de voorgaande code:

  • De service-exemplaren worden niet gemaakt door de servicecontainer.
  • Het framework verwijdert de services niet automatisch.
  • De ontwikkelaar is verantwoordelijk voor het verwijderen van de services.

IDisposable-richtlijnen voor tijdelijke en gedeelde exemplaren

Zie IDisposable-richtlijnen voor tijdelijke en gedeelde instanties in afhankelijkheidsinjectie in .NET

Standaard servicecontainer vervanging

Zie vervanging van de standaardservicecontainer in Afhankelijkheidsinjectie in .NET

Aanbevelingen

Zie Aanbevelingen in Afhankelijkheidsinjectie in .NET

  • Vermijd het gebruik van het service locator patroon. Roep bijvoorbeeld geen GetService aan om een service-exemplaar te verkrijgen wanneer u in plaats daarvan DI kunt gebruiken:

    Onjuist:

    onjuiste code

    Juiste:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Een andere servicezoekervariatie om te voorkomen, is het injecteren van een factory die afhankelijkheden tijdens runtime oplost. Beide procedures combineren Inversion of Control strategieën.

  • Vermijd statische toegang tot HttpContext (bijvoorbeeld IHttpContextAccessor.HttpContext).

DI is een alternatief voor statische/globale objecttoegangspatronen. Mogelijk kunt u de voordelen van DI niet realiseren als u deze combineert met statische objecttoegang.

Orchard Core is een toepassingsframework voor het bouwen van modulaire, multitenant-toepassingen op ASP.NET Core. Zie de Orchard Core Documentationvoor meer informatie.

Zie de Orchard Core-voorbeelden voor voorbeelden van het bouwen van modulaire en multitenant-apps met behulp van alleen het Boomgaard Core Framework zonder een van de CMS-specifieke functies.

Door framework geleverde services

Program.cs registreert services die de app gebruikt, inclusief platformfuncties, zoals Entity Framework Core en ASP.NET Core MVC. In eerste instantie worden de diensten die aan Program.cs zijn geleverd door het framework in IServiceCollection bepaald, afhankelijk van hoe de host is geconfigureerd. Voor apps op basis van de ASP.NET Core-sjablonen registreert het framework meer dan 250 services.

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

Servicetype Levensduur
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortstondig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Kortstondig
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortstondig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortstondig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Aanvullende informatiebronnen

Door Kirk Larkin, Steve Smithen Brandon Dahler

ASP.NET Core ondersteunt het ontwerppatroon voor afhankelijkheidsinjectie (DI), een techniek voor het bereiken van IoC- (Inversion of Control) tussen klassen en hun afhankelijkheden.

Zie Afhankelijkheidsinjectie in controllers in ASP.NET Corevoor meer informatie over afhankelijkheidsinjectie in MVC-controllers.

Zie Afhankelijkheidsinjectie in .NETvoor informatie over het gebruik van afhankelijkheidsinjectie in andere toepassingen dan web-apps.

Zie Options-patroon in ASP.NET Corevoor meer informatie over afhankelijkheidsinjectie van opties.

Dit onderwerp bevat informatie over afhankelijkheidsinjectie in ASP.NET Core. De primaire documentatie over het gebruik van afhankelijkheidsinjectie is opgenomen in Afhankelijkheidsinjectie in .NET.

Voorbeeldcode bekijken of downloaden (hoe te downloaden)

Overzicht van afhankelijkheidsinjectie

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

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Een klasse kan een exemplaar van de MyDependency-klasse maken om gebruik te maken van de WriteMessage methode. In het volgende voorbeeld is de MyDependency klasse een afhankelijkheid van de IndexModel-klasse:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

De klasse maakt en is rechtstreeks afhankelijk van de MyDependency klasse. Codeafhankelijkheden, zoals in het vorige voorbeeld, zijn problematisch en moeten om de volgende redenen worden vermeden:

  • Als u MyDependency wilt vervangen door een andere implementatie, moet de IndexModel klasse worden gewijzigd.
  • Als MyDependency afhankelijkheden heeft, moeten ze ook worden geconfigureerd door de IndexModel-klasse. In een groot project met meerdere klassen, afhankelijk van MyDependency, wordt de configuratiecode verspreid over de app.
  • Deze implementatie is moeilijk te testen.

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. ASP.NET Core biedt een ingebouwde servicecontainer, IServiceProvider. Services worden doorgaans geregistreerd in het Program.cs-bestand van de app.
  • Injectie van de service in de constructor van de klasse waarin 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.

In de voorbeeld-appdefinieert de IMyDependency interface de WriteMessage methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

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

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

De voorbeeld-app registreert de IMyDependency service met het concrete type MyDependency. De methode AddScoped registreert de service met een levensduur binnen het bereik, de levensduur van één aanvraag. Servicelevensduren worden verderop in dit onderwerp beschreven.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

In de voorbeeld-app wordt de IMyDependency-service aangevraagd en gebruikt om de WriteMessage methode aan te roepen:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Met behulp van het DI-patroon kan de controller of Razor-pagina worden gebruikt.

  • Maakt geen gebruik van het betontype MyDependency, maar alleen van de geïmplementeerde IMyDependency-interface. Hierdoor kunt u de implementatie eenvoudig wijzigen zonder de controller of Razor Pagina te wijzigen.
  • Maakt geen exemplaar van MyDependencyaan, het wordt gemaakt door de DI-container.

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

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

De bijgewerkte Program.cs registreert de nieuwe IMyDependency-implementatie:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

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

Het is niet ongebruikelijk om afhankelijkheidsinjectie op een ketenachtige manier 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, afhankelijkheidsgrafiekof objectgrafiek.

De container lost ILogger<TCategoryName> op door gebruik te maken van (algemene) open typen, waardoor het niet meer nodig is om elk (algemeen) samengestelde typete registreren.

In de terminologie van afhankelijkheidsinjectie: een service

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

Het framework biedt een robuust logboekregistratie systeem. De IMyDependency 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 geen services hoeven te worden geregistreerd:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

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

Groepen diensten registreren met extensiemethoden

Het ASP.NET Core-framework maakt gebruik van een conventie voor het registreren van een groep gerelateerde services. De conventie is om één Add{GROUP_NAME} extensiemethode te gebruiken om alle services te registreren die zijn vereist voor een frameworkfunctie. De extensiemethode AddControllers registreert bijvoorbeeld de services die vereist zijn voor MVC-controllers.

De volgende code wordt gegenereerd door de sjabloon Razor Pages met behulp van afzonderlijke gebruikersaccounts en laat zien hoe u aanvullende services toevoegt aan de container met behulp van de extensiemethoden AddDbContext en AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Houd rekening met het volgende waarmee services worden geregistreerd en opties worden geconfigureerd:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Gerelateerde groepen registraties kunnen worden verplaatst naar een extensiemethode om services te registreren. De configuratieservices worden bijvoorbeeld toegevoegd aan de volgende klasse:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

De resterende services worden geregistreerd in een vergelijkbare klasse. De volgende code maakt gebruik van de nieuwe extensiemethoden om de services te registreren:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Opmerking: Elke services.Add{GROUP_NAME} extensiemethode voegt services toe en configureert deze mogelijk. AddControllersWithViews voegt bijvoorbeeld de services toe die nodig zijn voor MVC-controllers met weergaven, en AddRazorPages voegt de services toe die Razor Pagina's vereist.

Levensduur van de service

Zie Servicetijdslijnen in Afhankelijkheidsinjectie in .NET

Als u scoped services in middleware wilt gebruiken, gebruikt u een van de volgende methoden:

  • Injecteer de service in de methode Invoke of InvokeAsync van de middleware. Het gebruik van constructorinjectie veroorzaakt een runtime-uitzondering omdat de scoped service zich gedraagt als een singleton. Het voorbeeld in de sectie Levensduur en registratieopties laat de InvokeAsync benadering zien.
  • Gebruik factory-gebaseerde middleware. Middleware die is geregistreerd met deze benadering wordt geactiveerd per clientaanvraag (verbinding), waardoor scoped services kunnen worden geïnjecteerd in de constructor van de middleware.

Zie Aangepaste ASP.NET Core-middleware schrijvenvoor meer informatie.

Serviceregistratiemethoden

Zie Service-registratiemethoden in Afhankelijkheidsinjectie in .NET

Het is gebruikelijk om meerdere implementaties te gebruiken bij mocking-typen voor het testen van.

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 type implementatie.

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

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Essentiële diensten

Keyed Services verwijst naar een mechanisme voor het registreren en ophalen van afhankelijkheidsinjectieservices (DI) met behulp van sleutels. Een service is gekoppeld aan een sleutel door AddKeyedSingleton (of AddKeyedScoped of AddKeyedTransient) aan te roepen om deze te registreren. Open een geregistreerde service door de sleutel op te geven met het kenmerk [FromKeyedServices]. De volgende code laat zien hoe u sleutelservices gebruikt:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Gedrag van constructorinjectie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Entity Framework-contexten

Entity Framework-contexten worden standaard toegevoegd aan de servicecontainer met behulp van de scoped lifetime omdat de databasebewerkingen van web-apps normaal afgesteld zijn op clientaanvragen. Als u een andere levensduur wilt gebruiken, geeft u de levensduur op met behulp van een AddDbContext overbelasting. Services van een bepaalde levensduur mogen geen databasecontext gebruiken met een levensduur die korter is dan de levensduur van de service.

Opties voor levensduur en registratie

Bekijk de volgende interfaces die een taak vertegenwoordigen als een bewerking met een id, OperationIdom het verschil tussen de levensduur van de service en de bijbehorende registratieopties te demonstreren. Afhankelijk van hoe de levensduur van de service van een bewerking is geconfigureerd voor de volgende interfaces, biedt de container dezelfde of verschillende exemplaren van de service wanneer deze door een klasse wordt aangevraagd:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Met de volgende Operation klasse worden alle voorgaande interfaces geïmplementeerd. De Operation constructor genereert een GUID en slaat de laatste 4 tekens op in de eigenschap OperationId:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Met de volgende code worden meerdere registraties van de Operation-klasse gemaakt op basis van de benoemde levensduur:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

De voorbeeld-app demonstreert de levensduur van objecten zowel binnen als tussen aanvragen. De IndexModel en de middleware vragen elk type IOperation aan en registreren de OperationId voor elk type:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Net als bij de IndexModel, lost de middleware dezelfde services op.

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Bereik- en tijdelijke services moeten worden opgelost in de methode InvokeAsync:

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

De uitvoer van het logboek toont:

  • tijdelijke objecten verschillen altijd. De tijdelijke OperationId waarde verschilt in de IndexModel en in de middleware.
  • Scoped objecten zijn hetzelfde voor een bepaald verzoek, maar verschillen voor elk nieuw verzoek.
  • Singleton--objecten zijn voor elke aanvraag hetzelfde.

Om de loguitvoer te verminderen, stelt u 'Logging:LogLevel:Microsoft:Error' in het appsettings.Development.json file in.

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Een service bij het opstarten van de app oplossen

De volgende code laat zien hoe u een scoped service voor een beperkte duur kunt oplossen wanneer de app wordt gestart:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Bereikvalidatie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Zie Bereikvalidatievoor meer informatie.

Services aanvragen

Services en hun afhankelijkheden binnen een ASP.NET Core-aanvraag worden weergegeven via HttpContext.RequestServices.

Het framework creëert per aanvraag een scope, en RequestServices maakt de scope-serviceprovider beschikbaar. Alle scoped services zijn geldig zolang de aanvraag actief is.

Notitie

Geef de voorkeur aan het aanvragen van afhankelijkheden als constructorparameters boven het ophalen van services van RequestServices. Het aanvragen van afhankelijkheden als constructorparameters levert klassen op die gemakkelijker te testen zijn.

Services ontwerpen voor afhankelijkheidsinjectie

Bij het ontwerpen van services voor afhankelijkheidsinjectie:

  • Vermijd toestandsafhankelijke, statische klassen en leden van de klas. Vermijd het maken van een globale status door apps te ontwerpen om in plaats daarvan singleton-services te gebruiken.
  • Vermijd directe instantiëring van afhankelijke klassen binnen services. Directe instantiëring koppelt de code aan een bepaalde implementatie.
  • Maak services klein, goed gefactoreerd en eenvoudig getest.

Als een klasse veel geïnjecteerde afhankelijkheden heeft, kan het een teken zijn dat de klasse te veel verantwoordelijkheden heeft en de Single Responsibility Principle (SRP)schendt. Probeer de klasse te herstructureren door een deel van de verantwoordelijkheden naar nieuwe klassen te verplaatsen. Houd er rekening mee dat Razor paginamodelklassen en MVC-controllerklassen zich moeten richten op problemen met de gebruikersinterface.

Verwijdering van diensten

De container roept Dispose aan voor de IDisposable types die hij creëert. Services die zijn omgezet vanuit de container, mogen nooit worden verwijderd door de ontwikkelaar. Als een type of fabriek is geregistreerd als een singleton, wordt de singleton automatisch verwijderd door de container.

In het volgende voorbeeld worden de services gemaakt door de servicecontainer en automatisch verwijderd: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

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

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

De console voor foutopsporing toont de volgende uitvoer na elke vernieuwing van de indexpagina:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Services die niet zijn gemaakt door de servicecontainer

Houd rekening met de volgende code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

In de voorgaande code:

  • De services worden niet gemaakt door de servicecontainer.
  • Het framework verwijdert de services niet automatisch.
  • De ontwikkelaar is verantwoordelijk voor het disponeren van de services.

IDisposable-richtlijnen voor tijdelijke en gedeelde exemplaren

Zie IDisposable-richtlijnen voor tijdelijke en gedeelde instantie in afhankelijkheidsinjectie in .NET

Vervanging van de standaard servicecontainer

Zie Standaardservice-containervervanging in Afhankelijkheidsinjectie in .NET

Aanbevelingen

Zie Aanbevelingen in Dependency injection in .NET

  • Vermijd het gebruik van het service locator pattern. Roep bijvoorbeeld geen GetService aan om een service-exemplaar te verkrijgen wanneer u in plaats daarvan DI kunt gebruiken:

    Onjuist:

    onjuiste code

    Juiste:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Een andere servicezoekervariatie om te voorkomen, is het injecteren van een factory die afhankelijkheden tijdens runtime oplost. Beide procedures combineren Inversie van Controle strategieën.

  • Vermijd statische toegang tot HttpContext (bijvoorbeeld IHttpContextAccessor.HttpContext).

DI is een alternatief voor statische/globale objecttoegangspatronen. Mogelijk kunt u de voordelen van DI niet realiseren als u deze combineert met statische objecttoegang.

Orchard Core is een toepassingsframework voor het bouwen van modulaire toepassingen met meerdere tenants op ASP.NET Core. Zie de Orchard Core Documentationvoor meer informatie.

Zie de Orchard Core-voorbeelden voor voorbeelden van het bouwen van modulaire en multitenant-apps met behulp van alleen het Boomgaard Core Framework zonder een van de CMS-specifieke functies.

Door framework geleverde services

Program.cs registreert services die de app gebruikt, inclusief platformfuncties, zoals Entity Framework Core en ASP.NET Core MVC. In eerste instantie heeft het IServiceCollection aan Program.cs services gedefinieerd door het framework, afhankelijk van hoe de host is geconfigureerd. Voor apps op basis van de ASP.NET Core-sjablonen registreert het framework meer dan 250 services.

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

Servicetype Levensduur
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortstondig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Kortstondig
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortstondig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortstondig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Aanvullende informatiebronnen

Door Kirk Larkin, Steve Smithen Brandon Dahler

ASP.NET Core ondersteunt het ontwerppatroon voor afhankelijkheidsinjectie (DI), een techniek voor het bereiken van IoC- (Inversion of Control) tussen klassen en hun afhankelijkheden.

Zie Afhankelijkheidsinjectie in controllers in ASP.NET Corevoor meer informatie over afhankelijkheidsinjectie in MVC-controllers.

Zie Afhankelijkheidsinjectie in .NETvoor informatie over het gebruik van afhankelijkheidsinjectie in andere toepassingen dan web-apps.

Zie Options-patroon in ASP.NET Corevoor meer informatie over afhankelijkheidsinjectie van opties.

Dit onderwerp bevat informatie over afhankelijkheidsinjectie in ASP.NET Core. De primaire documentatie over het gebruik van afhankelijkheidsinjectie is opgenomen in Afhankelijkheidsinjectie in .NET.

voorbeeldcode weergeven of downloaden (hoe te downloaden)

Overzicht van afhankelijkheidsinjectie

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

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Een klasse kan een exemplaar van de MyDependency-klasse maken om gebruik te maken van de WriteMessage methode. In het volgende voorbeeld is de MyDependency klasse een afhankelijkheid van de IndexModel-klasse:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

De klasse maakt en is rechtstreeks afhankelijk van de MyDependency klasse. Codeafhankelijkheden, zoals in het vorige voorbeeld, zijn problematisch en moeten om de volgende redenen worden vermeden:

  • Als u MyDependency wilt vervangen door een andere implementatie, moet de IndexModel klasse worden gewijzigd.
  • Als MyDependency afhankelijkheden heeft, moeten ze ook worden geconfigureerd door de IndexModel-klasse. In een groot project met meerdere klassen, afhankelijk van MyDependency, wordt de configuratiecode verspreid over de app.
  • Deze implementatie is moeilijk te testen.

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. ASP.NET Core biedt een ingebouwde servicecontainer, IServiceProvider. Services worden doorgaans geregistreerd in het Program.cs-bestand van de app.
  • Invoegen van de service in de constructor van de class waarin het wordt gebruikt. Het framework neemt de verantwoordelijkheid op zich voor het creëren van een instantie van de afhankelijkheid en het afhandelen ervan wanneer deze niet meer nodig is.

In de voorbeeld-appdefinieert de IMyDependency interface de WriteMessage methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

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

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

De voorbeeld-app registreert de IMyDependency-dienst met het concrete type MyDependency. De methode AddScoped registreert de service met een gescopeerde levensduur, de levensduur van een enkele aanvraag. Servicelevensduren worden verderop in dit onderwerp beschreven.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

In de voorbeeld-app wordt de IMyDependency-service aangevraagd en gebruikt om de WriteMessage methode aan te roepen:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Door gebruik te maken van het DI-patroon, de controller of Razor pagina.

  • Maakt geen gebruik van het betontype MyDependency, alleen van de interface IMyDependency die het implementeert. Hierdoor kunt u de implementatie eenvoudig wijzigen zonder de controller of Razor Pagina te wijzigen.
  • Er wordt geen exemplaar van MyDependencygemaakt, deze wordt gemaakt door de DI-container.

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

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

De bijgewerkte Program.cs registreert de nieuwe IMyDependency-implementatie:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 is afhankelijk van ILogger<TCategoryName>, die wordt aangevraagd in de constructor. ILogger<TCategoryName> is een door het framework geleverde service.

Het is niet ongebruikelijk om afhankelijkheidsinjectie op een opeenvolgende wijze 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, afhankelijkheidsgrafiekof objectgrafiek.

De container lost ILogger<TCategoryName> op door gebruik te maken van (algemene) open typen, waardoor het niet meer nodig is om elk (algemeen) samengestelde typete registreren.

In de terminologie van afhankelijkheidsinjectie, een service:

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

Het framework biedt een robuust logboekregistratie systeem. De IMyDependency 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 geen services hoeven te worden geregistreerd:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

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

Groepen services registreren met extensiemethoden

Het ASP.NET Core-framework maakt gebruik van een conventie voor het registreren van een groep gerelateerde services. De conventie is om één Add{GROUP_NAME} extensiemethode te gebruiken om alle services te registreren die zijn vereist voor een frameworkfunctie. De extensiemethode AddControllers registreert bijvoorbeeld de services die vereist zijn voor MVC-controllers.

De volgende code wordt gegenereerd door de sjabloon Razor Pages met behulp van afzonderlijke gebruikersaccounts en laat zien hoe u aanvullende services toevoegt aan de container met behulp van de extensiemethoden AddDbContext en AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Houd rekening met het volgende waarmee services worden geregistreerd en opties worden geconfigureerd:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Gerelateerde groepen registraties kunnen worden verplaatst naar een extensiemethode om services te registreren. De configuratieservices worden bijvoorbeeld toegevoegd aan de volgende klasse:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

De resterende services worden geregistreerd in een vergelijkbare klasse. De volgende code maakt gebruik van de nieuwe extensiemethoden om de services te registreren:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Opmerking: Elke services.Add{GROUP_NAME} extensiemethode voegt services toe en configureert deze mogelijk. AddControllersWithViews voegt bijvoorbeeld de services toe die MVC-controllers voor weergaven vereisen, en AddRazorPages voegt de services toe die Razor-pagina's vereisen.

Levensduur van de service

Zie levensduur van diensten in Afhankelijkheidsinjectie in .NET

Als u scoped services in middleware wilt gebruiken, gebruikt u een van de volgende methoden:

  • Injecteer de service in de methode Invoke of InvokeAsync van de middleware. Het gebruik van constructorinjectie genereert een runtime-uitzondering omdat de scoped service zich als een singleton gedraagt. Het voorbeeld in de sectie Levensduur en registratieopties laat de InvokeAsync benadering zien.
  • Gebruik Factory-gebaseerde middleware. Middleware die is geregistreerd met deze benadering wordt geactiveerd per clientaanvraag (verbinding), waardoor scoped services kunnen worden geïnjecteerd in de constructor van de middleware.

Zie voor meer informatie Aangepaste ASP.NET Core-middleware schrijven.

Serviceregistratiemethoden

Zie Service-registratiemethoden in Afhankelijkheidsinjectie in .NET

Het is gebruikelijk om meerdere implementaties te gebruiken wanneer typen gesimuleerd worden voor het testen van.

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 type implementatie.

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

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Gedrag van constructorinjectie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Entity Framework-contexten

Entity Framework-contexten worden standaard toegevoegd aan de servicecontainer met behulp van de scoped levensduur, omdat databasebewerkingen van webapplicaties normaal gesproken zijn afgestemd op de clientaanvraag. Als u een andere levensduur wilt gebruiken, geeft u de levensduur op met behulp van een AddDbContext overbelasting. Services van een bepaalde levensduur mogen geen databasecontext gebruiken met een levensduur die korter is dan de levensduur van de service.

Opties voor levensduur en registratie

Bekijk de volgende interfaces die een taak vertegenwoordigen als een bewerking met een id, OperationIdom het verschil tussen de levensduur van de service en de bijbehorende registratieopties te demonstreren. Afhankelijk van hoe de levensduur van de service van een bewerking is geconfigureerd voor de volgende interfaces, biedt de container dezelfde of verschillende exemplaren van de service wanneer deze door een klasse wordt aangevraagd:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Met de volgende Operation klasse worden alle voorgaande interfaces geïmplementeerd. De Operation constructor genereert een GUID en slaat de laatste 4 tekens op in de eigenschap OperationId:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Met de volgende code worden meerdere registraties van de Operation-klasse gemaakt op basis van de benoemde levensduur:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

De voorbeeld-app demonstreert de levensduur van objecten zowel binnen als tussen aanvragen. De IndexModel en de middleware vragen om elk soort type IOperation en registreren de OperationId voor elk:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Net als de IndexModelbehandelt de middleware dezelfde services:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Gescopeerde en tijdelijke services moeten in de InvokeAsync-methode worden geresolveerd.

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

De uitvoer van het logboek toont:

  • tijdelijke objecten verschillen altijd. De tijdelijke OperationId waarde verschilt in de IndexModel en in de middleware.
  • Met scope zijn objecten hetzelfde voor een bepaalde aanvraag, maar verschillen voor elke nieuwe aanvraag.
  • Singleton--objecten zijn voor elke aanvraag hetzelfde.

Als u de uitvoer van logboekregistratie wilt verminderen, stelt u 'Logging:LogLevel:Microsoft:Error' in het appsettings.Development.json bestand in:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Een service bij het opstarten van de app oplossen

De volgende code laat zien hoe u een scoped service voor een beperkte duur kunt oplossen wanneer de app wordt gestart:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Bereikvalidatie

Zie constructorinjectie-gedrag in Afhankelijkheidsinjectie in .NET

Zie Bereikvalidatievoor meer informatie.

Services aanvragen

Services en hun afhankelijkheden binnen een ASP.NET Core-aanvraag worden weergegeven via HttpContext.RequestServices.

Het framework maakt een bereik per aanvraag en RequestServices de bereikserviceprovider beschikbaar maakt. Alle scoped services zijn geldig zolang de aanvraag actief is.

Notitie

Geef de voorkeur aan het aanvragen van afhankelijkheden als constructorparameters boven het oplossen van services van RequestServices. Het aanvragen van afhankelijkheden als constructorparameters levert klassen op die gemakkelijker te testen zijn.

Ontwerpen van diensten voor afhankelijkheidsinjectie

Bij het ontwerpen van diensten voor afhankelijkheidsinjectie:

  • Vermijd toestand-behoudende, statische klassen en klasseleden. Vermijd het maken van een globale status door apps te ontwerpen om in plaats daarvan singleton-services te gebruiken.
  • Vermijd directe instantiëring van afhankelijke klassen binnen services. Directe instantiëring koppelt de code aan een bepaalde implementatie.
  • Maak services klein, goed gefactoreerd en eenvoudig getest.

Als een klasse veel geïnjecteerde afhankelijkheden heeft, kan het een teken zijn dat de klasse te veel verantwoordelijkheden heeft en de Single Responsibility Principle (SRP)schendt. Probeer de klasse te herstructureren door een deel van de verantwoordelijkheden naar nieuwe klassen te verplaatsen. Houd er rekening mee dat Razor paginamodelklassen en MVC-controllerklassen zich moeten richten op problemen met de gebruikersinterface.

Verwijdering van diensten

De container roept Dispose aan voor de IDisposable typen die het aanmaakt. Services die zijn omgezet vanuit de container, mogen nooit worden verwijderd door de ontwikkelaar. Als een type of fabriek als een singleton is geregistreerd, wordt de singleton automatisch beheerd door de container.

In het volgende voorbeeld worden de services gemaakt door de servicecontainer en automatisch verwijderd: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

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

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

De console voor foutopsporing toont de volgende uitvoer na elke vernieuwing van de indexpagina:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Services die niet zijn gemaakt door de servicecontainer

Houd rekening met de volgende code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

In de voorgaande code:

  • De service-exemplaren worden niet gemaakt door de servicecontainer.
  • Het framework verwijdert de services niet automatisch.
  • De ontwikkelaar is verantwoordelijk voor het verwijderen van de services.

IDisposable-richtlijnen voor tijdelijke en gedeelde exemplaren

Zie IDisposable-richtlijnen voor tijdelijke en gedeelde exemplaren in afhankelijkheidsinjectie in .NET

Standaardservicecontainervervanging

Zie Vervanging van de standaardservicecontainer in Afhankelijkheidsinjectie in .NET

Aanbevelingen

Zie Aanbevelingen in Dependency injection in .NET

  • Vermijd het gebruik van de service locator pattern. Roep bijvoorbeeld geen GetService aan om een service-exemplaar te verkrijgen wanneer u in plaats daarvan DI kunt gebruiken:

    Onjuist:

    onjuiste code

    Juiste:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Een andere servicezoekervariatie om te voorkomen, is het injecteren van een factory die afhankelijkheden tijdens runtime oplost. Beide methodes combineren Inversie van controle strategieën.

  • Vermijd statische toegang tot HttpContext (bijvoorbeeld IHttpContextAccessor.HttpContext).

DI is een alternatief voor statische/globale objecttoegangspatronen. Mogelijk kunt u de voordelen van DI niet realiseren als u deze combineert met statische objecttoegang.

Orchard Core is een toepassingsframework voor het bouwen van modulaire toepassingen met meerdere tenants op ASP.NET Core. Zie de Orchard Core Documentationvoor meer informatie.

Zie de Orchard Core-voorbeelden voor voorbeelden van het bouwen van modulaire en multitenant-apps met behulp van alleen het Boomgaard Core Framework zonder een van de CMS-specifieke functies.

Door framework geleverde services

Program.cs registreert services die de app gebruikt, inclusief platformfuncties, zoals Entity Framework Core en ASP.NET Core MVC. In eerste instantie worden aan Program.cs door IServiceCollection diensten geleverd die door het framework zijn gedefinieerd, afhankelijk van de configuratie van de host . Voor apps op basis van de ASP.NET Core-sjablonen registreert het framework meer dan 250 services.

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

Service-type Levensduur
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortstondig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Kortstondig
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortstondig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortstondig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Aanvullende informatiebronnen

Door Kirk Larkin, Steve Smith, Scott Addieen Brandon Dahler

ASP.NET Core ondersteunt het ontwerppatroon voor afhankelijkheidsinjectie (DI), een techniek voor het bereiken van IoC- (Inversion of Control) tussen klassen en hun afhankelijkheden.

Zie Afhankelijkheidsinjectie in controllers in ASP.NET Corevoor meer informatie over afhankelijkheidsinjectie in MVC-controllers.

Zie Afhankelijkheidsinjectie in .NETvoor informatie over het gebruik van afhankelijkheidsinjectie in andere toepassingen dan web-apps.

Zie Options-patroon in ASP.NET Corevoor meer informatie over afhankelijkheidsinjectie van opties.

Dit onderwerp bevat informatie over afhankelijkheidsinjectie in ASP.NET Core. De primaire documentatie over het gebruik van afhankelijkheidsinjectie is opgenomen in Afhankelijkheidsinjectie in .NET.

voorbeeldcode bekijken of downloaden (hoe te downloaden)

Overzicht van afhankelijkheidsinjectie

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

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Een klasse kan een exemplaar van de MyDependency-klasse maken om gebruik te maken van de WriteMessage methode. In het volgende voorbeeld is de MyDependency klasse een afhankelijkheid van de IndexModel-klasse:

public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet created this message.");
    }
}

De klasse maakt en is rechtstreeks afhankelijk van de MyDependency klasse. Codeafhankelijkheden, zoals in het vorige voorbeeld, zijn problematisch en moeten om de volgende redenen worden vermeden:

  • Als u MyDependency wilt vervangen door een andere implementatie, moet de IndexModel klasse worden gewijzigd.
  • Als MyDependency afhankelijkheden heeft, moeten ze ook worden geconfigureerd door de IndexModel-klasse. In een groot project met meerdere klassen, afhankelijk van MyDependency, wordt de configuratiecode verspreid over de app.
  • Deze implementatie is moeilijk te testen. De app moet een mock- of stub-MyDependency-klasse 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. ASP.NET Core biedt een ingebouwde servicecontainer, IServiceProvider. Services worden doorgaans geregistreerd in de Startup.ConfigureServices methode van de app.
  • Injectie van de service in de constructor van de klasse waarin deze wordt gebruikt. Het framework neemt de taak op zich voor het maken van een voorbeeld van de afhankelijkheid en het opruimen ervan wanneer dit niet meer nodig is.

In de voorbeeld-appdefinieert de IMyDependency interface de WriteMessage methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

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

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

De voorbeeld-app registreert de IMyDependency service met het concrete type MyDependency. De methode AddScoped registreert de service met een scope-gebonden levensduur, beperkt tot de duur van één aanvraag. Servicelevensduren worden verderop in dit onderwerp beschreven.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();

    services.AddRazorPages();
}

In de voorbeeld-app wordt de IMyDependency-service aangevraagd en gebruikt om de WriteMessage methode aan te roepen:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Door het DI-patroon te gebruiken, doet de controller het volgende:

  • Maakt geen gebruik van het betontype MyDependency, maar alleen van de interface IMyDependency die het implementeert. Hierdoor kunt u eenvoudig de implementatie wijzigen die de controller gebruikt zonder de controller te wijzigen.
  • Er wordt geen exemplaar van MyDependencygemaakt, dit gebeurt door de DI-container.

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

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

De bijgewerkte methode ConfigureServices registreert de nieuwe IMyDependency-implementatie:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 is afhankelijk van ILogger<TCategoryName>, dat wordt opgevraagd in de constructor. ILogger<TCategoryName> is een door het framework geleverde dienst.

Het is niet ongebruikelijk om afhankelijkheidsinjectie in 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, afhankelijkheidsgrafiekof objectgrafiek.

De container lost ILogger<TCategoryName> op door gebruik te maken van (algemene) open typen, waardoor het niet meer nodig is om elk (algemeen) samengestelde typete registreren.

In de terminologie van afhankelijkheidsinjectie, een service:

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

Het framework biedt een robuust logboekregistratie systeem. De IMyDependency 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 geen services hoeven te worden geregistreerd in ConfigureServices:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; }

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

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

Services die in de opstartfase worden geïnjecteerd

Services kunnen worden geïnjecteerd in de Startup constructor en de Startup.Configure methode.

Alleen de volgende services kunnen worden geïnjecteerd in de Startup constructor bij gebruik van de algemene host (IHostBuilder):

Elke service die is geregistreerd bij de DI-container kan worden geïnjecteerd in de Startup.Configure methode:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    ...
}

Zie voor meer informatie App-opstart in ASP.NET Core en Toegangsconfiguratie in Opstart.

Groepen van services registreren met extensiemethoden

Het ASP.NET Core-framework maakt gebruik van een conventie voor het registreren van een groep gerelateerde services. De conventie is om één Add{GROUP_NAME} extensiemethode te gebruiken om alle services te registreren die zijn vereist voor een frameworkfunctie. De extensiemethode AddControllers registreert bijvoorbeeld de services die vereist zijn voor MVC-controllers.

De volgende code wordt gegenereerd door de sjabloon Razor Pages met behulp van afzonderlijke gebruikersaccounts en laat zien hoe u aanvullende services toevoegt aan de container met behulp van de extensiemethoden AddDbContext en AddDefaultIdentity:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Houd rekening met de volgende ConfigureServices methode, waarmee services worden geregistreerd en opties worden geconfigureerd:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

Gerelateerde groepen registraties kunnen worden verplaatst naar een extensiemethode om services te registreren. De configuratieservices worden bijvoorbeeld toegevoegd aan de volgende klasse:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

De resterende services worden geregistreerd in een vergelijkbare klasse. In de volgende ConfigureServices methode worden de nieuwe extensiemethoden gebruikt om de services te registreren:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

Opmerking: Elke services.Add{GROUP_NAME} extensiemethode voegt services toe en configureert deze mogelijk. AddControllersWithViews voegt bijvoorbeeld de services toe die de MVC-controllers met weergaven vereisen, en AddRazorPages voegt de services toe die Razor Pagina's vereist. Het is raadzaam dat apps de naamconventie voor het maken van extensiemethoden in de Microsoft.Extensions.DependencyInjection naamruimte volgen. Extensiemethoden maken in de Microsoft.Extensions.DependencyInjection naamruimte:

  • Omvat groepen serviceregistraties.
  • Biedt handige IntelliSense- toegang tot de service.

Levensduur van de service

Zie servicelevensduur in afhankelijkheidsinjectie in .NET

Als u scoped services in middleware wilt gebruiken, gebruikt u een van de volgende methoden:

  • Injecteer de service in de methode Invoke of InvokeAsync van de middleware. Het gebruik van constructorinjectie genereert een runtime-uitzondering omdat de scoped service zich als een singleton gedraagt. Het voorbeeld in de sectie Levensduur en registratieopties laat de InvokeAsync benadering zien.
  • Gebruik Factory gebaseerde middleware. Middleware die met deze methode is geregistreerd, wordt geactiveerd per clientaanvraag (verbinding), waardoor scoped services kunnen worden geïnjecteerd in de InvokeAsync methode van middleware.

Zie Aangepaste ASP.NET Core-middleware schrijvenvoor meer informatie.

Serviceregistratiemethoden

Zie Service-registratiemethoden in Afhankelijkheidsinjectie in .NET

Het is gebruikelijk om meerdere implementaties te gebruiken wanneer je typen nabootst voor het testen van.

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 type implementatie.

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

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Gedrag van constructorinjectie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Entity Framework-contexten

Entity Framework-contexten worden standaard toegevoegd aan de servicecontainer met behulp van de gedefinieerde levensduur omdat databasebewerkingen van webapplicaties normaal gesproken gebonden zijn aan de clientaanvraag. Als u een andere levensduur wilt gebruiken, geeft u de levensduur op met behulp van een AddDbContext overbelasting. Services van een bepaalde levensduur mogen geen databasecontext gebruiken met een levensduur die korter is dan de levensduur van de service.

Opties voor levensduur en registratie

Bekijk de volgende interfaces die een taak vertegenwoordigen als een bewerking met een id, OperationIdom het verschil tussen de levensduur van de service en de bijbehorende registratieopties te demonstreren. Afhankelijk van hoe de levensduur van de service van een bewerking is geconfigureerd voor de volgende interfaces, biedt de container dezelfde of verschillende exemplaren van de service wanneer deze door een klasse wordt aangevraagd:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Met de volgende Operation klasse worden alle voorgaande interfaces geïmplementeerd. De Operation constructor genereert een GUID en slaat de laatste 4 tekens op in de eigenschap OperationId:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

De methode Startup.ConfigureServices maakt meerdere registraties van de Operation-klasse op basis van de benoemde levensduur:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

    services.AddRazorPages();
}

De voorbeeld-app demonstreert de levensduur van objecten zowel binnen als tussen aanvragen. De IndexModel en de middleware doen een verzoek voor elk type IOperation en registreren de OperationId voor elk type.

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Net als bij de IndexModel, verwerkt de middleware dezelfde services.

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationTransient transientOperation,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Scoped services moeten worden opgelost in de InvokeAsync-methode.

public async Task InvokeAsync(HttpContext context,
    IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + _transientOperation.OperationId);
    _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

De uitvoer van de logboekregistratie laat zien:

  • tijdelijke objecten verschillen altijd. De tijdelijke OperationId waarde verschilt in de IndexModel en in de middleware.
  • -scoped-objecten zijn hetzelfde voor een bepaalde aanvraag, maar verschillen voor elke nieuwe aanvraag.
  • Singleton--objecten zijn voor elke aanvraag hetzelfde.

Als u de uitvoer van logboekregistratie wilt verminderen, stelt u 'Logging:LogLevel:Microsoft:Error' in het appsettings.Development.json bestand in:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Services aanroepen vanuit de hoofdmap

Maak een IServiceScope met IServiceScopeFactory.CreateScope om een scoped service binnen het bereik van de app op te lossen. Deze methode is handig voor toegang tot een scoped service bij het opstarten om initialisatietaken uit te voeren.

In het volgende voorbeeld ziet u hoe u toegang krijgt tot de vastgelegde IMyDependency-service en de WriteMessage-methode aanroept in Program.Main.

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myDependency = services.GetRequiredService<IMyDependency>();
                myDependency.WriteMessage("Call services from main");
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Bereikvalidatie

Zie constructorinjectiegedrag in Afhankelijkheidsinjectie in .NET

Zie Bereikvalidatievoor meer informatie.

Services aanvragen

Services en hun afhankelijkheden binnen een ASP.NET Core-aanvraag worden weergegeven via HttpContext.RequestServices.

Het framework creëert een scope per aanvraag, en RequestServices stelt de gescoepde serviceprovider bloot. Alle scoped services zijn geldig zolang de aanvraag actief is.

Notitie

Geef de voorkeur aan het aanvragen van afhankelijkheden als constructorparameters boven het oplossen van services van RequestServices. Het aanvragen van afhankelijkheden als constructorparameters levert klassen op die gemakkelijker te testen zijn.

Services ontwerpen voor afhankelijkheidsinjectie

Bij het ontwerpen van services voor afhankelijkheidsinjectie:

  • Vermijd stateful, statische klassen en leden. Vermijd het maken van een globale status door apps te ontwerpen om in plaats daarvan singleton-services te gebruiken.
  • Vermijd directe instantiëring van afhankelijke klassen binnen services. Directe instantiëring koppelt de code aan een bepaalde implementatie.
  • Maak services klein, goed gefactoreerd en eenvoudig getest.

Als een klasse veel geïnjecteerde afhankelijkheden heeft, kan het een teken zijn dat de klasse te veel verantwoordelijkheden heeft en de Single Responsibility Principle (SRP)schendt. Probeer de klasse te herstructureren door een deel van de verantwoordelijkheden naar nieuwe klassen te verplaatsen. Houd er rekening mee dat Razor paginamodelklassen en MVC-controllerklassen zich moeten richten op problemen met de gebruikersinterface.

Verwijdering van diensten

De container roept Dispose aan voor de IDisposable types die hij aanmaakt. Services die zijn omgezet vanuit de container, mogen nooit worden verwijderd door de ontwikkelaar. Als een type of fabriek is geregistreerd als een singleton, wordt de singleton door de container automatisch verwijderd.

In het volgende voorbeeld worden de services door de servicecontainer gemaakt en automatisch opgeruimd.

public class Service1 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

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

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

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

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    
    var myKey = Configuration["MyKey"];
    services.AddSingleton<IService3>(sp => new Service3(myKey));

    services.AddRazorPages();
}
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

De console voor foutopsporing toont de volgende uitvoer na elke vernieuwing van de indexpagina:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose

Services die niet zijn gemaakt door de servicecontainer

Houd rekening met de volgende code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());

    services.AddRazorPages();
}

In de voorgaande code:

  • De service-instanties worden niet gemaakt door de servicecontainer.
  • Het framework verwijdert de services niet automatisch.
  • De ontwikkelaar is verantwoordelijk voor het verwijderen van de services.

IDisposable-richtlijnen voor tijdelijke en gedeelde exemplaren

Zie IDisposable-richtlijnen voor vluchtige en gedeelde instanties in afhankelijkheidsinjectie in .NET

Standaardservicecontainervervanging

Zie vervanging van de standaardservicecontainer in Afhankelijkheidsinjectie in .NET

Aanbevelingen

Zie Aanbevelingen in Afhankelijkheidsinjectie in .NET

  • Vermijd het gebruik van het service locator pattern. Roep bijvoorbeeld geen GetService aan om een service-exemplaar te verkrijgen wanneer u in plaats daarvan DI kunt gebruiken:

    Onjuist:

    onjuiste code

    Juiste:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Een andere servicezoekervariatie om te voorkomen, is het injecteren van een factory die afhankelijkheden tijdens runtime oplost. Beide praktijken combineren Inversie van Controle strategieën.

  • Vermijd statische toegang tot HttpContext (bijvoorbeeld IHttpContextAccessor.HttpContext).

  • Vermijd aanroepen naar BuildServiceProvider in ConfigureServices. Het aanroepen van BuildServiceProvider gebeurt meestal wanneer de ontwikkelaar een service in ConfigureServiceswil oplossen. Denk bijvoorbeeld aan het geval waarin de LoginPath vanuit de configuratie wordt geladen. Vermijd de volgende aanpak:

    slechte code die BuildServiceProvider aanroept

    Als u in de voorgaande afbeelding de groene golvende lijn onder services.BuildServiceProvider selecteert, ziet u de volgende ASP0000 waarschuwing:

    ASP0000 Het aanroepen van 'BuildServiceProvider' vanuit de toepassingscode resulteert erin dat een extra kopie van singleton-services wordt aangemaakt. Overweeg alternatieven zoals het injecteren van services voor afhankelijkheden als parameters voor Configureren.

    Bij het aanroepen van BuildServiceProvider wordt een tweede container gemaakt, die gescheurde singletons kan maken en verwijzingen naar objectgrafieken in meerdere containers kan veroorzaken.

    Een correcte manier om LoginPath te verkrijgen, is door gebruik te maken van de ingebouwde ondersteuning van het optiespatroon voor DI.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    
        services.AddOptions<CookieAuthenticationOptions>(
                            CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IMyService>((options, myService) =>
            {
                options.LoginPath = myService.GetLoginPath();
            });
    
        services.AddRazorPages();
    }
    
  • Tijdelijke services worden opgevangen door de container voor verwijdering. Dit kan leiden tot een geheugenlek als het wordt opgelost vanaf het hoogste niveau van de container.

  • Schakel bereikvalidatie in om ervoor te zorgen dat de app geen singletons heeft die services met een beperkt bereik vastleggen. Zie Bereikvalidatievoor meer informatie.

Net als bij alle sets aanbevelingen kunnen situaties optreden waarin het negeren van een aanbeveling vereist is. Uitzonderingen zijn zeldzaam, meestal speciale gevallen binnen het framework zelf.

DI is een alternatief voor statische/globale objecttoegangspatronen. Mogelijk kunt u de voordelen van DI niet realiseren als u deze combineert met statische objecttoegang.

Orchard Core is een toepassingsframework voor het bouwen van modulaire toepassingen met meerdere tenants op ASP.NET Core. Zie de Orchard Core Documentationvoor meer informatie.

Zie de Orchard Core-voorbeelden voor voorbeelden van het bouwen van modulaire en multitenant-apps met behulp van alleen het Boomgaard Core Framework zonder een van de CMS-specifieke functies.

Door framework geleverde services

De methode Startup.ConfigureServices registreert services die door de app worden gebruikt, inclusief platformfuncties, zoals Entity Framework Core en ASP.NET Core MVC. In eerste instantie heeft het IServiceCollection aan ConfigureServices services gedefinieerd door het framework, afhankelijk van hoe de host is geconfigureerd. Voor apps op basis van de ASP.NET Core-sjablonen registreert het framework meer dan 250 services.

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

Type dienstverlening Levensduur
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortstondig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Kortstondig
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortstondig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortstondig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Aanvullende informatiebronnen