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

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

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. 등록하는 동안 정적 데이터를 생성자에 주입하는 기능


답변

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- 케이스 스위치를 원하지 않기 때문에 모든 사용자 지정 해상도 유형을 키로 사용할 수 있습니다. 앱의 규모에 따라 다릅니다.


답변

또 다른 옵션은 확장 방법을 사용하는 것 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


답변

에서 지원하지 않습니다 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; }
    }


답변

맞습니다. 기본 제공 ASP.NET Core 컨테이너에는 여러 서비스를 등록한 다음 특정 서비스를 검색한다는 개념이 없으므로 팩토리가이 경우 유일한 솔루션입니다.

또는 필요한 솔루션을 제공하는 Unity 또는 StructureMap과 같은 타사 컨테이너로 전환 할 수 있습니다 ( http://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- -default-services-container ).


답변

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

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

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


답변

난 그냥 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;
    }


답변

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

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

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

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