Inject Array of Interfaces in Ninject
Consider the following code.
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>();
}
}
When I try to run the program I get the following exception.
Error activating IFoo
No matching bindings are available, and the type is not self-bindable. Activation path: 2) Injection of dependency IFoo into parameter foos of constructor of type Bar 1) Request for Bar
How can I inject / bind to an array in Ninject?
Thanks for you开发者_如何学运维r time.
Edit:
My application imports data which is created by a third party component. The import process applies different kind of filters (e.g. implementations of different filter interfaces). The rules for filtering change quite often but are too complex to be done with pure configuration (and a master filter).I want to make adding/editing filters as easy as possible. What I have is an assembly where all the filter implementations are located in. I tried to bind every filter interface to the following method (which provides an instance of every implementation of that filter type). Basically I want to avoid the need to change my Ninject module when I add/remove filter classes.
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>();
}
I am feeling a bit guilty in terms of bypassing the containers DI mechanism. Is this a bad practice? Is there a common practice to do such things?
Resolution:
I use a wrapper class as bsnote suggested.Ninject supports multi injection which would resolve your issue. 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..
}
}
This is largely a restatement of @bsnote's answer (which I've +1d) which may help in understanding why it works in this manner.
Ninject (and other DI / addin frameworks) have two distinct facilities:
- the notion of either binding to a single unambiguous implementation of a service (
Get
) - A facility that allows one to get a set of services [that one then programmatically picks one of or aggregates across in some way] (
GetAll
/ResolveAll
in Ninject)
Your example code happens to use syntax that's associated with 2. above. (e.g., in MEF, one typically use [ImportMany]
annotations to make this clear)
I'd need to look in the samples (look at the source - its really short, clean and easy to follow) to find a workaround for this.
However, as @bsnote says, one way of refactoring your requirement is to wrap the array either in a container, or to have an object that you ask for it (i.e., a factory method or repository type construct)
It may also be useful for you to explain what your real case is - why is there a naked array ? Surely there is a collection of items construct begging to be encapsulated underlying all this - this question certainly doesnt come up much?
EDIT: There are a set of scanning examples in the extensions that I imagine would attack a lot of the stuff you're trying to do (In things like StructureMap, this sort of stuff is more integrated, which obviously has pros and cons).
Depending on whether you're trying to achieve convention over configuration or not, you might want to consider sticking a marker interface on each type of plugin. Then you can explicitly Bind
each one. Alternately, for CoC, you can make the Module
's Load()
routine loop over the set of implementations you generate (i.e., lots of individual Get
s) in your edit.
Either way, when you have the multiple registrations in place you can happily either 'request' a T[]
or IEnumerable<T>
and get the full set. If you want to achieve this explicitly (i.e., Service Locator and all it implies - like in you're doing, you can use GetAll
to batch them so you're not doing the looping that's implicit in the way you've done it.
Not sure if you've made this connection or if I'm missing something. Either way, I hope it's taught you to stick some code into questions as it speaks > 1000 words :P
It was a problem for me as well. Ninject injects each item of an array instead of the array itself, so you should have a mapping defined for the type of array items. Actually there is no possibility to map the array as a type with the current version of Ninject. The solution is to create a wrapper around the array. Lazy class can be used for example if it suits you. Or you can create your own wrapper.
Since Array implements IReadOnlyList the following works.
// 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) { ;}
}
精彩评论