Asp.Net Core에서 동일한 인터페이스의 여러 구현을 등록하는 방법은 무엇입니까?


239

동일한 인터페이스에서 파생 된 서비스가 있습니다.

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { } 
public class ServiceC : IService { }

일반적으로 다른 IoC 컨테이너를 Unity사용하면 구체적인 구현을 구체적으로 등록 할 수 Key있습니다.

ASP.NET Core에서 이러한 서비스를 등록하고 일부 키를 기반으로 런타임에 어떻게 해결합니까?

나는 구체적인 구현을 구별하기 위해 일반적으로 사용되는 또는 매개 변수 Add를 사용하는 서비스 메소드를 보지 못했습니다 .keyname

    public void ConfigureServices(IServiceCollection services)
    {            
         // How do I register services of the same interface?            
    }


    public MyController:Controller
    {
       public void DoSomething(string key)
       { 
          // How do I resolve the service by key?
       }
    }

여기에서 팩토리 패턴이 유일한 옵션입니까?

Update1 여기 에서는 여러 구체적인 구현이있을 때 팩토리 패턴을 사용하여 서비스 인스턴스를 얻는 방법을 보여주는
기사를 보았습니다 . 그러나 여전히 완벽한 솔루션은 아닙니다. 내가 전화하면_serviceProvider.GetService()메서드를 하면 생성자에 데이터를 주입 할 수 없습니다.

예를 들어 이것을 고려하십시오 :

public class ServiceA : IService
{
     private string _efConnectionString;
     ServiceA(string efconnectionString)
     {
       _efConnecttionString = efConnectionString;
     } 
}

public class ServiceB : IService
{    
   private string _mongoConnectionString;
   public ServiceB(string mongoConnectionString)
   {
      _mongoConnectionString = mongoConnectionString;
   }
}

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}

_serviceProvider.GetService()적절한 연결 문자열을 어떻게 주입 할 수 있습니까? Unity 또는 다른 IoC 라이브러리에서는 형식 등록시이를 수행 할 수 있습니다. 그러나 IOption 을 사용할 수 있지만 모든 설정을 주입해야합니다. 서비스에 특정 연결 문자열을 삽입 할 수 없습니다.

또한 다른 컨테이너 (Unity 포함)를 사용하지 않으려 고합니다. 다른 컨테이너 (예 : 컨트롤러)를 새 컨테이너에 등록해야하기 때문입니다.

또한 팩토리 패턴을 사용하여 서비스 인스턴스를 작성하는 것은 클라이언트가 여기에 세부 사항을 갖는 종속성 수가 증가하므로 DIP에 대한 것입니다. 입니다.

따라서 ASP.NET Core의 기본 DI에는 두 가지가 빠져 있다고 생각합니다.

  1. 키를 사용하여 인스턴스를 등록하는 기능
  2. 등록하는 동안 정적 데이터를 생성자에 주입하는 기능


2
마지막으로 이름 기반 등록에 대한 확장 기능 이 있습니다. 도움이 되길 바랍니다
neleus

안녕하세요, 어리석은 질문에 대해 죄송하지만 Microsoft.Extensions.DependencyInjection을 처음 사용합니다. "공용 인터페이스 IServiceA : IService"및 "공용 클래스 ServiceA : IServiceA와 같이 Iservice를 확장하는 3 개의 빈 인터페이스를 생성한다고 생각하십니까? "... 좋은 연습 옵션이 될 수 있습니까?
Emiliano Magliocca

이 기사는 어떤 용도로 사용됩니까? stevejgordon.co.uk/…
Mike B

Update1생성자에서 일을 주입 같은 다른 문제로 이동이 구조에있는 객체를 작업에서 매우 다르다

답변:


245

Func이 상황에서 자신을 발견했을 때 간단한 해결 방법을 사용했습니다 .

먼저 공유 위임을 선언하십시오.

public delegate IService ServiceResolver(string key);

그런 다음 Startup.cs여러 구체적인 등록 및 해당 유형의 수동 매핑을 설정하십시오.

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case "A":
            return serviceProvider.GetService<ServiceA>();
        case "B":
            return serviceProvider.GetService<ServiceB>();
        case "C":
            return serviceProvider.GetService<ServiceC>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});

DI에 등록 된 모든 클래스에서 사용하십시오.

public class Consumer
{
    private readonly IService _aService;

    public Consumer(ServiceResolver serviceAccessor)
    {
        _aService = serviceAccessor("A");
    }

    public void UseServiceA()
    {
        _aService.DoTheThing();
    }
}

이 예에서 해결의 열쇠는 단순성을 위해 그리고 특히 OP 가이 경우를 요구했기 때문에 문자열입니다.

그러나 일반적으로 코드를 썩는 거대한 n- 케이스 스위치를 원하지 않기 때문에 모든 사용자 지정 해상도 유형을 키로 사용할 수 있습니다. 앱의 규모에 따라 다릅니다.


1
@MatthewStevenMonkan은 내 대답을 예제로 업데이트했습니다
.

2
이와 같은 팩토리 패턴을 사용하는 것이 가장 좋습니다. 공유해 주셔서 감사합니다!
Sergey Akopov

2
+1 매우 깔끔하고 깨끗합니다. 다른 di-container를 사용할 때 의존성을 해결해야 할 때마다 패키지를 포함해야하기 때문입니다. AutoFac의 ILifetimeScope.
Anupam Singh

1
@AnupamSingh 내 의견으로는 .NET Core에서 실행되는 대부분의 중소형 응용 프로그램에는 DI 프레임 워크가 필요하지 않으며 복잡성과 원치 않는 종속성 만 추가하면 내장 DI의 아름다움과 단순성이 충분하며 충분할 수 있습니다. 또한 쉽게 확장 할 수 있습니다.
Miguel A. Arilla

