.NET Core DI, 생성자에 매개 변수를 전달하는 방법


105

다음 서비스 생성자 보유

public class Service : IService
{
     public Service(IOtherService service1, IAnotherOne service2, string arg)
     {

     }
}

.NET Core IOC 메커니즘을 사용하여 매개 변수를 전달하는 선택 사항은 무엇입니까?

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));

다른 방법이 있습니까?


3
디자인을 변경하십시오. 매개 변수 객체에 인수를 추출하고 주입합니다.
Steven

답변:


125

팩토리 대리자 의 식 매개 변수 ( 이 경우 x )는 IServiceProvider.

이를 사용하여 종속성을 해결하십시오.

_serviceCollection.AddSingleton<IService>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
                x.GetRequiredService<IAnotherOne>(), 
                ""));

공장 대표는 지연된 호출입니다. 형식이 확인 될 때마다 완료된 공급자를 대리자 매개 변수로 전달합니다.


1
예, 이것이 제가 지금하고있는 방식이지만 다른 방법이 있습니까? 더 우아할까요? 등록 된 서비스 인 다른 매개 변수가 있으면 조금 이상해 보일 것입니다. 서비스를 정상적으로 등록하고 서비스가 아닌 인수,이 경우에는 인수 만 전달하는 것과 같은 것을 찾고 있습니다. Autofac과 같은 것.WithParameter("argument", "");
boris

1
아니요, 공급자를 수동으로 구축하는 것은 좋지 않습니다. 대리자는 지연된 호출입니다. 형식을 확인할 때마다 완료된 공급자를 대리자 매개 변수로 전달합니다.
Nkosi

@MCR은 Core DI의 기본 접근 방식입니다.
Nkosi

12
@Nkosi : 패키지 의 일부인 ActivatorUtilities.CreateInstance를 살펴보십시오 Microsoft.Extensions.DependencyInjection.Abstractions(컨테이너 특정 종속성 없음)
Tseng

감사합니다, 우리가 여기서 찾고있는 실제 답변처럼 보이는 @Tseng.
BrainSlugs83

63

권장되는 방법은 옵션 패턴 을 사용하는 것 입니다. 그러나 실용적이지 않거나 (파라미터가 시작 / 컴파일 시간이 아닌 런타임에만 알고있는 경우) 또는 종속성을 동적으로 교체해야하는 사용 사례가 있습니다.

단일 종속성 (문자열, 정수 또는 다른 유형의 종속성)을 교체해야하거나 문자열 / 정수 매개 변수 만 허용하고 런타임 매개 변수가 필요한 타사 라이브러리를 사용할 때 매우 유용합니다.

CreateInstance (IServiceProvider, Object []) 를 바로 가기로 사용해 볼 수 있습니다 ( 문자열 매개 변수 / 값 유형 / 기본 형식 (int, float, string), 테스트되지 않은 상태에서 작동하는지 확실하지 않음) (그냥 시도하고 작동하는지 확인했습니다. 여러 문자열 매개 변수) 모든 단일 종속성을 수동으로 해결하는 대신 :

_serviceCollection.AddSingleton<IService>(x => 
    ActivatorUtilities.CreateInstance<Service>(x, "");
);

매개 변수 ( CreateInstance<T>/의 마지막 매개 변수 CreateInstance)는 교체해야하는 매개 변수를 정의합니다 (제공자에서 확인되지 않음). 표시되는대로 왼쪽에서 오른쪽으로 적용됩니다 (즉, 첫 번째 문자열은 인스턴스화 할 유형의 첫 번째 문자열 유형 매개 변수로 대체됩니다).

ActivatorUtilities.CreateInstance<Service> 서비스를 해결하고이 단일 활성화에 대한 기본 등록 중 하나를 대체하기 위해 여러 곳에서 사용됩니다.

예를 들어라는 클래스 MyServiceIOtherService있고 ILogger<MyService>종속성으로,이 있고 서비스를 해결하고 싶지만 기본 서비스 IOtherService(예 : 해당 OtherServiceA)를로 바꾸려면 OtherServiceB다음과 같이 할 수 있습니다.

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())

