Szukaj…


Wprowadzenie

Rdzeń Aspnet jest zbudowany z Dependency Injection jako jedną z kluczowych koncepcji. Wprowadza jedną zgodną abstrakcję kontenera, dzięki czemu można zastąpić wbudowaną abstrakcję wybranym przez siebie pojemnikiem innej firmy.

Składnia

  • 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>()

Uwagi

Aby korzystać z ogólnych wariantów metod IServiceProvider , musisz dołączyć następującą przestrzeń nazw:

using Microsoft.Extensions.DependencyInjection;

Zarejestruj się i rozwiąż ręcznie

Preferowanym sposobem opisywania zależności jest użycie wstrzykiwania konstruktora zgodnie z zasadą Jawne zależności :

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();
        }
    }
}

Zarejestruj zależności

Wbudowany pojemnik zawiera zestaw wbudowanych funkcji:

Kontrola dożywotnia

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 : Tworzony za każdym razem, gdy jest rozwiązany
  • AddScoped : Utworzony raz na żądanie
  • AddSingleton : Lazily tworzone raz na aplikację
  • AddSingleton (instancja) : Udostępnia wcześniej utworzoną instancję dla aplikacji

Wymienne zależności

Możliwe jest również zarejestrowanie wyliczalnych zależności:

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

Możesz je następnie spożywać w następujący sposób:

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

Ogólne zależności

Możesz także zarejestrować ogólne zależności:

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

A następnie spożyj go w następujący sposób:

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

Pobierz zależności na kontrolerze

Po zarejestrowaniu można odzyskać zależność, dodając parametry do konstruktora kontrolera.

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

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

Wstrzyknięcie zależności do akcji kontrolera

Mniej znaną wbudowaną funkcją jest wstrzykiwanie akcji kontrolera za pomocą FromServicesAttribute .

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

Ważną uwagą jest to, że [FromServices] nie może być używany jako ogólny mechanizm „wstrzykiwania właściwości” lub „wstrzykiwania metod”! Można go używać tylko w przypadku parametrów metody akcji kontrolera lub konstruktora kontrolera (w konstruktorze jest on jednak przestarzały, ponieważ system ASP.NET Core DI już używa wstrzykiwania konstruktora i nie są wymagane żadne dodatkowe markery).

Nie można go używać nigdzie poza kontrolerami, działanie kontrolera . Jest również bardzo specyficzny dla ASP.NET Core MVC i znajduje się w zestawie Microsoft.AspNetCore.Mvc.Core .

Oryginalny cytat z wydania ASP.NET Core MVC GitHub ( Limit [FromServices], aby zastosować tylko do parametrów ), dotyczący tego atrybutu:

@rynowak:

@Eilon:

Problem z właściwościami polega na tym, że wielu ludziom wydaje się, że można je zastosować do dowolnej właściwości dowolnego obiektu.

Zgodziliśmy się, mieliśmy wiele problemów zgłaszanych przez użytkowników z niejasnościami co do sposobu korzystania z tej funkcji. Naprawdę było dość dużo informacji zwrotnych obu rodzajów: „[FromServices] jest dziwny i nie podoba mi się”, a „[FromServices] mnie zdezorientował”. Czuje się jak pułapka i coś, co zespół nadal będzie odpowiadał na pytania za lata.

Uważamy, że najcenniejszym scenariuszem dla [FromServices] jest parametr metody akcji dla usługi, której potrzebujesz tylko w tym jednym miejscu.

/ cc @ danroth27 - zmiany w dokumentacji

Wszystkim, którzy są zakochani w bieżącym [FromServices], zdecydowanie polecam przyjrzeć się systemowi DI, który może wykonywać iniekcje własności (na przykład Autofac).

Uwagi:

  • Wszelkie usługi zarejestrowane w systemie .NET Core Dependency Injection można wstrzyknąć do akcji kontrolera za pomocą atrybutu [FromServices] .

  • Najbardziej odpowiedni przypadek ma miejsce, gdy potrzebujesz usługi tylko w metodzie pojedynczego działania i nie chcesz zaśmiecać konstruktora kontrolera inną zależnością, która zostanie użyta tylko raz.

  • Nie można go używać poza ASP.NET Core MVC (tj. Aplikacjami opartymi wyłącznie na .NET Framework lub .NET Core), ponieważ znajduje się on w zestawie Microsoft.AspNetCore.Mvc.Core .

  • Do iniekcji właściwości lub metody należy użyć jednego z dostępnych kontenerów IoC innych firm (Autofac, Unity itp.).

Wzorzec opcji / Opcje wstrzykiwania do usług

Wraz z ASP.NET Core zespół Microsoft wprowadził również wzorzec opcji, który pozwala mieć mocne opcje pisania i po skonfigurowaniu możliwości wstrzykiwania opcji do twoich usług.

Najpierw zaczynamy od silnej klasy maszynowej, która utrzyma naszą konfigurację.

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

