I'm having a little trouble wrapping my head around the Ninject Factory Extension.
I have the following class structure
public class Resource
{
public IResourceLoader ResourceLoader {get;set;}
public Resource(IResourceLoader ResourceLoader)
{
this.ResourceLoader = ResourceLoader ;
}
}
public class Banner : Resource
{
public Banner([Named("pngLoader")] IResourceLoader ResourceLoader)
:base(ResourceLoader)
{ }
}
public class MetaData : Resource
{
public MetaData ([Named("xmlLoader") IResourceLoader ResourceLoader)
:base(ResourceLoader)
{ }
}
interface IResourceLoader
{
object resource {get;}
}
public class XMLLoader : IResourceLoader
{
public resource { return "this was sent by xml loader"; }
}
public class PNGLoader : IResourceLoader
{
public resource { return "this was sent by png loader"; }
}
I'm trying to implement convention based filtering based on the Named attribute as show here. So I implemented the following interface.
interface IResourceLoaderFactory
{
IResourceLoader GetxmlLoader();
IResourceLoader GetpngLoader()
}
And then my bindings in the dependency resolver look like
kernel.Bind<IResourceLoader>().To<XMLLoader>().NamedLikeFactoryMethod((IResourceLoaderFactory f) => f.GetxmlLoader());
kernel.Bind<IResourceLoader>().To<PNGLoader>().NamedLikeFactoryMethod((IResourceLoaderFactory f) => f.GetpngLoader());
Assuming the above is correct, I don't know how to proceed to have it so that Ninject gives Banner or MetaData the correct IResourceLoader based on the [Named] in the constructor that it passes it to the base constructor.
I'm using all of this in an mvc 5 application like
public class HomeController
{
public ActionResult Index(/* do banners and meta need to be asked for here? */)
{
/* or do I need to instantiate them here/? */
Banner banner = new Banner(/* what to put here? */);
Meta meta = new Meta(/* what to put here? */);
...
}
}
Thanks
Let me try to answer your question, i'm not a 100% sure i've understand your question correctly, so please give me feedback if i haven't.
Now, your basic problem is that you want to inject an IResourceLoader
- but depending on what you inject it into, it should either be an XMLLoader
or a PNGLoader
.
You've correctly identified named bindings as one possible solution for choosing the appropriate IResourceLoader
.
However, you don't need to combine NamedLikeFactory
and [Named]
-Attribute pattern to achieve what you want, one of those is enough, and here the [Named]
-Attribute is probably the better alternative of the two (there is a third which i'll get to later).
So here's what you do:
public const string XmlLoaderName = "XmlLoader";
public const string PngLoaderName = "PngLoader";
Bind<IResourceLoader>().To<XMLLoader>()
.Named(XmlLoaderName);
Bind<IResourceLoader>().To<PNGLoader>()
.Named(PngLoaderName);
And then you specify the appropriate type in the ctor (as you did):
public class Banner : Resource
{
public Banner([Named(pngLoaderName)] IResourceLoader ResourceLoader)
:base(ResourceLoader)
{ }
}
public class MetaData : Resource
{
public MetaData ([Named(xmlLoaderName) IResourceLoader ResourceLoader)
:base(ResourceLoader)
{ }
}
and that's basically it!
now to use it in your controller all you've got to do is:
public class HomeController
{
public HomeController(Banner baner, MetaData metaData)
{
...
}
}
no need to use a factory. Except, in case you need to instantiate a Banner
orMetaData
instance per request, in which case you would create a factory interface:
public interface IResourceFactory
{
Banner CreateBanner();
MetaData CreateMetaData();
}
which is bound like:
Bind<IResourceFactory>().ToFactory();
// ToFactory is an extension method from Ninject.Extensions.Factory
which will be used like:
public class HomeController
{
private readonly IResourceFactory resourceFactory;
public HomeController(IResourceFactory resourceFactory)
{
this.resourceFactory = resourceFactory;
}
public ActionResult Index()
{
var banner = this.resourceFactory.CreateBanner();
....
}
}
Alternative for Named
binding: You could also use a conditional binding like:
Bind<IResourceLoader>().To<XMLLoader>()
.WhenInjectedInto<Banner>();
and corresponding Banner
ctor:
public Banner(IResourceLoader resourceLoader)
{
...
}
This alternative can make sense if there's a very limited set of classes which get a ResourceLoader injected. Or if you can't add an [Named]
-Attribute to the ctor (for example because it's a third party library...).
Another alternative altogether again would be to give Ninject more information on how to construct a type. For Example:
Bind<Banner>().ToSelf()
.WithConstructorArgument(
typeof(IResourceLoader),
ctx => ctx.Kernel.Get<XmlLoader>());