다음의 첫 번째 매개 변수는 IOtherService얻을 것 OtherServiceB보다는, 주입 OtherServiceA하지만, 나머지 매개 변수는 컨테이너에서 올 것이다.

이는 종속성이 많고 단일 공급자 만 특별히 처리하려는 경우에 유용합니다 (예 : 데이터베이스 특정 공급자를 요청 중 또는 특정 사용자에 대해 구성된 값으로 교체합니다. 런타임 및 요청 중에 만 알고있는 것). 애플리케이션이 빌드 / 시작될 때가 아닙니다.)

또한 ActivatorUtilities.CreateFactory (Type, Type []) 메서드 를 사용하여 더 나은 성능을 제공하는 GitHub ReferenceBenchmark를 제공하므로 대신 팩토리 메서드 를 만들 수 있습니다 .

나중에 유형이 매우 자주 확인 될 때 ​​유용합니다 (예 : SignalR 및 기타 요청이 많은 시나리오). 기본적으로 ObjectFactory비아를 만들 것입니다.

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });

그런 다음 (변수 등으로) 캐시하고 필요한 곳에 호출하십시오.

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

## 업데이트 : 문자열과 정수로도 작동하는지 확인하기 위해 직접 시도했으며 실제로 작동합니다. 다음은 테스트 한 구체적인 예입니다.

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddTransient<HelloWorldService>();
        services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow"));

        var provider = services.BuildServiceProvider();

        var demoService = provider.GetRequiredService<DemoService>();

        Console.WriteLine($"Output: {demoService.HelloWorld()}");
        Console.ReadKey();
    }
}

public class DemoService
{
    private readonly HelloWorldService helloWorldService;
    private readonly string firstname;
    private readonly string lastname;

    public DemoService(HelloWorldService helloWorldService, string firstname, string lastname)
    {
        this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService));
        this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname));
        this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname));
    }

    public string HelloWorld()
    {
        return this.helloWorldService.Hello(firstName, lastName);
    }
}

public class HelloWorldService
{
    public string Hello(string name) => $"Hello {name}";
    public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}";
}

// Just a helper method to shorten code registration code
static class ServiceProviderExtensions
{
    public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => 
        ActivatorUtilities.CreateInstance<T>(provider, parameters);
}

인쇄물

Output: Hello Tseng Stackoverflow

6
이것은 또한 ASP.NET 코어가 기본으로 컨트롤러 인스턴스화하는 방법 ControllerActivatorProvider을 , 그들이 직접하지 않는 IOC의 (에서 해결되지 .AddControllersAsServices대체 사용, ControllerActivatorProviderServiceBasedControllerActivator

16

신규 서비스가 불편하다면 Parameter Object패턴을 사용할 수 있습니다 .

따라서 문자열 매개 변수를 자체 유형으로 추출하십시오.

public class ServiceArgs
{
   public string Arg1 {get; set;}
}

그리고 생성자는 이제 다음과 같이 보일 것입니다.

public Service(IOtherService service1, 
               IAnotherOne service2, 
               ServiceArgs args)
{

}

그리고 설정

_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();

첫 번째 이점은 서비스 생성자를 변경하고 여기에 새 서비스를 추가해야하는 경우 new Service(...호출 을 변경할 필요가 없다는 것 입니다. 또 다른 이점은 설정이 조금 더 깔끔하다는 것입니다.

하나 또는 두 개의 매개 변수가있는 생성자의 경우 이것은 너무 많을 수 있습니다.


2
복잡한 매개 변수가 옵션 패턴 을 사용하는 것이 더 직관적 이고 옵션 패턴 에 권장되는 방법이지만 런타임시 (예 : 요청 또는 클레임에서) 만 알고있는 매개 변수에는 적합하지 않습니다.
Tseng

0

이 프로세스로 종속성을 주입 할 수도 있습니다.

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.