Ninject Внедрение всех экземпляров универсального типа с помощью ninject

Я хотел бы иметь возможность использовать ninject для внедрения всех экземпляров определенного универсального типа в класс. Например, у меня есть куча пользовательских экстракторов формата, похожего на:

public interface IExtract<TEntity> 
{ 
    TEntity ExtractFrom(MyBulkExportedEntity exportedEntity);
}

и я хочу внедрить все экземпляры этих экстракторов в класс, отвечающий за обработку этого файла, используя множественную привязку ninject.

ie

public class ProcessDataExtract
{
    /*This isn't valid c# but demonstrates the intent of what i would like to do*/
    public ProcessDataExtract(IEnumerable<IExtract<>> allExtractors)
    {
    }

    public void Process(MyBulkExportedEntity exportedEntity)
    {
        /*loop through all of the extractors and pull relevant data from the object*/
    }
}

В прошлом я делал это, имея класс управления (IProvideExtractors), который напрямую обращается к ядру, но мне не нравится этот метод, и мне было интересно, знает ли кто-нибудь лучший способ сделать это. С помощью множественной привязки ninject я могу получить все интересующие меня экземпляры, используя kernel.GetAll(typeof(IExtract<>))


person Not loved    schedule 07.06.2012    source источник
comment
Внутри метода Process необходимо, чтобы IExtract<TEntity> был универсальным? Потому что в противном случае я бы создал неуниверсальный IExtract, а IExtract<TEntity> унаследовал бы от IExtract. И при правильной регистрации в вашем конструкторе ProcessDataExtract вы будете зависеть от IEnumerable<IExtract> allExtractors   -  person nemesv    schedule 08.06.2012


Ответы (3)


Я искал что-то связанное: я не хотел указывать все привязки отдельно, используя расширение Convention.

Во-первых: вам нужно внедрить List<IExtract> и унаследовать IExtract<T> : IExtract . Это просто связано с тем, что в C# нельзя указать тип коллекции, содержащей разные дженерики. Как вы отметили в своем вопросе, это недопустимый синтаксис - по уважительной причине, помимо этого ответа.

Позже вы можете вытащить элементы IExtract из списка и использовать отражение, чтобы получить параметр универсального типа и вернуть его обратно. Или, если вы знаете, какой экстрактор вы ищете:

public IExtract<T> GetExtractor<T>() {
    return (IExtract<T>)Extractors.Find(e => e is ExtractImpl<T>);
}

