Внедрить массив интерфейсов в Ninject - PullRequest
14 голосов
/ 23 июня 2010

Рассмотрим следующий код.

public interface IFoo { }


public class Bar
{
    public Bar(IFoo[] foos) { }
}


public class MyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IFoo[]>().ToConstant(new IFoo[0]);
        // ToConstant() is just an example
    }
}


public class Program
{
    private static void Main(string[] args)
    {
        var kernel = new StandardKernel(new MyModule());
        var bar = kernel.Get<Bar>();
    }
}

Когда я пытаюсь запустить программу, я получаю следующее исключение.

Ошибка активации IFoo
Соответствующие привязки недоступны, и тип не является самосвязываемым.
Путь активации:
2) Внедрение зависимости IFoo в параметр foos конструктора типа Bar
1) Запрос на бар

Как добавить / связать массив в Ninject?

Спасибо за ваше время.

Edit:
Мое приложение импортирует данные, созданные сторонним компонентом. В процессе импорта применяются фильтры различного типа (например, реализации различных интерфейсов фильтров). Правила фильтрации меняются довольно часто, но они слишком сложны, чтобы их можно было выполнить с чистой конфигурацией (и главным фильтром).

Я хочу сделать добавление / редактирование фильтров максимально простым. У меня есть сборка, в которой находятся все реализации фильтра. Я попытался привязать каждый интерфейс фильтра к следующему методу (который предоставляет экземпляр каждой реализации этого типа фильтра). По сути, я хочу избежать необходимости менять мой модуль Ninject при добавлении / удалении классов фильтров.

    private IEnumerable<TInterface> GetInterfaceImplementations<TInterface>(IContext context)
    {
        return GetType().Assembly.GetTypes()
            .Where(t => typeof (TInterface).IsAssignableFrom(t) && IsConcreteClass(t))
            .Select(t => Kernel.Get(t)).Cast<TInterface>();
    }

Я чувствую себя немного виноватым с точки зрения обхода механизма DI контейнеров. Это плохая практика? Есть ли обычная практика для таких вещей?

Разрешение:
Я использую класс-оболочку, как подсказал bsnote.

Ответы [ 4 ]

9 голосов
/ 14 ноября 2012

Ninject поддерживает множественную инъекцию, которая решит вашу проблему. https://github.com/ninject/ninject/wiki/Multi-injection

public interface IFoo { }
public class FooA : IFoo {}
public class FooB : IFoo {}

public class Bar
{
    //array injected will contain [ FooA, FooB ] 
    public Bar(IFoo[] foos) { }
}

public class MyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IFoo>().To<FooA>();
        Bind<IFoo>().To<FooB>();
        //etc..
    }
}
7 голосов
/ 24 июня 2010

Это в значительной степени повторение ответа @ bsnote (который я + 1d), который может помочь понять, почему он работает таким образом.

Ninject (и другие структуры DI / addin) имеют два различных средства:

  1. понятие либо привязки к одной однозначной реализации службы (Get)
  2. Средство, которое позволяет получить набор услуг [которые затем программно выбирают один из или каким-либо образом объединяются) (GetAll / ResolveAll в Ninject)

В вашем примере кода используется синтаксис, связанный с 2. выше. (например, в MEF для пояснения обычно используют аннотации [ImportMany])

Мне нужно было бы посмотреть в примерах (посмотрите на источник - он очень короткий, чистый и понятный), чтобы найти обходной путь для этого.

Однако, как говорит @bsnote, один из способов рефакторинга вашего требования состоит в том, чтобы обернуть массив либо в контейнере, либо получить запрашиваемый вами объект (т. Е. Фабричный метод или конструкцию типа репозитория)

Вам также может быть полезно объяснить, каков ваш реальный случай - почему существует голый массив? Конечно, есть коллекция конструктов предметов, просящих заключить их в основу всего этого - этот вопрос, конечно, не поднимается?

РЕДАКТИРОВАТЬ: Существует несколько примеров сканирования в расширениях, которые, я думаю, могут атаковать многие вещи, которые вы пытаетесь сделать (В таких вещах, как StructureMap, такие вещи более интегрированы, что, очевидно, имеет плюсы и минусы).

В зависимости от того, пытаетесь ли вы достичь соглашения о конфигурации или нет, вы можете рассмотреть возможность установки интерфейса маркера на каждый тип плагина. Тогда вы можете явно Bind каждый. В качестве альтернативы, для CoC вы можете сделать цикл Module s Load() для набора реализаций, которые вы генерируете (то есть, множество отдельных Get s) при редактировании.

В любом случае, если у вас есть несколько регистраций, вы можете с радостью либо «запросить» T[] или IEnumerable<T> и получить полный набор. Если вы хотите достичь этого в явном виде (т. Е. Service Locator и все, что он подразумевает), как вы это делаете, вы можете использовать GetAll для их пакетной обработки, чтобы не выполнять зацикливание, которое подразумевается в том, как вы это сделали это.

Не уверен, что вы установили это соединение или я что-то упустил. В любом случае, я надеюсь, что вас научили вставлять какой-то код в вопросы, поскольку он говорит> 1000 слов: P

4 голосов
/ 24 июня 2010

Это была проблема и для меня. Ninject внедряет каждый элемент массива вместо самого массива, поэтому у вас должно быть определено отображение для типа элементов массива. На самом деле нет возможности отобразить массив как тип с текущей версией Ninject. Решением является создание оболочки вокруг массива. Ленивый класс можно использовать, например, если он вам подходит. Или вы можете создать свою собственную обертку.

0 голосов
/ 25 мая 2019

Поскольку Array реализует IReadOnlyList, следующие работы.

   // Binding
   public sealed class FooModule: NinjectModule 
   {
     public opverride void Load() 
     {
        Bind<IReadOnlyList<IFoo>>().ToConstant(new IFoo[0]);
      }
   }

   // Injection target
   public class InjectedClass {
      public InjectedClass(IReadOnlyList<IFoo> foos) { ;}
   }
...