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 page 및 GitHub Project 와 같은 다른 의존성 주입 메커니즘을 플러그인 할 수 있습니다 .
전혀 어렵지 않습니다.
에서 StructureMap에 종속성을 추가하십시오
project.json."Structuremap.Microsoft.DependencyInjection" : "1.0.1",내부의 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>(); }그런 다음 명명 된 인스턴스를 얻으려면
IContainerpublic 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....
}
'Programming' 카테고리의 다른 글
| 간단히 말해서 스택 프레임의 개념을 설명하십시오 (0) | 2020.05.24 |
|---|---|
| AssertjUnit의 문자열에 포함 (0) | 2020.05.24 |
| 종속 DLL이 Visual Studio의 빌드 출력 폴더로 복사되지 않습니다 (0) | 2020.05.24 |
| 기존 JavaScript 라이브러리에서 .d.ts "typings"정의 파일을 어떻게 생성합니까? (0) | 2020.05.24 |
| ReactJS : 경고 : setState (…) : 기존 상태 전환 중에 업데이트 할 수 없습니다 (0) | 2020.05.24 |