동일한 인터페이스에서 파생 된 서비스가 있습니다.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
일반적으로 다른 IoC 컨테이너를 Unity
사용하면 구체적인 구현을 구체적으로 등록 할 수 Key
있습니다.
ASP.NET Core에서 이러한 서비스를 등록하고 일부 키를 기반으로 런타임에 어떻게 해결합니까?
나는 구체적인 구현을 구별하기 위해 일반적으로 사용되는 또는 매개 변수 Add
를 사용하는 서비스 메소드를 보지 못했습니다 .key
name
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에는 두 가지가 빠져 있다고 생각합니다.
- 키를 사용하여 인스턴스를 등록하는 기능
- 등록하는 동안 정적 데이터를 생성자에 주입하는 기능
답변
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>(); }
-
그런 다음 명명 된 인스턴스를 얻으려면
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 : 누락 된 명명 된 등록 문제를 해결하는 방법