7
다운 투표 설명-매우 흥미롭지 만 현재 몇 년 전에 누군가 (MS DI 혁명 이전)했던이 Func 마술을 모두 제거하기 위해 대규모 코드베이스를 리팩토링하고 있습니다.이 문제는 속성에 대한 일치 성 복잡성을 크게 증가시킵니다. 회선 아래로 복잡한 DI 해상도가 발생할 수 있습니다. 예를 들어 Func와 관련하여 1.6k 줄 이상의 코드가있는 Windows 서비스 핸들러에서 작업했으며 DI를 권장하는 방법으로 0.2k 줄로 줄였습니다. 코드의 OK-Lines는 아무 의미가 없습니다. 이제 쉽게 읽고 재사용 할 수 있다는 점을 제외하고 ...
Piotr Kula

79

또 다른 옵션은 확장 방법을 사용하는 것 GetServices입니다.Microsoft.Extensions.DependencyInjection .

서비스를 다음과 같이 등록하십시오 :

services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();

그런 다음 약간의 Linq로 해결하십시오.

var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));

또는

var serviceZ = services.First(o => o.Name.Equals("Z"));

(그것을 가정 IService "Name"이라는 문자열 속성이 )

가지고 있어야합니다 using Microsoft.Extensions.DependencyInjection;

최신 정보

AspNet 2.1 소스 : GetServices


6
확실하지 않지만 결정적이지는 않습니다. 오늘 얻는 결과는 내일 바뀔 수 있습니다. 좋은 습관이 아닌 것 같습니다.
rnrneverdies

4
GetServices에 대한 링크로 찬성하여 요청하여 종속 서비스에 대한 서비스 목록을 요청할 수 있음을 보여주었습니다.IEnumerable<IService>
johnny 5

20
serviceProvider.GetServices <IService> ()는 각 ServiceA, ServiceB 및 ServiceC를 인스턴스화합니다. 실제로 필요한 서비스 하나의 생성자 만 호출하려고합니다. 구현이 경량이 아니거나 많은 IService 구현이있는 경우 (예 : 각 모델에 대해 자동 생성 된 IRepository 구현이있는 경우) 큰 문제입니다.
Uros

6
@Uros에 동의합니다. 이것은 좋은 해결책이 아닙니다. 10 개의 IService 구현을 등록하고 실제로 필요한 인스턴스가 마지막 인스턴스 인 경우 어떻게되는지 상상해보십시오. 이 경우 실제로는 9 개의 인스턴스가 DI에 의해 생성되며 결코 사용되지 않습니다.
thomai

4
나쁜 생각 : 여러 개의 사용하지 않는 인스턴스, 서비스 로케이터 안티 패턴 및 실제 구현에 직접 연결 (typeof <ServiceA>).
Rico Suter

20

에서 지원하지 않습니다 Microsoft.Extensions.DependencyInjection.

그러나 StructureMap See it 's Home pageGitHub Project 와 같은 다른 의존성 주입 메커니즘을 플러그인 할 수 있습니다 .

전혀 어렵지 않습니다.

  1. 에서 StructureMap에 종속성을 추가하십시오 project.json.

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. 내부의 ASP.NET 파이프 라인에 삽입하고 ConfigureServices클래스를 등록하십시오 (문서 참조).

    public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
    {
        // Add framework services.
        services.AddMvc();
        services.AddWhatever();
    
        //using StructureMap;
        var container = new Container();
        container.Configure(config =>
        {
            // Register stuff in container, using the StructureMap APIs...
            config.For<IPet>().Add(new Cat("CatA")).Named("A");
            config.For<IPet>().Add(new Cat("CatB")).Named("B");
            config.For<IPet>().Use("A"); // Optionally set a default
            config.Populate(services);
        });
    
        return container.GetInstance<IServiceProvider>();
    }
  3. 그런 다음 명명 된 인스턴스를 얻으려면 IContainer

    public class HomeController : Controller
    {
        public HomeController(IContainer injectedContainer)
        {
            var myPet = injectedContainer.GetInstance<IPet>("B");
            string name = myPet.Name; // Returns "CatB"

그게 다야.

예제를 빌드하려면

    public interface IPet
    {
        string Name { get; set; }
    }

    public class Cat : IPet
    {
        public Cat(string name)
        {
            Name = name;
        }

        public string Name {get; set; }
    }

이 방법을 시도했지만 빌드 계획에 IContainer가 없기 때문에 컨트롤러에서 런타임 오류가 발생합니다. IContainer를 자동 주사하기 위해 필요한 사항이 있습니까?
mohrtan

BTW, StructureMap.Micorosoft.DependencyInjection 1.3.0을 사용하고 있습니다.
mohrtan

ConfigureServices에서 새 컨테이너를 반환합니까?
Gerardo Grignoli

위의 2 단계에 표시된대로 새 컨테이너의 IServiceProviderInstance를 반환합니다. 나는 그것을 내 유형에 대해서만 정확히 변경했습니다. 이것은 좋은 솔루션이며 완벽하게 작동합니다. 유일한 단점은 주입 된 컨테이너를 사용할 수 없으며 정적 컨테이너를 사용하고 싶지 않다는 것입니다.
mohrtan

1
GerardoGrignoli 덕분에 저에게 효과적입니다. @mohrtan 샘플 코드는 여전히 여기에 있습니다. github.com/Yawarmurtaza/AspNetCoreStructureMap
Murtaza


13

나는 똑같은 문제에 직면하여 어떻게 해결했는지, 왜 그런지 공유하고 싶습니다.

언급했듯이 두 가지 문제가 있습니다. 첫번째:

Asp.Net Core에서 이러한 서비스를 어떻게 등록하고 일부 키를 기반으로 런타임에 해결합니까?

어떤 옵션이 있습니까? 사람들은 두 가지를 제안합니다.

  • 사용자 정의 팩토리를 사용하십시오 (예 _myFactory.GetServiceByKey(key):)

  • 다른 DI 엔진을 사용하여 (같은 _unityContainer.Resolve<IService>(key))

여기에서 팩토리 패턴이 유일한 옵션입니까?

실제로 각 IoC 컨테이너는 팩토리이기 때문에 두 옵션 모두 팩토리입니다 (높은 구성 가능하지만 복잡함). 그리고 다른 옵션도 팩토리 패턴의 변형 인 것 같습니다.

그렇다면 어떤 옵션이 더 낫습니까? 여기에 사용자 정의 팩토리 사용을 제안한 @Sock에 동의하며 그 이유입니다.

첫째, 나는 항상 새로운 의존성이 실제로 필요하지 않을 때 추가하지 않도록 노력합니다. 그래서 나는이 점에서 당신에 동의합니다. 또한 두 개의 DI 프레임 워크를 사용하는 것은 커스텀 팩토리 추상화를 만드는 것보다 나쁩니다. 두 번째 경우에는 Unity와 같은 새로운 패키지 종속성을 추가해야하지만 새로운 팩토리 인터페이스에 따라 덜 악의적입니다. ASP.NET Core DI의 주요 아이디어는 단순함입니다. KISS 원칙에 따라 최소 기능 세트를 유지합니다 . 추가 기능이 필요한 경우 DIY 또는 해당 Plungin을 사용하십시오. 원하는 기능을 구현 를 (폐쇄 원칙).

둘째, 단일 서비스에 대해 많은 명명 된 종속성을 주입해야하는 경우가 종종 있습니다. Unity의 경우 생성자를 사용하여 생성자 매개 변수의 이름을 지정해야 할 수 있습니다 InjectionConstructor. 이 등록은 반사와 일부 스마트 로직 을 하여 생성자의 인수를 추측합니다. 등록이 생성자 인수와 일치하지 않으면 런타임 오류가 발생할 수도 있습니다. 반면, 자체 팩토리를 사용하는 경우 생성자 매개 변수를 제공하는 방법을 완전히 제어 할 수 있습니다. 더 읽기 쉽고 컴파일 타임에 해결됩니다. 다시 키스 원리 .

두 번째 문제 :

_serviceProvider.GetService ()는 어떻게 적절한 연결 문자열을 주입 할 수 있습니까?

먼저, 새로운 것과 같은 IOptions(따라서 package Microsoft.Extensions.Options.ConfigurationExtensions) 에 의존 하는 것은 좋지 않다는 것에 동의합니다 . IOptions혜택에 대한 의견이 다른 곳에 대해 논의한 적이 있습니다 . 다시, 나는 실제로 필요하지 않을 때 새로운 의존성을 추가하지 않도록 노력합니다. 정말 필요한가요? 아니에요 그렇지 않으면 각 구현은 해당 구현에서 오는 명확한 요구없이 그것에 의존해야합니다 (나에게도 ISP에 위배되는 것처럼 보입니다. 이것은 공장에 따라 다르지만이 경우에는 피할 있습니다.

ASP.NET Core DI는 이러한 목적을 위해 매우 훌륭한 과부하를 제공합니다.

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));

안녕하세요, 어리석은 질문에 대해 죄송하지만 Microsoft.Extensions.DependencyInjection을 처음 사용합니다. "public interface IServiceA : IService"와 "public class ServiceA : IServiceA"와 같이 Iservice를 확장하는 3 개의 인터페이스를 만드는 것으로 생각하십니까? ... 좋은 연습 옵션이 될 수 있습니까?
Emiliano Magliocca 17

1
@ emiliano-magliocca 일반적으로, 사용하지 않는 인터페이스 (ISP)에 의존해서는 안됩니다 IServiceA. 메소드 IService만 사용하므로 의존성 IService만 있어야 합니다.
neleus

1
@ cagatay-kalan OP의 질문이있는 경우 ASP.NET Core DI로 쉽게 목표를 달성 할 수 있습니다. 다른 DI 프레임 워크가 필요하지 않습니다.
neleus

1
@EmilianoMagliocca services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));첫 번째 클래스와 services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));두 번째 클래스의 경우 다음과 같이 쉽게 해결할 수 있습니다 .
neleus

1
내 예제에서 @EmilianoMagliocca는 'MyFirstClass'와 'MySecondClass'모두 Escpos와 Usbpos가 구현하는 인터페이스 유형의 동일한 ctor 매개 변수를 갖습니다. 따라서 위의 코드는 IoC 컨테이너에만 'MyFirstClass'및 'MySecondClass'를 설치하는 방법을 지시합니다. 더 이상 없습니다. 또한 다른 인터페이스를 'MyFirstClass'및 'MySecondClass'에 매핑해야 할 수도 있습니다. 그것은 당신의 필요에 달려 있으며 내 예제에서는 다루지 않았습니다.
neleus

13

난 그냥 IEnumerable을 주입

Startup.cs의 서비스 구성

Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                {
                    services.AddScoped(typeof(IService), t);
                });

서비스 폴더

public interface IService
{
    string Name { get; set; }
}

public class ServiceA : IService
{
    public string Name { get { return "A"; } }
}

public class ServiceB : IService
{    
    public string Name { get { return "B"; } }
}

public class ServiceC : IService
{    
    public string Name { get { return "C"; } }
}

MyController.cs

public class MyController
{
    private readonly IEnumerable<IService> _services;
    public MyController(IEnumerable<IService> services)
    {
        _services = services;
    }
    public void DoSomething()
    {
        var service = _services.Where(s => s.Name == "A").Single();
    }
...
}