I wpis w appsettings.json .

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

Następnie inicjalizujemy go w klasie Autostart. Można to zrobić na dwa sposoby

  1. Załaduj go bezpośrednio z appsettings.json „mysettings” appsettings.json

    services.Configure<MySettings>(Configuration.GetSection("mysettings"));
    
  2. Zrób to ręcznie

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

    Każdy poziom hierarchii appsettings.json jest oddzielony : Ponieważ value2 jest własnością obiektu mysettings , uzyskujemy do niej dostęp za pośrednictwem mysettings:value2 .

Wreszcie możemy wprowadzić opcje do naszych usług, korzystając z interfejsu IOptions<T>

public class MyService : IMyService
{
    private readonly MySettings settings;

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

Uwagi

Jeśli IOptions<T> nie zostanie skonfigurowane podczas uruchamiania, wstrzyknięcie IOptions<T> spowoduje wstrzyknięcie domyślnej instancji klasy T

Korzystanie z usług o zasięgu podczas uruchamiania aplikacji / inicjowania bazy danych

Rozwiązywanie usług o zasięgu podczas uruchamiania aplikacji może być trudne, ponieważ nie ma żądania, a zatem nie ma usługi o zasięgu.

Rozwiązanie usługi o app.ApplicationServices.GetService<AppDbContext>() podczas uruchamiania aplikacji za pośrednictwem app.ApplicationServices.GetService<AppDbContext>() może powodować problemy, ponieważ zostanie ona utworzona w zasięgu globalnego kontenera, skutecznie czyniąc ją singletonem wraz z czasem życia aplikacji, co może prowadzić do wyjątków, takich jak Cannot access a disposed object in ASP.NET Core when injecting DbContext .

Poniższy wzorzec rozwiązuje problem, najpierw tworząc nowy zakres, a następnie rozwiązując z niego usługi o zasięgu, a następnie, po zakończeniu pracy, usuwając pojemnik o zasięgu.

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);
        }
    }
}

Jest to półoficjalny sposób podstawowego zespołu Entity Framework do inicjowania danych podczas uruchamiania aplikacji i znajduje odzwierciedlenie w przykładowej aplikacji MusicStore .

Rozwiązuj kontrolery, ViewComponents i TagHelpers za pomocą wstrzykiwania zależności

Domyślnie Kontrolery, ViewComponents i TagHelpers nie są rejestrowane i rozwiązywane przez kontener wstrzykiwania zależności. Powoduje to niezdolność do wykonania, tj. Wstrzyknięcia właściwości przy użyciu kontenera Inversion of Control (IoC) innej firmy, takiego jak AutoFac.

Aby program ASP.NET Core MVC rozwiązał również te typy za pośrednictwem IoC, należy dodać następujące rejestracje w Startup.cs (wzięte z oficjalnej próbki ControllersFromService na 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>();
}

Przykład wstrzykiwania zwykłej zależności (bez Startup.cs)

To pokazuje, jak korzystać z pakietu WebHostBuilder Microsoft.Extensions.DependencyInjection bez użycia WebHostBuilder z WebHostBuilder (np. Gdy chcesz zbudować coś innego niż webApp):

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;
    }
}

Wewnętrzne funkcjonowanie Microsoft.Extensions.DependencyInjection

IServiceCollection

Aby rozpocząć budowanie kontenera IOC za pomocą pakietu Microsoft Nuget DI, zacznij od utworzenia IServiceCollection . Możesz użyć już dostarczonej kolekcji: ServiceCollection :

var services = new ServiceCollection();

Ta IServiceCollection jest niczym innym jak implementacją: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable

Wszystkie następujące metody są jedynie metodami rozszerzenia ServiceDescriptor do dodawania wystąpień ServiceDescriptor do listy:

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

Servicerovider to ten, który „kompiluje” wszystkie rejestracje, aby można było z nich szybko korzystać, można to zrobić za pomocą services.BuildServiceProvider() który jest w zasadzie rozszerzeniem dla:

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

Za kulisami każdy ServiceDescriptor w IServiceCollection jest kompilowany do metody fabrycznej Func<ServiceProvider, object> gdzie obiekt jest typem zwracanym i jest: utworzoną instancją typu Implementacja, Singleton lub własną zdefiniowaną metodą fabryczną.

Te rejestracje są dodawane do ServiceTable która jest w zasadzie ConcurrentDictionary przy czym kluczem jest ServiceType i wartością zdefiniowaną powyżej metodę Factory.

Wynik

Teraz mamy ConcurrentDictionary<Type, Func<ServiceProvider, object>> którego możemy używać jednocześnie, aby poprosić o utworzenie dla nas Usług. Aby pokazać podstawowy przykład tego, jak mogło to wyglądać.

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

To nie tak działa!

ConcurrentDictionary to właściwość ServiceTable która jest własnością ServiceProvider



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow