수색…


소개

Aspnet 코어는 핵심 핵심 개념 중 하나 인 Dependency Injection으로 구축됩니다. 컨테이너 컨셉을 준수하여 하나의 컨테이너 추상화를 도입하여 내장 컨테이너를 원하는 타사 컨테이너로 대체 할 수 있습니다.

통사론

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

비고

IServiceProvider 메서드의 일반적인 변형을 사용하려면 다음 네임 스페이스를 포함해야합니다.

using Microsoft.Extensions.DependencyInjection;

등록 및 수동 해결

의존성을 설명하는 바람직한 방법은 명시 적 종속성 을 따르는 생성자 삽입을 사용하는 것입니다. 원리 :

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

종속성 등록

Builtin 컨테이너에는 다음과 같은 기본 기능 세트가 있습니다.

평생 제어

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 : 해결 될 때마다 만들어 집니다 .
  • AddScoped : 요청 당 한 번 생성 됨
  • AddSingleton : 응용 프로그램마다 한 번만 지연 생성
  • AddSingleton (인스턴스) : 응용 프로그램별로 이전에 만든 인스턴스를 제공합니다.

열거 가능한 의존성

또한 열거 가능 종속성을 등록하는 것도 가능합니다.

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

그런 다음 다음과 같이 소비 할 수 있습니다.

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

일반적인 의존성

일반적인 의존성을 등록 할 수도 있습니다.

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

그리고 다음과 같이 소비하십시오.

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

컨트롤러의 종속성 검색

등록되면 컨트롤러 생성자에 매개 변수를 추가하여 종속성을 검색 할 수 있습니다.

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

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

컨트롤러 동작에 종속성 주입

잘 알려지지 않은 내장 기능은 FromServicesAttribute 사용하는 Controller Action 주입입니다.

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

중요한 것은 [FromServices] 일반적인 "Property Injection"또는 "Method Injection"메커니즘으로 사용될 수 없다는 것입니다. 컨트롤러 액션 또는 컨트롤러 생성자의 메서드 매개 변수에서만 사용할 수 있습니다 (ASP.NET Core DI 시스템에서 이미 생성자 주입을 사용하고 있으므로 별도의 마커가 필요하지 않으므로 생성자에서는 더 이상 사용되지 않습니다).

컨트롤러 외부의 어느 곳에서도 사용할 수 없습니다 . 또한 ASP.NET Core MVC에만 적용되며 Microsoft.AspNetCore.Mvc.Core 어셈블리에 있습니다.

이 속성과 관련하여 ASP.NET 코어 MVC GitHub 문제 ( [FromServices]를 매개 변수에만 적용)로 제한하십시오 .

@rynowak :

@ 아이론 :

속성의 문제는 많은 개체에게 모든 개체의 모든 속성에 적용 할 수있는 것으로 보입니다.

우리는 사용자가이 기능의 사용 방법을 혼동스럽게 생각하여 여러 가지 문제를 게시했습니다. "[FromServices]가 이상하고 나는 그것을 좋아하지 않는다는 것과"[FromServices]가 나를 혼란에 빠뜨렸다 "라는 두 가지 종류의 피드백이 실제로 많이있었습니다. 함정과 팀이 지금부터 몇 년 후에도 계속 질문에 대답 할 것이라고 생각합니다.

[FromServices]가 메소드 매개 변수에 가장 가치있는 시나리오 인 것처럼 느껴집니다. 한 곳에서만 필요한 서비스에 대한 작업입니다.

/ cc @ danroth27 - docs 변경

현재 [FromServices]와 사랑에 빠진 누구에게나 속성 주입을 할 수있는 DI 시스템 (Autofac)을 살펴 보는 것이 좋습니다.

노트:

  • .NET Core Dependency Injection 시스템에 등록 된 모든 서비스는 [FromServices] 속성을 사용하여 컨트롤러의 동작 내에 삽입 될 수 있습니다.

  • 가장 관련있는 경우는 단일 작업 메서드에서만 서비스가 필요하고 컨트롤러의 생성자를 다른 종속성과 함께 혼란스럽게 만들고 싶지 않을 때입니다.이 종속성은 한 번만 사용됩니다.

  • Microsoft.AspNetCore.Mvc.Core 어셈블리에 있기 때문에 ASP.NET Core MVC (순수한 .NET Framework 또는 .NET Core 콘솔 응용 프로그램) 외부에서는 사용할 수 없습니다.

  • 속성 또는 메소드 주입을 위해서는 사용 가능한 타사 IoC 컨테이너 (Autofac, Unity 등) 중 하나를 사용해야합니다.

옵션 패턴 / 서비스에 옵션 주입

Microsoft 팀은 ASP.NET 코어를 통해 강력한 형식화 된 옵션을 제공하고 한 번 옵션을 서비스에 삽입 할 수있는 옵션 패턴을 도입했습니다.

먼저 우리의 설정을 유지할 강력한 타입의 클래스로 시작합니다.

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