Extensions.cs

    public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
    {
        return assembly.GetTypesAssignableFrom(typeof(T));
    }
    public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
    {
        List<Type> ret = new List<Type>();
        foreach (var type in assembly.DefinedTypes)
        {
            if (compareType.IsAssignableFrom(type) && compareType != type)
            {
                ret.Add(type);
            }
        }
        return ret;
    }

Controller의 DoSomething () 메소드에서 typeof를 사용하여 원하는 서비스를 분석 할 수 있습니다. var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen

나는 문자 그대로 모든 것을 시도했지만 이것이 나를 위해 일한 유일한 솔루션입니다. 감사!
Skatz1990

@ Skatz1990 다른 게시물에서 아래에서 만든 솔루션을 사용해보십시오. 더 깨끗하고 사용하기 쉽다고 생각합니다.
T 브라운

12

여기에있는 대부분의 답변은 단일 책임 원칙 (서비스 클래스가 종속성 자체를 해결해서는 안 됨)을 위반하거나 서비스 로케이터 안티 패턴을 사용합니다.

이러한 문제를 피하는 또 다른 옵션은 다음과 같습니다.

  • 인터페이스에서 추가 일반 유형 매개 변수를 사용하거나 일반 인터페이스가 아닌 인터페이스를 구현하는 새 인터페이스
  • 마커 유형을 추가하기 위해 어댑터 / 인터셉터 클래스를 구현 한 다음
  • 제네릭 형식을 "이름"으로 사용

자세한 내용과 함께 기사를 작성했습니다. .NET의 Dependency Injection : 누락 된 명명 된 등록 문제를 해결하는 방법


수락 된 바이올렛은 단일 책임 원칙에 어떻게 대답합니까?
LP13

stackoverflow.com/a/52066039/876814의 의견을 참조 하고 허용 된 답변에서 서비스가 게으르게 해결됩니다. 즉, 런타임에 실패하고 컨테이너 빌드 후 시작시이를 정적으로 확인할 수있는 방법이 없다는 것을 알고 있습니다 ( 의견의 답변). SRP는 비즈니스 로직뿐만 아니라 종속성 해결에도 책임이 있기 때문에 SRP
Rico Suter

@RicoSuter 저는 블로그의 솔루션을 정말 좋아하지만 Startup 클래스 내 DI에 의해 혼동됩니다. 특히, 해당 서명이있는 생성자를 볼 수 없으므로 MessagePublisher ( "MyOrderCreatedQueue") 줄을 이해하지 못합니다. services.AddSingleton <IMessagePublisher <OrderCreatedMessage >> (new MessagePublisher <OrderCreatedMessage> (new MessagePublisher ( "MyOrderCreatedQueue")));
Lee Z

감사합니다. 기사를 업데이트하고
Rico Suter

7

이 파티에 조금 늦었지만 여기 내 해결책이 있습니다 : ...

일반 처리기 인 경우 Startup.cs 또는 Program.cs ...

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();

T 인터페이스 설정의 IMyInterface

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}

T의 IMyInterface의 구체적인 구현

public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
    public async Task Consume();
}

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}

이 방법으로 문제가 발생하면 누군가 이것이 왜 이것이 잘못된 방법인지 지적 할 수 있기를 바랍니다.


2
IMyInterface<CustomerSavedConsumer>하고 IMyInterface<ManagerSavedConsumer>있는 다른 서비스 유형 -이 전혀 작전의 질문에 대답하지 않습니다.
Richard Hauer

2
OP는 Asp.net 코어에서 동일한 인터페이스의 여러 구현을 등록하는 방법을 원했습니다. 내가 이것을하지 않으면 어떻게 (정확하게) 설명하십시오.
그레이

1
정확하지만이 패턴을 사용하면 op가 원하는 효과를 얻을 수 있습니다. 적어도 내가 이것을하려고했을 때 나는이 게시물을 우연히 발견했으며 내 솔루션은 내 상황에 가장 적합했습니다.
그레이

1
문제는 MS DI를 사용하여 단일 인터페이스에 여러 구현을 등록하면 컨테이너가 구현을 다른 구현과 구별 할 수 없다는 것입니다. 다른 DI에서는 컨테이너가 선택할 것을 알 수 있도록 키를 지정할 수 있습니다. MS에서는 대리자를 사용하여 수동으로 선택합니다. 인터페이스가 다르기 때문에 솔루션이이 시나리오를 해결하지 않으므로 컨테이너는 올바른 구현을 선택하는 데 아무런 문제가 없습니다. 샘플은 분명히 작동하지만 명시된대로 문제에 대한 해결책은 아닙니다.
Richard Hauer

3
@Gray 게시물에 나쁜 소식이 있지만이 솔루션을 제시해 주셔서 감사합니다. .net 코어 DI의 한계를 극복 할 수있는 또 다른 옵션을 독자에게 제공합니다. OP 질문에 직접 대답하지는 못하지만 완벽한 대안 솔루션을 제공합니다.
Neil Watson

5

분명히, 당신은 서비스 인터페이스의 IEnumerable을 주입 할 수 있습니다! 그런 다음 LINQ를 사용하여 원하는 인스턴스를 찾으십시오.

저의 예는 AWS SNS 서비스에 대한 것이지만 주입 된 서비스에 대해서도 실제로 동일한 작업을 수행 할 수 있습니다.

스타트 업

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);

SNSConfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

SNS 공장

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}

이제 사용자 정의 서비스 또는 컨트롤러에서 원하는 지역에 대한 SNS 서비스를 얻을 수 있습니다

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}

5

