Использование Windsor
Я собираюсь обойти вопросы о том, имеет ли это смысл в этом случае, и просто попытаюсь ответить на вопрос, как задано:
Контейнер IoC .NET Coreне очень хорошо подходит для такого сценария.(Они подтверждают это в своей документации.) Вы можете обойти это, добавив еще один контейнер IoC, такой как 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
позволит нам использовать типизированные фабрики Виндзора.Это создаст реализацию нашего фабричного интерфейса для нас. - Мы регистрируем три реализации
ICustomService
.Поскольку мы регистрируем более одной реализации, каждая из них должна иметь имя.Когда мы разрешаем ICustomService
, мы можем указать имя, и оно разрешит тип в соответствии с этой строкой. - Для иллюстрации я зарегистрировал другую реализацию
ICustomService
без имени.Это позволит нам разрешить реализацию по умолчанию, если мы попытаемся решить, используя нераспознанное имя.(Некоторые альтернативы просто генерируют исключение, или возвращают «нулевой» экземпляр ICustomService
, или создают класс, такой как 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
для настройки нашего контейнера.Мы могли бы просто сконфигурировать контейнер прямо в этом методе, но это хороший способ сохранить наши конфигурации контейнера организованными.Мы можем создать множество реализаций IWindsorInstaller
или наших собственных пользовательских классов для настройки контейнера Windsor.
WindsorRegistrationHelper.CreateServiceProvider(container, services)
создает IServiceProvider
, который использует container
и services
.
Это работает?
Я бы не стал публиковать все это, не узнав сначала.Вот несколько тестов NUnit.(Я обычно пишу некоторые базовые тесты для конфигурации DI.)
При установке создается IServiceProvider
, аналогично тому, что происходит при запуске приложения.Создает контейнер и применяет WindsorConfiguration
.Я также регистрирую сервис напрямую в ServiceCollection
, чтобы убедиться, что они хорошо играют вместе.Затем я объединяю их в IServiceProvider
.
Затем я определяю ICustomerServiceFactory
из IServiceProvider
и проверяю, что он возвращает правильную реализацию 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 и , впервые увидев его абстрактную фабричную реализацию. Вот еще одна статья с дополнительной информацией об абстрактной фабрике Виндзора без всякого шума о .NET Core.