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 deIndexModel
klasse worden gewijzigd. - Als
MyDependency
afhankelijkheden heeft, moeten ze ook worden geconfigureerd door deIndexModel
-klasse. In een groot project met meerdere klassen, afhankelijk vanMyDependency
, 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 deIMyDependency
interface die het implementeert. Hierdoor kunt u de implementatie eenvoudig wijzigen zonder de controller of Razor Pagina te wijzigen. - Er wordt geen exemplaar van
MyDependency
gemaakt; 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.cs
te 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
ofInvokeAsync
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 deInvokeAsync
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, OperationId
om 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 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 de log toont:
-
tijdelijke objecten verschillen altijd. De tijdelijke
OperationId
waarde verschilt in deIndexModel
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:
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.
Aanbevolen patronen voor multitenancy in DI
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
- ASP.NET Core Blazor afhankelijkheidsinjectie
- Invoeging van afhankelijkheden in weergaven in ASP.NET Core
- Afhankelijkheidsinjectie bij controllers in ASP.NET Core-
- Afhankelijkheidsinjectie in vereistehandlers in ASP.NET Core-
- NDC Conferentiepatronen voor DI-appontwikkeling
- app opstarten in ASP.NET Core
- Middlewareactivering op basis van factory in ASP.NET Core
- Basisbeginselen van afhankelijkheidsinjectie in .NET-
- Richtlijnen voor Afhankelijkheidsinjectie
- zelfstudie: Afhankelijkheidsinjectie gebruiken in .NET
- .NET-afhankelijkheidsinjectie
- ASP.NET KERNINJECTIE VAN AFHANKELIJKHEDEN: WAT IS DE ISERVICECOLLECTION?
- Vier manieren om IDisposables in ASP.NET Core te verwijderen
- Schone code schrijven in ASP.NET Core met afhankelijkheidsinjectie (MSDN)
- Principe van expliciete afhankelijkheden
- Inversie van Controle Containers en het Dependency Injection Patroon (Martin Fowler)
- Een service registreren bij meerdere interfaces in ASP.NET Core DI
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 deIndexModel
klasse worden gewijzigd. - Als
MyDependency
afhankelijkheden heeft, moeten ze ook worden geconfigureerd door deIndexModel
-klasse. In een groot project met meerdere klassen, afhankelijk vanMyDependency
, 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ïmplementeerdeIMyDependency
-interface. Hierdoor kunt u de implementatie eenvoudig wijzigen zonder de controller of Razor Pagina te wijzigen. - Maakt geen exemplaar van
MyDependency
aan, 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.cs
niet 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
ofInvokeAsync
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 deInvokeAsync
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, OperationId
om 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 deIndexModel
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:
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.
Aanbevolen patronen voor multitenancy in DI
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
- Afhankelijkheidsinjectie in views binnen ASP.NET Core
- Afhankelijkheidsinjectie in controllers in ASP.NET Core-
- Afhankelijkheidsinjectie in vereistenhandlers in ASP.NET Core
- ASP.NET Core Blazor afhankelijkheidsinjectie
- NDC Conferentiepatronen voor DI-appontwikkeling
- app opstarten in ASP.NET Core
- Factory-gebaseerde activering van middleware in ASP.NET Core
- Basisbeginselen van afhankelijkheidsinjectie in .NET-
- Richtlijnen voor afhankelijkheidsinjectie
- zelfstudie: Afhankelijkheidsinjectie gebruiken in .NET
- .NET-afhankelijkheidsinjectie
- ASP.NET KERNINJECTIE VAN AFHANKELIJKHEDEN: WAT IS DE ISERVICECOLLECTION?
- Vier manieren om IDisposables in ASP.NET Core te verwijderen
- Schone code schrijven in ASP.NET Core met afhankelijkheidsinjectie (MSDN)
- Expliciete afhankelijkheden principe
- Inversie van Controle Containers en het Afhankelijksheid Injectiepatroon (Martin Fowler)
- Een service registreren bij meerdere interfaces in ASP.NET Core DI
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 deIndexModel
klasse worden gewijzigd. - Als
MyDependency
afhankelijkheden heeft, moeten ze ook worden geconfigureerd door deIndexModel
-klasse. In een groot project met meerdere klassen, afhankelijk vanMyDependency
, 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 interfaceIMyDependency
die het implementeert. Hierdoor kunt u de implementatie eenvoudig wijzigen zonder de controller of Razor Pagina te wijzigen. - Er wordt geen exemplaar van
MyDependency
gemaakt, 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.cs
niet 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
ofInvokeAsync
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 deInvokeAsync
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, OperationId
om 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 IndexModel
behandelt 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 deIndexModel
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:
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.
Aanbevolen patronen voor multitenancy in DI
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
- Afhankelijkheidsinjectie in weergaven in ASP.NET Core
- Afhankelijkheidsinjectie in controllers in ASP.NET Core-
- Afhankelijkheidsinjectie in vereistenhandlers in ASP.NET Core
- ASP.NET Core Blazor afhankelijkheidsinjectie
- NDC Conferentiepatronen voor DI-appontwikkeling
- app opstarten in ASP.NET Core
- Factory-gebaseerde middlewareactivering in ASP.NET Core
- Vier manieren om IDisposables in ASP.NET Core te verwijderen
- Schone code schrijven in ASP.NET Core met afhankelijkheidsinjectie (MSDN)
- Expliciete Afhankelijkheden Principe
- Omkering van Containermodellen voor Controle en het Afhankelijkheidsinjectiepatroon (Martin Fowler)
- Een service registreren bij meerdere interfaces in ASP.NET Core DI
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 deIndexModel
klasse worden gewijzigd. - Als
MyDependency
afhankelijkheden heeft, moeten ze ook worden geconfigureerd door deIndexModel
-klasse. In een groot project met meerdere klassen, afhankelijk vanMyDependency
, 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 interfaceIMyDependency
die het implementeert. Hierdoor kunt u eenvoudig de implementatie wijzigen die de controller gebruikt zonder de controller te wijzigen. - Er wordt geen exemplaar van
MyDependency
gemaakt, 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 ConfigureServices
niet 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
ofInvokeAsync
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 deInvokeAsync
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, OperationId
om 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 deIndexModel
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:
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 vanBuildServiceProvider
gebeurt meestal wanneer de ontwikkelaar een service inConfigureServices
wil oplossen. Denk bijvoorbeeld aan het geval waarin deLoginPath
vanuit de configuratie wordt geladen. Vermijd de volgende aanpak: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.
Aanbevolen patronen voor multitenancy in DI
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
- Afhankelijkheidsinjectie in weergaven in ASP.NET Core
- Afhankelijkheidsinjectie in controllers in ASP.NET Core-
- Afhankelijkheidsinjectie in vereistenhandlers in ASP.NET Core
- ASP.NET Core Blazor afhankelijkheidsinjectie
- NDC Conferentie Patronen voor DI-appontwikkeling
- app opstarten in ASP.NET Core
- Factory-gebaseerde middlewareactivering in ASP.NET Core
- Vier manieren om IDisposables in ASP.NET Core te verwijderen
- Schone code schrijven in ASP.NET Core met afhankelijkheidsinjectie (MSDN)
- Expliciet-afhankelijkheidsprincipe
- Annotatie van objecten en afhankelijksheids-injectie patroon (Martin Fowler)
- Een service registreren bij meerdere interfaces in ASP.NET Core DI