Programming

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

procodes 2020. 5. 24. 21:52
반응형

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


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

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

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

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

구체적인 구현을 구별하는 데 일반적으로 사용되는 AddService 메서드 key또는 name매개 변수가 표시되지 않습니다 .

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


    public MyController:Controller
    {
       public void DoSomeThing(string key)
       { 
          // How do get service based on key
       }
    }

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

Update1 여기서는 여러 concreate 구현이있을 때 팩토리 패턴을 사용하여 서비스 인스턴스를 얻는 방법을 보여주는
기사를 보았습니다. 그러나 여전히 완전한 해결책은 아닙니다. 메소드를 호출 하면 생성자에 데이터를 주입 할 수 없습니다. 예를 들어이 예제를 고려하십시오_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 코어의 기본 DI에는 2>
키를 사용하여 인스턴스 등록
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에서 이러한 서비스를 어떻게 등록하고 일부 키를 기반으로 런타임에 해결합니까?

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

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

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

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

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

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

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

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

두 번째 문제 :

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

먼저, 나는 IOptions(그리고 패키지에 같은) 새로운 것들에 의존 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>())));

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

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


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

분명히, 당신은 서비스 인터페이스의 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();
    }

     .........
}

팩토리 접근 방식은 확실합니다. 또 다른 방법은 상속을 사용하여 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>();

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

가치있는 것에 대한 내 솔루션 ... 위의 솔루션 중 어느 것이 마음에 들지 않는다고 말할 수없는 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();
        .....
   }

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

일반 처리기 인 경우 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();
}

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


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

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



// https://stackoverflow.com/questions/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


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

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

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

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


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

https://github.com/macsux/DotNetDINamedInstances


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

.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를 자주 쓸 것입니다. 적은 코드로 간단하고 재사용 가능한 것으로 시작하십시오. 그렇지 않으면 복잡하지 않습니다.


@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....
}

참고 URL : https://stackoverflow.com/questions/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core

반응형