팩토리 접근 방식은 확실합니다. 또 다른 방법은 상속을 사용하여 IService에서 상속하는 개별 인터페이스를 만들고, IService 구현에서 상속 된 인터페이스를 구현하고, 기본이 아닌 상속 된 인터페이스를 등록하는 것입니다. 상속 계층 또는 팩토리 추가가 "올바른"패턴인지 여부는 모두 말하는 사람에 따라 다릅니다. 같은 응용 프로그램에서 여러 데이터베이스 공급자를 처리 할 때이 패턴을 사용해야하는 경우가 종종 있습니다.IRepository<T>데이터 액세스의 기초 합니다.

인터페이스 및 구현 예 :

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}

컨테이너:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();

5

괴롭힘.
나는 여기 사람들이 바퀴를 재발 명한다고 생각합니다. 그리고 그렇게 말할 수
없다면 ... 키로 구성 요소를 등록하려면 사전을 사용하십시오.

System.Collections.Generic.Dictionary<string, IConnectionFactory> dict = 
    new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
        System.StringComparer.OrdinalIgnoreCase);

dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
dict.Add("TestDB", new ConnectionFactory("connectionString3"));
dict.Add("Analytics", new ConnectionFactory("connectionString4"));
dict.Add("LogDB", new ConnectionFactory("connectionString5"));

그런 다음 서비스 콜렉션에 사전을 등록하십시오.

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);

그런 다음 사전 및 키의 접근을 얻을 꺼리는 경우에, 당신은 서비스 컬렉션에 추가 키 조회-방법을 추가하여 사전을 숨길 수 있습니다 :
(위임의 사용은 / 폐쇄로 전향 메인테이너에게 기회를 주어야한다 진행 상황 이해-화살표 표기법은 약간 암호입니다)

services.AddTransient<Func<string, IConnectionFactory>>(
    delegate (IServiceProvider sp)
    {
        return
            delegate (string key)
            {
                System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
 <System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);

                if (dbs.ContainsKey(key))
                    return dbs[key];

                throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
            };
    });

이제 두 가지 중 하나를 사용하여 유형에 액세스 할 수 있습니다

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection

또는

System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection

보시다시피, 첫 번째 것은 완전히 불필요한 것입니다. 클로저와 AddTransient를 요구하지 않고 사전을 사용하여 정확히 그렇게 할 수 있기 때문입니다 (VB를 사용하는 경우 중괄호도 다르지 않습니다).

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection

(더 간단할수록 좋습니다-확장 방법으로 사용하고 싶을 수도 있습니다)

물론 사전이 마음에 들지 않으면 인터페이스에 속성 Name(또는 무엇이든)을 입히고 키로 찾아 볼 수도 있습니다.

services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));



// /programming/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core
services.AddTransient<Func<string, IConnectionFactory>>(
    delegate(IServiceProvider sp)
    {
        return
            delegate(string key)
            {
                System.Collections.Generic.IEnumerable<IConnectionFactory> svs = 
                    sp.GetServices<IConnectionFactory>();

                foreach (IConnectionFactory thisService in svs)
                {
                    if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
                        return thisService;
                }

                return null;
            };
    });

그러나 속성을 수용하기 위해 인터페이스를 변경해야하며 많은 요소를 반복하는 것은 연관 배열 조회 (사전)보다 훨씬 느려 야합니다.
그래도 dicionary없이 수행 할 수 있다는 것을 아는 것이 좋습니다.

이들은 단지 내 $ 0.05


서비스가 IDispose구현 된 경우 누가 서비스를 처리해야합니까? 다음과 같이 사전을 등록 Singleton
하셨습니다.

@ LP13 : 델리게이트를 사용하여 사전을 값으로 등록 한 다음 itransient에 등록하고 새 인스턴스를 만들 수 있습니다. GetRequiredService <T> () [ "logDB"] ()
Stefan Steiger

5

위의 게시물 이후 일반 팩토리 클래스로 옮겼습니다.

용법

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }

이행

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}

신장

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static IServiceCollection AddFactory<I, P>(this IServiceCollection services, Action<FactoryBuilder<I, P>> builder)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            var factoryBuilder = new FactoryBuilder<I, P>(services);
            builder(factoryBuilder);
            return services;
        }
    }
}

.AddFactory () 메소드 확장을 제공 할 수 있습니까?
개발자

죄송합니다. 이걸 보았습니다 ... 추가
T Brown

3

@Miguel A. Arilla가 분명히 지적하고 그에게 투표 한 것처럼 보이지만 유용한 솔루션 위에 깔끔하게 보이지만 더 많은 작업이 필요한 다른 솔루션을 만들었습니다.

위의 솔루션에 따라 다릅니다. 그래서 기본적으로 비슷한 것을 만들고 인터페이스 Func<string, IService>>라고 부르고 IServiceAccessor다음과 같이 확장 기능을 추가해야했습니다 IServiceCollection.

public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>(
            this IServiceCollection services,
            string instanceName
        )
            where TService : class
            where TImplementation : class, TService
            where TServiceAccessor : class, IServiceAccessor<TService>
        {
            services.AddSingleton<TService, TImplementation>();
            services.AddSingleton<TServiceAccessor>();
            var provider = services.BuildServiceProvider();
            var implementationInstance = provider.GetServices<TService>().Last();
            var accessor = provider.GetServices<TServiceAccessor>().First();

            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }

            accessor.SetService(implementationInstance, instanceName);
            services.AddSingleton<TServiceAccessor>(prvd => accessor);
            return services;
        }

서비스 접근자는 다음과 같습니다.

 public interface IServiceAccessor<TService>
    {
         void Register(TService service,string name);
         TService Resolve(string name);

    }

결과적으로 다른 컨테이너와 마찬가지로 이름이나 명명 된 인스턴스로 서비스를 등록 할 수 있습니다.

    services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
    services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");

지금까지는 충분하지만 작업을 완료하려면 동일한 접근 방식에 따라 모든 유형의 등록을 처리 할 수있는 확장 방법을 추가하는 것이 좋습니다.

stackoverflow에 대한 또 다른 게시물이 있었지만 포스터 에서이 기능이 지원되지 않는 이유와 해결 방법을 자세하게 설명했습니다. 기본적으로 @Miguel이 말한 것과 유사합니다. 명명 된 인스턴스가 실제로 필요한 상황이 있다고 생각하기 때문에 각 요점에 동의하지 않더라도 멋진 게시물이었습니다. 다시 찾은 링크를 여기에 게시하겠습니다.

실제로 해당 선택기 또는 접근자를 전달하지 않아도됩니다.

내 프로젝트에서 다음 코드를 사용하고 있으며 지금까지 잘 작동했습니다.

 /// <summary>
    /// Adds the singleton.
    /// </summary>
    /// <typeparam name="TService">The type of the t service.</typeparam>
    /// <typeparam name="TImplementation">The type of the t implementation.</typeparam>
    /// <param name="services">The services.</param>
    /// <param name="instanceName">Name of the instance.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection AddSingleton<TService, TImplementation>(
        this IServiceCollection services,
        string instanceName
    )
        where TService : class
        where TImplementation : class, TService
    {
        var provider = services.BuildServiceProvider();
        var implementationInstance = provider.GetServices<TService>().LastOrDefault();
        if (implementationInstance.IsNull())
        {
            services.AddSingleton<TService, TImplementation>();
            provider = services.BuildServiceProvider();
            implementationInstance = provider.GetServices<TService>().Single();
        }
        return services.RegisterInternal(instanceName, provider, implementationInstance);
    }

    private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services,
        string instanceName, ServiceProvider provider, TService implementationInstance)
        where TService : class
    {
        var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault();
        if (accessor.IsNull())
        {
            services.AddSingleton<ServiceAccessor<TService>>();
            provider = services.BuildServiceProvider();
            accessor = provider.GetServices<ServiceAccessor<TService>>().Single();
        }
        else
        {
            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }
        }
        accessor.Register(implementationInstance, instanceName);
        services.AddSingleton<TService>(prvd => implementationInstance);
        services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor);
        return services;
    }

    //
    // Summary:
    //     Adds a singleton service of the type specified in TService with an instance specified
    //     in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
    //
    // Parameters:
    //   services:
    //     The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service
    //     to.
    //   implementationInstance:
    //     The instance of the service.
    //   instanceName:
    //     The name of the instance.
    //
    // Returns:
    //     A reference to this instance after the operation has completed.
    public static IServiceCollection AddSingleton<TService>(
        this IServiceCollection services,
        TService implementationInstance,
        string instanceName) where TService : class
    {
        var provider = services.BuildServiceProvider();
        return RegisterInternal(services, instanceName, provider, implementationInstance);
    }

    /// <summary>
    /// Registers an interface for a class
    /// </summary>
    /// <typeparam name="TInterface">The type of the t interface.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection As<TInterface>(this IServiceCollection services)
         where TInterface : class
    {
        var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
        if (descriptor.IsNotNull())
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
            services?.AddSingleton(implementationInstance);
        }
        return services;
    }

1
이것은 서비스 접근 자에서 유형의 등록이 손실되는 문제를 해결하는 데 도움이되었습니다. 비결은 서비스 접근 자에 대한 모든 바인딩을 제거한 다음 다시 추가하는 것입니다!
Umar Farooq Khawaja 2018 년

3

멋진 기능을 구현하는 라이브러리를 만들었습니다. 코드는 GitHub에서 찾을 수 있습니다 : https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet : https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

사용법은 간단합니다.

  1. Dazinator.Extensions.DependencyInjection 너겟 패키지를 프로젝트에 추가하십시오.
  2. 명명 된 서비스 등록을 추가하십시오.
    var services = new ServiceCollection();
    services.AddNamed<AnimalService>(names =>
    {
        names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
        names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
        names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
        names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).

        names.AddTransient("E"); // new AnimalService() every time..
        names.AddTransient<LionService>("F"); // new LionService() every time..

        names.AddScoped("G");  // scoped AnimalService
        names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.

    });

위의 예에서 각 명명 된 등록에 대해 수명 또는 싱글 톤, 범위 또는 일시적을 지정하고 있음에 유의하십시오.

서비스가이 패키지에 의존하지 않는 것에 익숙한 지 여부에 따라 두 가지 방법 중 하나로 서비스를 해결할 수 있습니다.

public MyController(Func<string, AnimalService> namedServices)
{
   AnimalService serviceA = namedServices("A");
   AnimalService serviceB = namedServices("B"); // BearService derives from AnimalService
}

또는

public MyController(NamedServiceResolver<AnimalService> namedServices)
{
   AnimalService serviceA = namedServices["A"];
   AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
}

이 라이브러리를 Microsoft.Extensions.DependencyInjection과 잘 작동하도록 특별히 설계했습니다. 예를 들면 다음과 같습니다.

  1. 당신이라는 이름의 서비스를 등록하면 등록하는 것이 어떤 유형의 매개 변수와 생성자를 가질 수 있습니다 - 그들은 그 같은 방식으로, DI를 통해 만족 될 것이다 AddTransient<>, AddScoped<>AddSingleton<>방법은 일반적으로 작동합니다.

  2. 일시적이고 범위가 지정된 명명 된 서비스의 경우 레지스트리는 ObjectFactory필요한 경우 유형의 새 인스턴스를 매우 빠르게 활성화 할 수 있도록 빌드 합니다. 이것은 다른 접근 방식보다 훨씬 빠르며 Microsoft.Extensions.DependencyInjection이 수행하는 방식과 일치합니다.


2

가치있는 것에 대한 내 솔루션 ... 위의 솔루션 중 어느 것이 마음에 들지 않는다고 말할 수없는 Castle Windsor로 전환하는 것을 고려했습니다. 죄송합니다!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

다양한 구현을 만듭니다

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }

기재

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()

생성자와 인스턴스 사용법 ...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }

1

@rnrneverdies의 솔루션 확장. ToString () 대신 다음 옵션을 사용할 수도 있습니다. 1) 공통 속성 구현 2) @Craig Brunetti가 제안한 서비스 서비스.

public interface IService { }
public class ServiceA : IService
{
    public override string ToString()
    {
        return "A";
    }
}

public class ServiceB : IService
{
    public override string ToString()
    {
        return "B";
    }

}

/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
    public static T GetService<T>(this IServiceProvider provider, string identifier)
    {
        var services = provider.GetServices<T>();
        var service = services.FirstOrDefault(o => o.ToString() == identifier);
        return service;
    }
}

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

    services.AddSingleton<IService, ServiceA>();
    services.AddSingleton<IService, ServiceB>();
    services.AddSingleton<IService, ServiceC>();

    var sp = services.BuildServiceProvider();
    var a = sp.GetService<IService>("A"); //returns instance of ServiceA
    var b = sp.GetService<IService>("B"); //returns instance of ServiceB

    //Remaining configurations....
}

1

여기에 대한 답변과 다른 곳의 기사를 읽은 후 문자열없이 작동하도록 할 수있었습니다. 동일한 인터페이스를 여러 번 구현하면 DI가이를 컬렉션에 추가하므로을 사용하여 컬렉션에서 원하는 버전을 검색 할 수 있습니다 typeof.

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IService, ServiceA);
    services.AddScoped(IService, ServiceB);
    services.AddScoped(IService, ServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IService> _myServices;

    public Consumer(IEnumerable<IService> myServices)
    {
        _myServices = myServices;
    }

    public UseServiceA()
    {
        var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
        serviceA.DoTheThing();
    }

    public UseServiceB()
    {
        var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
        serviceB.DoTheThing();
    }

    public UseServiceC()
    {
        var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
        serviceC.DoTheThing();
    }
}

IoC의 목적을 패배시킵니다. 다음과 같이 쓸 수도 있습니다.var serviceA = new ServiceA();
James Curran

2
ServiceA에 종속성이 있거나 클래스를 단위 테스트하려는 경우 @JamesCurran이 아닙니다.
Jorn.Beyers

0

기본 제공 구현이 제공하지는 않지만 여기에 명명 된 인스턴스를 등록한 다음 INamedServiceFactory를 코드에 삽입하고 이름별로 인스턴스를 가져 오는 샘플 프로젝트가 있습니다. 여기의 다른 facory 솔루션과 달리 동일한 구현 이지만 여러 구성으로 구성된 여러 인스턴스를 등록 할 수 있습니다.

https://github.com/macsux/DotNetDINamedInstances


0

서비스 서비스는 어떻습니까?

.Name 속성이있는 INamedService 인터페이스가있는 경우 .GetService (string name)에 대한 IServiceCollection 확장을 작성할 수 있습니다. 여기서 확장은 해당 문자열 매개 변수를 사용하고 자체적으로 .GetServices ()를 수행합니다. 인스턴스에서 INamedService.Name이 주어진 이름과 일치하는 인스턴스를 찾으십시오.

이처럼 :

public interface INamedService
{
    string Name { get; }
}

public static T GetService<T>(this IServiceProvider provider, string serviceName)
    where T : INamedService
{
    var candidates = provider.GetServices<T>();
    return candidates.FirstOrDefault(s => s.Name == serviceName);
}

따라서 IMyService는 INamedService를 구현해야하지만 원하는 키 기반 해상도를 얻을 수 있습니까?

공평하게 말하면,이 INamedService 인터페이스조차도 추악한 것처럼 보이지만 더 나아가고 더 우아하게 만들고 싶다면 구현 / 클래스의 [NamedServiceAttribute ( "A")]를이 코드에서 찾을 수 있습니다 확장 기능도 작동합니다. 더 공정하게 말하자면, Reflection은 느리기 때문에 최적화가 필요할 수 있지만 솔직히 DI 엔진이 도와야 할 것입니다. 속도와 단순성은 각각 TCO에 큰 기여를합니다.

"명명 된 서비스 찾기"는 재사용 가능한 개념이며 팩토리 클래스는 솔루션으로 확장 할 수 없기 때문에 명시 적 팩토리가 필요하지 않습니다. 그리고 Func <>는 괜찮은 것처럼 보이지만 스위치 블록은 너무 희미합니다 . 다시 팩토리를 작성할 때마다 Funcs를 작성하게됩니다. 적은 코드로 간단하고 재사용 가능한 것으로 시작하십시오. 그렇지 않으면 복잡하지 않습니다.


2
이것을 서비스 로케이터 패턴이라고하며 일반적으로 꼭 필요한 경우가 아니면 최선의 방법은 아닙니다
Joe Phillips

@JoePhillips 왜 좋은 해결책이 아닌지에 대한 정보가 있습니까? 나는 그것의 우아함을 좋아합니다. 내가 생각할 수있는 유일한 단점은 하나를 얻을 때마다 모든 인스턴스를 만드는 것입니다.
Peter

2
@Peter 주된 이유는 작업하기가 매우 어렵 기 때문입니다. serviceLocator 객체를 클래스로 전달하는 경우 클래스가 사용하는 종속성이 마술 "신"객체에서 가져 오기 때문에 클래스가 어떤 종속성을 사용하는지는 분명하지 않습니다. 변경하려는 유형의 참조를 찾아야한다고 상상해보십시오. 이 기능은 기본적으로 서비스 로케이터 객체를 통해 모든 것을 가져올 때 사라집니다. 생성자 주입이 훨씬 명확하고 신뢰할 수 있음
Joe Phillips

