Вы можете использовать Контекстная инъекция .Однако, поскольку контекстно-зависимая инъекция, основанная на потребителе потребителя зависимости (или родителях их родителей), может привести к всевозможным осложнениям и незначительным ошибкам (особенно когда кешируется образ жизни, такой как Scoped
и Singleton
),API Simple Injector ограничивает вас на один уровень вверх.
Существует несколько способов обойти это кажущееся ограничение в Simple Injector.Первое, что вы должны сделать, это сделать шаг назад и посмотреть, сможете ли вы упростить свой дизайн, поскольку такие требования часто (но не всегда) возникают из-за неэффективности проектирования.Одной из таких проблем является нарушение принципа замены Лискова (LSP).С этой точки зрения, хорошо бы задать себе вопрос, сломается ли Mode1
, когда ему вводят Grouping
, который содержит стратегию для Mode2
?Если ответ «да», вы, вероятно, нарушаете LSP, и вам следует прежде всего попытаться решить эту проблему в первую очередь.После исправления вы, скорее всего, также увидите, что ваши проблемы с конфигурацией исчезнут.
Если вы определили, что дизайн не нарушает LSP, вторым лучшим вариантом является запись информации о типе потребителя непосредственно вграф.Вот краткий пример:
var container = new Container();
container.Collection.Append<IMode, Mode1>();
container.Collection.Append<IMode, Mode2>();
container.RegisterConditional(
typeof(ICommonBuilder),
c => typeof(CommonBuilder<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Transient,
c => true);
container.RegisterConditional(
typeof(IGrouping),
c => typeof(Grouping<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Transient,
c => true);
container.RegisterConditional<IGroupingStrategy, Strategy1>(
c => typeof(Model1) == c.Consumer.ImplementationType
.GetGenericArguments().Single() // Consumer.Consumer
.GetGenericArguments().Single(); // Consumer.Consumer.Consumer
container.RegisterConditional<IGroupingStrategy, Strategy2>(
c => typeof(Mode2)) == c.Consumer.ImplementationType
.GetGenericArguments().Single()
.GetGenericArguments().Single();
В этом примере вместо использования неуниверсального класса Grouping
создается новый класс Grouping<T>
, и то же самое делается для CommonBuilder<T>
.Эти классы могут быть подклассом неуниверсальных классов Grouping
и CommonBuilder
, размещенных в корне композиции, поэтому вам не нужно изменять код приложения для этого:
class Grouping<T> : Grouping // inherit from base class
{
public Grouping(IGroupingStrategy strategy) : base(strategy) { }
}
class CommonBuilder<T> : CommonBuilder // inherit from base class
{
public CommonBuilder(IGrouping grouping) : base(grouping) { }
}
Используя этот общий CommonBuilder<T>
, вы регистрируете, где T
становится типом потребителя, в которого он вводится.Другими словами, Mode1
будет введен с CommonBuilder<Mode1>
, а Mode2
получит CommonBuilder<Mode2>
.Это идентично тому, что обычно при регистрации ILogger
реализаций, как , показанное в документации .Однако из-за общей типизации CommonBuilder<Mode1>
будет введено с Grouping<CommonBuilder<Mode1>>
.
. Эти регистрации на самом деле не являются условными, скорее контекстными.Тип ввода изменяется в зависимости от его потребителя.Однако эта конструкция делает информацию о типе потребителя IGrouping
доступной в построенном объектном графе.Это позволяет применять условные регистрации для IGroupingStrategy
на основе информации этого типа.Вот что происходит внутри предиката регистрации:
c => typeof(Mode2)) == c.Consumer.ImplementationType // = Grouping<CommonBuilder<Mode2>>
.GetGenericArguments().Single() // = CommonBuilder<Mode2>
.GetGenericArguments().Single(); // = Mode2
Другими словами, если мы можем изменить реализацию IGrouping
таким образом, чтобы тип ее реализации (Grouping<T>
) предоставлял информацию о своих потребителях (IMode
реализации).Таким образом, условная регистрация IGroupingStrategy
может использовать эту информацию о потребителе своего потребителя.
Здесь регистрация запрашивает тип реализации потребителя (который будет Grouping<Mode1>
или Grouping<Mode2>
) и будет захватывать одиночныйуниверсальный аргумент из этой реализации (который будет либо Mode1
, либо Mode2
).Другими словами, это позволяет нам получить потребителя.Это можно сопоставить с ожидаемым типом, возвращающим либо true
, либо false
.
. Хотя это кажется немного неуклюжим и сложным, преимущество этой модели состоит в том, что полный граф объектов известен Simple Injector,что позволяет анализировать и проверять граф объекта.Это также позволяет иметь автоматическую проводку.Другими словами, если реализации IGrouping
или IGroupingStrategy
имеют (другие) зависимости, Simple Injector автоматически внедрит их и проверит их правильность.Это также позволяет визуализировать граф объекта без потери какой-либо информации.Например, это график, который будет отображать Simple Injector, если вы наведите на него курсор мыши в отладчике Visual Studio:
Mode1(
CommonBuilder<Mode1>(
Grouping<CommonBuilder<Mode1>>(
Strategy1()))
Очевидный недостаток этого подхода заключается в том, что если CommonBuilder<T>
или Grouping<T>
зарегистрированный как синглтон, теперь будет один экземпляр для закрытого универсального типа.Это означает, что CommonBuilder<Mode1>
будет отличаться от CommonBuilder<Mode2>
.
В качестве альтернативы, вы также можете просто сделать условную регистрацию CommonBuilder
следующим образом:
var container = new Container();
container.Collection.Append<IMode, Mode1>();
container.Collection.Append<IMode, Mode2>();
container.RegisterConditional<ICommonBuilder>(
Lifestyle.Transient.CreateRegistration(
() => new CommonBuilder(new Grouping(new Strategy1())),
container),
c => c.Consumer.ImplementationType == typeof(Mode1));
container.RegisterConditional<ICommonBuilder>(
Lifestyle.Transient.CreateRegistration(
() => new CommonBuilder(new Grouping(new Strategy2())),
container),
c => c.Consumer.ImplementationType == typeof(Mode2));
Это немного проще, чем в предыдущем подходе, но отключает Auto-Wiring.В этом случае CommonBuilder
и его зависимости соединяются вручную.Когда граф объектов прост (не содержит много зависимостей), этот метод может быть достаточно хорошим.Однако когда зависимости добавляются либо к CommonBuilder
, Grouping
, либо к стратегиям, это может привести к значительному обслуживанию и может скрывать ошибки, поскольку Simple Injector не сможет проверить график зависимостей от вашего имени.
Пожалуйста,Имейте в виду следующее утверждение в документации о методах RegisterConditional
:
Предикаты используются только во время компиляции графа объекта, а результат предиката записывается в структуре возвращаемого объекта.граф.Для запрошенного типа точно такой же график будет создаваться при каждом последующем вызове.Это запрещает изменять график в зависимости от условий выполнения.