그리고 appsettings.json 의 항목.

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

다음으로 Startup 클래스에서 초기화합니다. 이 작업을 수행하는 데는 두 가지 방법이 있습니다.

  1. appsettings.json "mysettings"섹션에서 직접로드하십시오.

    services.Configure<MySettings>(Configuration.GetSection("mysettings"));
    
  2. 수동으로 수행하십시오.

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

    appsettings.json 의 각 계층 수준은 : 로 구분됩니다. value2mysettings 객체의 속성이기 때문에 mysettings:value2 를 통해 액세스합니다.

마지막으로 IOptions<T> 인터페이스를 사용하여 서비스에 옵션을 주입 할 수 있습니다.

public class MyService : IMyService
{
    private readonly MySettings settings;

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

비고

시작 중에 IOptions<T> 가 구성되지 않으면 IOptions<T> 를 주입하면 T 클래스의 기본 인스턴스가 주입됩니다.

응용 프로그램 시작 / 데이터베이스 시드 중에 범위가 지정된 서비스 사용

요청이 없으므로 범위가 지정된 서비스가 없으므로 응용 프로그램 시작 중에 범위가 지정된 서비스를 해결하는 것이 어려울 수 있습니다.

app.ApplicationServices.GetService<AppDbContext>() 를 통해 응용 프로그램을 시작하는 동안 범위가 지정된 서비스를 확인하면 전역 컨테이너의 범위에서 만들어지기 때문에 문제가 발생할 수 있으므로 응용 프로그램의 수명이 다할 때까지 효과적으로 처리 할 수 app.ApplicationServices.GetService<AppDbContext>() 문제가 발생할 수 있습니다 Cannot access a disposed object in ASP.NET Core when injecting DbContext .

다음 패턴은 먼저 새 범위를 작성한 다음 범위가 지정된 서비스를 해석 한 다음 범위 지정 컨테이너를 처리하고 작업이 완료되면 문제를 해결합니다.

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

이것은 Entity Framework 핵심 팀이 응용 프로그램을 시작하는 동안 데이터를 시드하는 준 정식 방식이며 MusicStore 샘플 응용 프로그램에 반영됩니다.

Dependency Injection을 통해 컨트롤러, ViewComponents 및 TagHelpers 해결

기본적으로 컨트롤러, ViewComponents 및 TagHelpers는 종속성 주입 컨테이너를 통해 등록 및 해결되지 않습니다. AutoFac과 같은 제 3 자 Inversion of Control (IoC) 컨테이너를 사용할 때 속성 주입을 수행 할 수 없게됩니다.

ASP.NET 코어 MVC가 IoC를 통해 이러한 유형을 해결할 수있게하려면 Startup.cs 다음 등록을 추가해야합니다 (GitHub의 공식 ControllersFromService 샘플 에서 가져옴)

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

Plain Dependency Injection 예제 (Startup.cs없이)

이것은 kestrel의 WebHostBuilder 를 사용하지 않고 Microsoft.Extensions.DependencyInjection nuget 패키지를 사용하는 방법을 보여줍니다 (예 : 다른 웹 애플리케이션을 빌드하려는 경우).

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

Microsoft.Extensions.DependencyInjection의 내부 동작

IServiceCollection

Microsoft의 DI nuget 패키지로 IOC 컨테이너를 작성하려면 IServiceCollection 을 만드는 것으로 시작하십시오. 이미 제공되는 Collection : ServiceCollection :

var services = new ServiceCollection();

IServiceCollectionIList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable

다음의 모든 메서드는 ServiceDescriptor 인스턴스를 목록에 추가하는 확장 메서드 일뿐입니다.

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

serviceprovider는 모든 등록을 '컴파일하여 신속하게 사용할 수 있도록' services.BuildServiceProvider()services.BuildServiceProvider() 합니다 .BuildServiceProvider services.BuildServiceProvider() 는 기본적으로 다음에 대한 확장 메트로 (mehtod)입니다.

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

백그라운드에서 IServiceCollection 모든 ServiceDescriptor 가 팩터 리 메서드 Func<ServiceProvider, object> 로 컴파일됩니다. 여기서 object는 반환 유형이며 구현 유형의 인스턴스, Singleton 또는 사용자 정의 팩토리 메서드입니다.

이 등록은에 추가됩니다 ServiceTable 기본적이다 ConcurrentDictionary 키가되는과 ServiceType 하고 값 위에서 정의 된 팩토리 메소드입니다.

결과

이제는 ConcurrentDictionary<Type, Func<ServiceProvider, object>> 를 사용하여 우리를 위해 서비스를 생성하도록 요청할 수 있습니다. 이것이 어떻게 보일 수 있었는지에 대한 기본 예를 보여줄 수 있습니다.

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

이것은 어떻게 작동하지 않습니다!

ConcurrentDictionaryServiceProvider 의 속성 인 ServiceTable 의 속성입니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow