Zoeken…


Invoering

Aspnet-kern is gebouwd met Dependency Injection als een van de belangrijkste kernconcepten. Het introduceert een conforme containerabstractie, zodat u de ingebouwde container kunt vervangen door een container van uw keuze.

Syntaxis

  • IServiceCollection.Add(ServiceDescriptor item);
  • IServiceCollection.AddScoped(Type serviceType);
  • IServiceCollection.AddScoped(Type serviceType, Type implementationType);
  • IServiceCollection.AddScoped(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddScoped<TService>()
  • IServiceCollection.AddScoped<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddScoped<TService, TImplementation>()
  • IServiceCollection.AddScoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceCollection.AddSingleton(Type serviceType);
  • IServiceCollection.AddSingleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddSingleton(Type serviceType, Type implementationType);
  • IServiceCollection.AddSingleton(Type serviceType, object implementationInstance);
  • IServiceCollection.AddSingleton<TService>()
  • IServiceCollection.AddSingleton<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddSingleton<TService>(TService implementationInstance)
  • IServiceCollection.AddSingleton<TService, TImplementation>()
  • IServiceCollection.AddSingleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceCollection.AddTransient(Type serviceType);
  • IServiceCollection.AddTransient(Type serviceType, Func<IServiceProvider, object> implementationFactory);
  • IServiceCollection.AddTransient(Type serviceType, Type implementationType);
  • IServiceCollection.AddTransient<TService>()
  • IServiceCollection.AddTransient<TService>(Func<IServiceProvider, TService> implementationFactory)
  • IServiceCollection.AddTransient<TService, TImplementation>()
  • IServiceCollection.AddTransient<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
  • IServiceProvider.GetService(Type serviceType)
  • IServiceProvider.GetService<T>()
  • IServiceProvider.GetServices(Type serviceType)
  • IServiceProvider.GetServices<T>()

Opmerkingen

Als u generieke varianten van IServiceProvider methoden wilt gebruiken, moet u de volgende naamruimte opnemen:

using Microsoft.Extensions.DependencyInjection;

Registreren en handmatig oplossen

De geprefereerde manier om afhankelijkheden te beschrijven, is door constructorinjectie te gebruiken volgens het principe van expliciete afhankelijkheden :

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureServices)

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(ITestService service)
        {
            int rnd = service.GenerateRandom();
        }
    }
}

Registreer afhankelijkheden

Ingebouwde container wordt geleverd met een reeks ingebouwde functies:

Levenslange controle

public void ConfigureServices(IServiceCollection services)
    {
        // ...
    
        services.AddTransient<ITestService, TestService>();
        // or
        services.AddScoped<ITestService, TestService>();
        // or
        services.AddSingleton<ITestService, TestService>();
        // or
        services.AddSingleton<ITestService>(new TestService());
    }
  • AddTransient : telkens gemaakt wanneer het is opgelost
  • AddScoped : eenmaal per aanvraag gemaakt
  • AddSingleton : Lazily eenmaal per applicatie gemaakt
  • AddSingleton (instantie) : biedt een eerder gemaakte instantie per toepassing

Talloze afhankelijkheden

Het is ook mogelijk om talloze afhankelijkheden te registreren:

 services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl1>());
 services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl2>());

Je kunt ze dan als volgt consumeren:

public class HomeController : Controller
{
    public HomeController(IEnumerable<ITestService> services)
    {
        // do something with services.
    }
}

Algemene afhankelijkheden

U kunt ook generieke afhankelijkheden registreren:

services.Add(ServiceDescriptor.Singleton(typeof(IKeyValueStore<>), typeof(KeyValueStore<>)));

En consumeer het dan als volgt:

public class HomeController : Controller
{
    public HomeController(IKeyValueStore<UserSettings> userSettings)
    {
        // do something with services.
    }
}

Haal afhankelijkheden op een controller op

Eenmaal geregistreerd kan een afhankelijkheid worden opgehaald door parameters toe te voegen aan de Controller-constructor.

// ...
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(ITestService service)
        {
            int rnd = service.GenerateRandom();
        }
    }
}

Een afhankelijkheid injecteren in een controlleractie

Een minder bekende ingebouwde functie is Controller Action-injectie met behulp van het FromServicesAttribute .

[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
     return Ok(await products.GetAllAsync());
}

Een belangrijke opmerking is dat de [FromServices] niet kunnen worden gebruikt als algemeen "Property Injection" of "Method injectie" -mechanisme! Het kan alleen worden gebruikt op methodeparameters van een controlleractie of controllerconstructor (in de constructor is het echter verouderd, omdat het ASP.NET Core DI-systeem al constructorinjectie gebruikt en er geen extra markers nodig zijn).

Het kan nergens buiten een controller worden gebruikt, controlleractie . Het is ook zeer specifiek voor ASP.NET Core MVC en bevindt zich in de Microsoft.AspNetCore.Mvc.Core assemblage.

Oorspronkelijk citaat uit het ASP.NET Core MVC GitHub-probleem ( Beperk [FromServices] alleen van toepassing op parameters ) met betrekking tot dit kenmerk:

@rynowak:

@Eilon:

Het probleem met eigenschappen is dat het voor veel mensen lijkt dat het op elke eigenschap van een object kan worden toegepast.

We zijn het erover eens dat gebruikers een aantal problemen hebben geplaatst met verwarring over hoe deze functie moet worden gebruikt. Er is echt een vrij grote hoeveelheid feedback van beide soorten "[FromServices] is raar en ik vind het niet leuk" en "[FromServices] heeft me in de war gebracht". Het voelt als een valstrik, en iets dat het team nog steeds vragen zou beantwoorden over jaren.

We hebben het gevoel dat het meest waardevolle scenario voor [FromServices] een parameterparameter is voor een actie voor een service die u alleen op die ene plek nodig hebt.

/ cc @ danroth27 - docs wijzigingen

Iedereen die verliefd is op de huidige [FromServices], raad ik ten zeerste aan om te kijken naar een DI-systeem dat onroerendgoedinjectie kan uitvoeren (bijvoorbeeld Autofac).

Opmerkingen:

  • Alle services die zijn geregistreerd bij het .NET Core Dependency Injection-systeem kunnen worden geïnjecteerd in de actie van een controller met behulp van het kenmerk [FromServices] .

  • Het meest relevante geval is wanneer u een service alleen nodig hebt in een enkele actiemethode en de constructor van uw controller niet wilt volproppen met een andere afhankelijkheid, die slechts eenmaal zal worden gebruikt.

  • Kan niet worden gebruikt buiten ASP.NET Core MVC (dwz pure .NET Framework- of .NET Core-consoletoepassingen), omdat deze zich bevindt in de Microsoft.AspNetCore.Mvc.Core assemblage.

  • Voor injectie van onroerend goed of methode moet u een van de beschikbare IoC-containers van derden gebruiken (Autofac, Unity, enz.).

Het Optiepatroon / Opties in services injecteren

Met ASP.NET Core heeft het Microsoft-team ook het Optiepatroon geïntroduceerd, dat het mogelijk maakt om sterk getypte opties te hebben en eenmaal de mogelijkheid heeft geconfigureerd om de opties in uw services te injecteren.

Eerst beginnen we met een sterk getypte klasse, die onze configuratie zal behouden.

public class MySettings 
{
    public string Value1 { get; set; }
    public string Value2 { get; set; }
}

En een vermelding in de appsettings.json .

{
  "mysettings" : {
    "value1": "Hello",
    "value2": "World"
  }
}

Vervolgens initialiseren we het in de Startup-klasse. Er zijn twee manieren om dit te doen

  1. Laad het rechtstreeks uit de appsettings.json "mijninstellingen" van appsettings.json

    services.Configure<MySettings>(Configuration.GetSection("mysettings"));
    
  2. Doe het handmatig

    services.Configure<MySettings>(new MySettings 
    {
        Value1 = "Hello",
        Value2 = Configuration["mysettings:value2"]
    });
    

    Elk hiërarchieniveau van appsettings.json wordt gescheiden door een : Omdat value2 een eigenschap is van het object mysettings , hebben we er toegang toe via mysettings:value2 .

Eindelijk kunnen we de opties in onze services injecteren, met behulp van de IOptions<T> -interface

public class MyService : IMyService
{
    private readonly MySettings settings;

    public MyService(IOptions<MySettings> mysettings) 
    {
        this.settings = mySettings.Value;
    }
}

Opmerkingen

Als de IOptions<T> niet is geconfigureerd tijdens het opstarten, injecteert IOptions<T> de standaardinstantie van de T klasse.

Scoped-services gebruiken tijdens het opstarten van de applicatie / Database-seeding

Scoped-services oplossen tijdens het opstarten van de toepassing kan moeilijk zijn, omdat er geen aanvraag en dus geen scoped-service is.

Het oplossen van een scoped-service tijdens het opstarten van de app via app.ApplicationServices.GetService<AppDbContext>() kan problemen veroorzaken, omdat deze wordt gemaakt in het bereik van de globale container, waardoor het een singleton wordt tijdens de levensduur van de applicatie, wat kan leiden tot op uitzonderingen zoals Cannot access a disposed object in ASP.NET Core when injecting DbContext .

Het volgende patroon lost het probleem op door eerst een nieuwe scope te maken en vervolgens de scoped-services ervan op te lossen en vervolgens, zodra het werk is gedaan, de scopedcontainer te verwijderen.

public Configure(IApplicationBuilder app)
{
    // serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
    using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
    {
        var db = serviceScope.ServiceProvider.GetService<AppDbContext>();

        if (await db.Database.EnsureCreatedAsync())
        {
            await SeedDatabase(db);
        }
    }
}

Dit is een semi-officiële manier van het kernteam van Entity Framework om gegevens te zaaien tijdens het opstarten van de toepassing en wordt weerspiegeld in de voorbeeldtoepassing van MusicStore .

Los Controllers, ViewComponents en TagHelpers op via Dependency Injection

Standaard worden controllers, ViewComponents en TagHelpers niet geregistreerd en opgelost via de container voor afhankelijkheidsinjectie. Dit resulteert in het onvermogen om bijvoorbeeld eigendominjectie te doen bij gebruik van een Inversion of Control-container (IoC) van derden zoals AutoFac.

Om ASP.NET Core MVC ook deze typen via IoC te laten oplossen, moet men de volgende registraties toevoegen in de Startup.cs (afkomstig uit het officiële ControllersFromService-monster op GitHub)

public void ConfigureServices(IServiceCollection services)
{
    var builder = services
        .AddMvc()
        .ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
        .AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
        .ConfigureApplicationPartManager(manager =>
        {
            manager.ApplicationParts.Add(new TypesPart(
              typeof(AnotherController),
              typeof(ComponentFromServicesViewComponent),
              typeof(InServicesTagHelper)));

            manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
        })
        .AddControllersAsServices()
        .AddViewComponentsAsServices()
        .AddTagHelpersAsServices();

    services.AddTransient<QueryValueService>();
    services.AddTransient<ValueService>();
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

Gewoon afhankelijkheid injectie voorbeeld (zonder Startup.cs)

Dit laat zien hoe u Microsoft.Extensions.DependencyInjection nuget-pakket kunt gebruiken zonder het gebruik van de WebHostBuilder van kestrel (bijvoorbeeld wanneer u iets anders dan een webApp wilt bouwen):

internal class Program
{
    public static void Main(string[] args)
    {
        var services = new ServiceCollection(); //Creates the service registry
        services.AddTransient<IMyInterface, MyClass>(); //Add registration of IMyInterface (should create an new instance of MyClass every time)
        var serviceProvider = services.BuildServiceProvider(); //Build dependencies into an IOC container
        var implementation = serviceProvider.GetService<IMyInterface>(); //Gets a dependency

        //serviceProvider.GetService<ServiceDependingOnIMyInterface>(); //Would throw an error since ServiceDependingOnIMyInterface is not registered
        var manualyInstaniate = new ServiceDependingOnIMyInterface(implementation); 

        services.AddTransient<ServiceDependingOnIMyInterface>();
        var spWithService = services.BuildServiceProvider(); //Generaly its bad practise to rebuild the container because its heavey and promotes use of anti-pattern.
        spWithService.GetService<ServiceDependingOnIMyInterface>(); //only now i can resolve
    }
}

interface IMyInterface
{
}

class MyClass : IMyInterface
{
}

class ServiceDependingOnIMyInterface
{
    private readonly IMyInterface _dependency;

    public ServiceDependingOnIMyInterface(IMyInterface dependency)
    {
        _dependency = dependency;
    }
}

Innerlijke werking van Microsoft.Extensions.DependencyInjection

IServiceCollection

Om te beginnen met het bouwen van een IOC-container met het DI nuget-pakket van Microsoft, begint u met het maken van een IServiceCollection . U kunt de reeds geleverde Collection: ServiceCollection :

var services = new ServiceCollection();

Deze IServiceCollection is niets meer dan een implementatie van: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable

Alle volgende methoden zijn alleen uitbreidingsmethoden om ServiceDescriptor instanties aan de lijst toe te voegen:

services.AddTransient<Class>(); //add registration that is always recreated
services.AddSingleton<Class>(); // add registration that is only created once and then re-used
services.AddTransient<Abstract, Implementation>(); //specify implementation for interface
services.AddTransient<Interface>(serviceProvider=> new Class(serviceProvider.GetService<IDependency>())); //specify your own resolve function/ factory method.
services.AddMvc(); //extension method by the MVC nuget package, to add a whole bunch of registrations.
// etc..

//when not using an extension method:
services.Add(new ServiceDescriptor(typeof(Interface), typeof(Class)));

IServiceProvider

De serviceprovider is degene die alle registraties 'compileert' zodat ze snel kunnen worden gebruikt, dit kan worden gedaan met services.BuildServiceProvider() wat in feite een extensie is voor:

var provider = new ServiceProvider( services, false); //false is if it should validate scopes

Achter de schermen wordt elke ServiceDescriptor in de IServiceCollection gecompileerd naar een fabrieksmethode Func<ServiceProvider, object> waarbij object het retourtype is en is: het gemaakte exemplaar van het Implementatietype, de Singleton of uw eigen gedefinieerde fabrieksmethode.

Deze registraties worden toegevoegd aan de ServiceTable die in feite een ConcurrentDictionary met als sleutel het ServiceType en de waarde van de Factory-methode die hierboven is gedefinieerd.

Resultaat

Nu hebben we een ConcurrentDictionary<Type, Func<ServiceProvider, object>> die we gelijktijdig kunnen gebruiken om te vragen Services voor ons te maken. Om een eenvoudig voorbeeld te tonen van hoe dit eruit had kunnen zien.

  var serviceProvider = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
  var factoryMethod = serviceProvider[typeof(MyService)];
  var myServiceInstance = factoryMethod(serviceProvider)

Dit is niet hoe het werkt!

Deze ConcurrentDictionary is een eigenschap van de ServiceTable die eigendom is van de ServiceProvider



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow