使用Windsor
我将回避关于这是否在此情况下有意义的问题,只尝试回答所提出的问题:
.NET Core的IoC容器对于这种情况建立得不是特别好。(他们在文档中承认了这一点。)您可以通过添加像Windsor这样的另一个IoC容器来解决这个问题。
实现最终看起来比我想象的要复杂得多,但一旦完成设置,它就不错,并且您可以访问Windsor的功能。我将提供另一个答案,其中不包括Windsor。我必须完成所有这些工作才能看到我可能更喜欢另一种方法。
在您的项目中,添加Castle.Windsor.MsDependencyInjection NuGet软件包。
用于测试的接口和实现
为了进行测试,我添加了一些接口和实现:
public interface ICustomService { }
public interface IRegisteredWithServiceCollection { }
public class CustomServiceOne : ICustomService { }
public class CustomServiceTwo : ICustomService { }
public class CustomServiceThree : ICustomService { }
public class RegisteredWithServiceCollection : IRegisteredWithServiceCollection { }
目的是创建一个工厂,使用一些运行时输入来选择并返回ICustomService
的实现。
这是一个作为工厂的接口。我们可以将其注入到一个类中,在运行时调用它以获取ICustomService
的实现:
public interface ICustomServiceFactory
{
ICustomService Create(string input);
}
配置Windsor容器
下一步是编写一个类来配置IWindsorContainer
以解决依赖关系:
public class WindsorConfiguration : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<ICustomService, CustomServiceOne>().Named("TypeOne"),
Component.For<ICustomService, CustomServiceTwo>().Named("TypeTwo"),
Component.For<ICustomService, CustomServiceThree>().Named("TypeThree"),
Component.For<ICustomService, CustomServiceOne>().IsDefault(),
Component.For<ICustomServiceFactory>().AsFactory(new CustomServiceSelector())
);
}
}
public class CustomServiceSelector : DefaultTypedFactoryComponentSelector
{
public CustomServiceSelector()
: base(fallbackToResolveByTypeIfNameNotFound: true) { }
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
return (string) arguments[0];
}
}
以下是这里正在进行的内容:
TypedFactoryFacility
将使我们能够使用Windsor的类型化工厂。它将为我们创建一个工厂接口的实现。
- 我们注册了三个
ICustomService
的实现。因为我们注册了多个实现,每个实现都必须有一个名称。当我们解析ICustomService
时,我们可以指定一个名称,它将根据该字符串来解析类型。
- 为了说明问题,我注册了另一个没有名称的
ICustomService
实现。这将使我们能够在尝试使用无法识别的名称进行解析时解析默认实现。(一些替代方案是抛出异常、返回ICustomService
的“null”实例或创建一个像UnknownCustomService
这样的类,该类会抛出异常。)
Component.For<ICustomServiceFactory>().AsFactory(new CustomServiceSelector())
告诉容器创建一个代理类来实现ICustomServiceFactory
。(更多信息请参见他们的文档。)
CustomServiceSelector
是负责获取传递给工厂的Create
方法的参数,并返回用于选择组件的组件名称(TypeOne、TypeTwo等)的对象。在本例中,我们期望传递给工厂的参数与我们使用的注册名称相同。但是我们可以用其他逻辑替换它。我们的工厂甚至可以接受其他类型的参数,我们可以检查并确定要返回哪个字符串。
配置您的应用程序以使用Windsor容器
现在,在StartUp中,修改ConfigureServices
以返回IServiceProvider
而不是void
,并创建一个IServiceProvider
,将直接注册到IServiceCollection
的服务与注册到Windsor容器的服务结合起来:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var container = new WindsorContainer();
container.Install(new WindsorConfiguration());
return WindsorRegistrationHelper.CreateServiceProvider(container, services);
}
container.Install(new WindsorConfiguration())
允许 WindsorConfiguration
配置我们的容器。我们可以在此方法中直接配置容器,但这是一种保持容器配置组织化的好方法。我们可以创建 numerous IWindsorInstaller
实现或自己的自定义类来配置 Windsor 容器。
WindsorRegistrationHelper.CreateServiceProvider(container, services)
创建使用 container
和 services
的 IServiceProvider
。
它是否有效?
我不会没有先弄清楚就发布所有这些内容。以下是一些 NUnit 测试。(我通常为 DI 配置编写一些基本测试。)
设置创建类似于应用程序启动时会发生的 IServiceProvider
。它创建一个容器并应用 WindsorConfiguration
。我还直接向 ServiceCollection
注册服务,以确保两者能够很好地配合。然后将两者合并为一个 IServiceProvider
。
然后我从 IServiceProvider
中解析出一个 ICustomerServiceFactory
,并验证它对于每个输入字符串返回正确的 ICustomService
实现,包括当字符串不是已识别的依赖项名称时的回退。我还验证了直接向 ServiceCollection
注册的服务已被解析。
public class Tests
{
private IServiceProvider _serviceProvider;
[SetUp]
public void Setup()
{
var services = new ServiceCollection();
services.AddSingleton<IRegisteredWithServiceCollection, RegisteredWithServiceCollection>();
var container = new WindsorContainer();
container.Install(new WindsorConfiguration());
_serviceProvider = WindsorRegistrationHelper.CreateServiceProvider(container, services);
}
[TestCase("TypeOne", typeof(CustomServiceOne))]
[TestCase("TypeTwo", typeof(CustomServiceTwo))]
[TestCase("TYPEThree", typeof(CustomServiceThree))]
[TestCase("unknown", typeof(CustomServiceOne))]
public void FactoryReturnsExpectedService(string input, Type expectedType)
{
var factory = _serviceProvider.GetService<ICustomServiceFactory>();
var service = factory.Create(input);
Assert.IsInstanceOf(expectedType, service);
}
[Test]
public void ServiceProviderReturnsServiceRegisteredWithServiceCollection()
{
var service = _serviceProvider.GetService<IRegisteredWithServiceCollection>();
Assert.IsInstanceOf<RegisteredWithServiceCollection>(service);
}
}
这一切值得吗?
现在我已经弄清楚了,如果我真的需要这种功能,我可能会使用它。如果你试图同时使用Windsor和.NET Core,并且第一次看到它的抽象工厂实现,那么情况会更糟。 这里有另一篇文章,介绍了关于Windsor抽象工厂的更多信息,没有关于.NET Core的干扰。
photoServiceFactory.GetServiceForUser(User user)
,但说实话,如果您所需的只是基于条件的不同凭据,我不确定为什么您需要三个不同的照片服务实现。 - Tseng