난 몰라 구성 요소가 종속성을 활용하는 방법을 추적하는 데 관심이 있다면 그에 대한 단위 테스트가 필요합니다 ... 각 종속성을 나타낼뿐만 아니라 이해하는 데 도움이되는 테스트 각 의존성이 필요한 방법. 생성자를 읽으면 어떻게 다른 점을 알 수 있습니까?!?
Craig Brunetti

0

나는 같은 문제에 부딪 쳤고 명명 된 서비스를 허용하기 위해 간단한 확장으로 작업했습니다. 여기에서 찾을 수 있습니다.

다음과 같이 원하는만큼 서비스를 추가 할 수 있습니다.

 var serviceCollection = new ServiceCollection();
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);

 var serviceProvider = serviceCollection.BuildServiceProvider();

 var myServiceA = serviceProvider.GetService<IMyService>("A");
 var myServiceB = serviceProvider.GetService<IMyService>("B");

라이브러리를 사용하면 다음과 같이 "공장 패턴"을 쉽게 구현할 수 있습니다.

    [Test]
    public void FactoryPatternTest()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();

        var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
        Assert.NotNull(myServiceA);
        Assert.IsInstanceOf<MyServiceA>(myServiceA);

        var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
        Assert.NotNull(myServiceB);
        Assert.IsInstanceOf<MyServiceB>(myServiceB);
    }

    public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
    {
    }

    public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
    {
        public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
        : base(serviceProvider)
        {
        }
    }

    public enum MyEnum
    {
        A = 1,
        B = 2
    }

그것이 도움이되기를 바랍니다.


0

IServiceCollection사용 된 WithName확장에 대해 내 확장을 만들었습니다 .

public static IServiceCollection AddScopedWithName<TService, TImplementation>(this IServiceCollection services, string serviceName)
        where TService : class
        where TImplementation : class, TService
    {
        Type serviceType = typeof(TService);
        Type implementationServiceType = typeof(TImplementation);
        ServiceCollectionTypeMapper.Instance.AddDefinition(serviceType.Name, serviceName, implementationServiceType.AssemblyQualifiedName);
        services.AddScoped<TImplementation>();
        return services;
    }

ServiceCollectionTypeMapper지도하는 싱글 인스턴스 IService> NameOfService> Implementation우리가 해결할 수 있습니다 때 꼬마 필요하고 우리가 원하는 것을 선택하는 해결의 여러 서비스와는 다른 접근 방식이보다 인터페이스가 다른 이름으로 많은 구현을 가질 수있다,이 유형을 등록 할 수 있습니다.

 /// <summary>
/// Allows to set the service register mapping.
/// </summary>
public class ServiceCollectionTypeMapper
{
    private ServiceCollectionTypeMapper()
    {
        this.ServiceRegister = new Dictionary<string, Dictionary<string, string>>();
    }

    /// <summary>
    /// Gets the instance of mapper.
    /// </summary>
    public static ServiceCollectionTypeMapper Instance { get; } = new ServiceCollectionTypeMapper();

    private Dictionary<string, Dictionary<string, string>> ServiceRegister { get; set; }

    /// <summary>
    /// Adds new service definition.
    /// </summary>
    /// <param name="typeName">The name of the TService.</param>
    /// <param name="serviceName">The TImplementation name.</param>
    /// <param name="namespaceFullName">The TImplementation AssemblyQualifiedName.</param>
    public void AddDefinition(string typeName, string serviceName, string namespaceFullName)
    {
        if (this.ServiceRegister.TryGetValue(typeName, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out _))
            {
                throw new InvalidOperationException($"Exists an implementation with the same name [{serviceName}] to the type [{typeName}].");
            }
            else
            {
                services.Add(serviceName, namespaceFullName);
            }
        }
        else
        {
            Dictionary<string, string> serviceCollection = new Dictionary<string, string>
            {
                { serviceName, namespaceFullName },
            };
            this.ServiceRegister.Add(typeName, serviceCollection);
        }
    }

    /// <summary>
    /// Get AssemblyQualifiedName of implementation.
    /// </summary>
    /// <typeparam name="TService">The type of the service implementation.</typeparam>
    /// <param name="serviceName">The name of the service.</param>
    /// <returns>The AssemblyQualifiedName of the inplementation service.</returns>
    public string GetService<TService>(string serviceName)
    {
        Type serviceType = typeof(TService);

        if (this.ServiceRegister.TryGetValue(serviceType.Name, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out string serviceImplementation))
            {
                return serviceImplementation;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

새로운 서비스를 등록하려면 :

services.AddScopedWithName<IService, MyService>("Name");

서비스를 해결하려면 IServiceProvider이와 같은 확장 이 필요합니다 .

/// <summary>
    /// Gets the implementation of service by name.
    /// </summary>
    /// <typeparam name="T">The type of service.</typeparam>
    /// <param name="serviceProvider">The service provider.</param>
    /// <param name="serviceName">The service name.</param>
    /// <returns>The implementation of service.</returns>
    public static T GetService<T>(this IServiceProvider serviceProvider, string serviceName)
    {
        string fullnameImplementation = ServiceCollectionTypeMapper.Instance.GetService<T>(serviceName);
        if (fullnameImplementation == null)
        {
            throw new InvalidOperationException($"Unable to resolve service of type [{typeof(T)}] with name [{serviceName}]");
        }
        else
        {
            return (T)serviceProvider.GetService(Type.GetType(fullnameImplementation));
        }
    }

해결할 때 :

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

serviceProvider는 애플리케이션의 생성자 내에로 삽입 될 수 있음을 기억하십시오 IServiceProvider.

이게 도움이 되길 바란다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.