Теперь у вас может быть куча классов, для которых вы хотите, чтобы некоторые T были привязаны к IExtract`.

Bind<IExtract>().To<ExtractImpl<MyEntity>>();
Bind<IExtract>().To<ExtractImpl<YourEntity>>();

где

MyEntity : BaseEntity
YourEntity : BaseEntity

Вы можете указать соглашение следующим образом

Kernel.Bind(x => x.FromThisAssembly().SelectAllClasses()
    .InheritedFrom<BaseEntity>()
    .BindWith(new GenericArgumentBindingGenerator(typeof(IExtract<>))));

Где GenericArgumentBindingGenerator определяется как:

public class GenericArgumentBindingGenerator : IBindingGenerator
{
    private readonly Type m_Generic;

    public GenericArgumentBindingGenerator(Type generic)
    {
        if (!generic.IsGenericTypeDefinition)
        {
            throw new ArgumentException("given type must be a generic type definition.", "generic");
        }
        m_Generic = generic;
    }

    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
    {
        if (type == null)
            throw new ArgumentNullException("type");
        if (bindingRoot == null)
            throw new ArgumentNullException("bindingRoot");
        if (type.IsAbstract || type.IsInterface)
        {
            return Enumerable.Empty<IBindingWhenInNamedWithOrOnSyntax<object>>();
        }

        var bindings = new List<IBindingWhenInNamedWithOrOnSyntax<object>>();
        IBindingWhenInNamedWithOrOnSyntax<object> binding = bindingRoot
            .Bind(typeof(IExtract)) // you maybe want to pass typeof(IExtract) to constructor
            .To(m_Generic.MakeGenericType(type));

        bindings.Add(binding);

        return bindings;
    }
}
person Tarion    schedule 14.12.2013
comment
Мне очень нравится этот генератор привязок, он кажется довольно крутым, также я думаю, что вы правы, расширение неуниверсального решит эту проблему с точки зрения привязки - person Not loved; 17.12.2013
comment
Что такое IEnvironmentConfigFile, чтобы заменить его? - person chad.mellor; 17.12.2013
comment
О, черт, я пропустил один - я получил его из своего кода;) IEnvironmentConfigFile, я думаю, будет IExtract. Но его следует передать через конструктор GenericArgumentBindingGenerator, чтобы сделать его универсальным. вы действительно хотите связать Bind<IExtract>().To<ExtractImpl<YourEntity>>();, как указано выше. - person Tarion; 17.12.2013

Ответ на это, кажется, заключается в том, что нет способа сделать это с помощью ninject.

person Not loved    schedule 18.06.2012
comment
В разделе Inject Array of Interfaces in Ninject есть несколько обходных путей. - person ladenedge; 26.10.2012

Вариант А

Я уверен, что вы можете сделать это:

public class ProcessDataExtract
{
    public ProcessDataExtract<TExtract>(IEnumerable<IExtract<TExtract>> allExtractors)
    {
    }
    ...etc...
}

А затем перечислите свои привязки в методе Load вашего модуля привязки:

...
Bind<IExtract<TEntity>>().To<SomeConcreteExtract>();
Bind<IExtract<TEntity>>().To<AnotherConcreteExtract>();
Bind<IExtract<TEntity>>().To<YetAnotherConcreteExtract>();
...

И NInject доставит их вашему конструктору, который объявляет о зависимости от их множества. Я делал это в прошлом с успехом.

Вариант Б

Изменять

public interface IExtract<TEntity> 
{ 
    TEntity ExtractFrom(MyBulkExportedEntity exportedEntity);
}

to

public interface IExtract
{ 
    TEntity ExtractFrom<TEntity>(MyBulkExportedEntity exportedEntity);
}

Что позволит:

        public ProcessDataExtract<TExtract>(IEnumerable<IExtract<TExtract>> allExtractors)
    {
    }
    ...etc...

to be:

    public ProcessDataExtract(IEnumerable<IExtract> allExtractors)
    {
    }
    ...etc...

И привязки NInject тоже будут скорректированы:

...
Bind<IExtract>().To<SomeConcreteExtract>();
Bind<IExtract>().To<AnotherConcreteExtract>();
Bind<IExtract>().To<YetAnotherConcreteExtract>();
...
person bluevector    schedule 07.06.2012
comment
Да, так что в ninject есть концепция внедрения всех экземпляров привязки, которую я хотел бы использовать. Вы можете сделать это непосредственно из ninject с указанным выше экземпляром, используя kernel.GetAll(typeof(IExtract<>)), который вернет IEnumerable<object>, содержащий все мои экстракторы. Моя проблема не в этом, моя проблема в том, что я не могу понять, как указать это в моем конструкторе, так как приведенный выше код недействителен С# - person Not loved; 08.06.2012
comment
Я исправил синтаксис. Однако вы застрянете только с одним конкретным TExtract. Который должен быть неуниверсальным базовым классом (абстрактным или нет). Или вы можете переместить параметр типа из IExtract в ExtractFrom, что лишит однородность ProcessDataExtract. Я добавлю, что я имею в виду, в свой ответ. - person bluevector; 08.06.2012
comment
Я полагаю, что вы говорите, что я мог бы использовать Enumerable<IExtract<object>> allExtractors, я почти уверен, что это невозможно, поскольку я не думаю, что typeof(IExtract<object>).IsAssignableFrom(tyepof(IExtract<MyConcreteEntity>)) см. compilify.net/1tj - person Not loved; 08.06.2012
comment
Чтобы упростить жизнь с отражением, используйте соглашение об именах для конкретных классов IExtract, которые кодируют TEntity, чтобы вам не приходилось зацикливаться каждый раз, когда вы добавляете новый. - person bluevector; 08.06.2012
comment
это похоже на хак, чтобы обойти проблему, но наверняка есть более элегантное решение - person Not loved; 08.06.2012