Search code examples
c#dependency-injectionninjectninject.web.mvcninject-extensions

Ninject Factory Extension


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


Solution

  • 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>());