views:

573

answers:

2

Hi,

I'm a newbie when it comes to DI and ninject and I'm struggling a bit about when the actual injection should happen and how to start the binding.

I'm using it already in my web application and it working fine there, but now I want to use injection in a class library.

Say I have a class like this:

public class TestClass
    {
        [Inject]
        public IRoleRepository RoleRepository { get; set; }
        [Inject]
        public ISiteRepository SiteRepository { get; set; }
        [Inject]
        public IUserRepository UserRepository { get; set; }

        private readonly string _fileName;

        public TestClass(string fileName)
        {
            _fileName = fileName;
        }

        public void ImportData()
        {
            var user = UserRepository.GetByUserName("myname");
            var role = RoleRepository.GetByRoleName("myname");
            var site = SiteRepository.GetByID(15);
            // Use file etc
        }

    }

I want to use property injection here because I need to pass in a filename in my constructor. Am I correct in saying that if I need to pass in a constructor parameter, I cannot use constructor injection? If I can use constructor injection with additional parameters, how do I pass those parameters in?

I have a console app that consumes by Test class that looks as follows:

class Program
    {
        static void Main(string[] args)
        {
// NinjectRepositoryModule Binds my IRoleRepository etc to concrete
// types and works fine as I'm using it in my web app without any
// problems
            IKernel kernel = new StandardKernel(new NinjectRepositoryModule());

            var test = new TestClass("filename");

            test.ImportData();

        }
    }

My problem is that when I call test.ImportData() my repositories are null - nothing has been injected into them. I have tried creating another module and calling

Bind<TestClass>().ToSelf();

as I thought this might resolve all injection properties in TestClass but I'm getting nowhere.

I'm sure this is a trivial problem, but I just can't seem to find out how to go about this.

Thanks

+4  A: 

You are directly newing TestClass, which Ninject has no way of intercepting - remember there's no magic like code transformation intercepting your news etc.

You should be doing kernel.Get<TestClass> instead.

Failing that, you can inject it after you new it with a kernel.Inject( test);

I think there's an article in the wiki that talks about Inject vs Get etc.

Note that in general, direct Get or Inject calls are a Doing It Wrong smell. In the case of your web app, the NinjectHttpModule and PageBase are the hook that intercepts object creation - there are similar interceptors / logical places to intercept in other styles of app.

Re your Bind<TestClass>().ToSelf(), generally a StandardKernel has ImplicitSelfBinding = true which would make that unnecessary.

Ruben Bartelink
Using kernel.Inject(test) seems to work. Thanks.Out of curiousity another question - I'm newing TestClass because I need to pass a string into the constructor. Is there a way of passing a parameter using kernel.Get<TestClass>()?
Ciaran
Yes - there are overloads of Get and you can also put them into the Bind
Ruben Bartelink
Generally a better patttern in place of hardwiring it like that is have the thing that needs the constructor arg instead reference a confuigurator object which you new up and then Bind when creating the module
Ruben Bartelink
So, in my case where I want to inject into a class from within a console app (it will eventually be a service), I shouldn't be using Get or Inject?Is there a class that my console app should derive from that does the wiring? I haven't seen anything like that in the docs.
Ciaran
Hmmm, do you have any links to tutorials/blog posts on that subject?
Ciaran
Hi Ciaran, I only had the wiki, which is great. Other than that the Micah Martin Agile Principles Patterns and Techniques in C# is excellent. Not aware of an interceptor specific to a service (or a console app. Main point is, top level code should inject top level object only. There shouldnt be calls to Get/Inject sprinkled around your code - maybe google service locator vs injecton antipattern or similar. sorry, GTG.
Ruben Bartelink
I do seem to be able to pass in my constructor if I use:ParameterCollection coll = new ParameterCollection();coll.Add(new ConstructorArgumentParameter("fileName", "yadda"));var test = kernel.Get<TestClass>(coll);test.ImportData();It does seem a little verbose which again leads me to believe I'm not quite using it correctly. Strange that all the docs on the ninject site use kernel.Get<> if that's not best practise.
Ciaran
Thanks for your help
Ciaran
Demos are just that... You sure there isnt a cleaner overload of Get for passing in params? If not, you could say Get<T>(new ParameterCollection { new ConstructorArgumentParameter("filename","yadda") }). The main thing instead of passing params is to have the class take a config object which houses the params.
Ruben Bartelink
In summary 1. Prefer a param object instead of passing a param 2. Failing that, prefer putting params in the Bind() (WithConstructorArgunment etc.) over in the Get. (See Contextual Binding in the wiki). Finally, be sure to read all of the wiki (N times!) and also Dimecasts.net has great content on both ninject and DI in general.
Ruben Bartelink
+1  A: 

OK,

I've found out how to do what I need, thanks in part to your comments Ruben. I've created a new module that basically holds the configuration that I use in the class library. Within this module I can either Bind using a placeholder Interface or I can add a constructor parameter to the CustomerLoader. Below is the code from a dummy console app to demonstrating both ways.

This might help someone else getting started with Ninject!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject.Core;
using Ninject.Core.Behavior;

namespace NinjectTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var kernel = new StandardKernel(new RepositoryModule(), new  ProgramModule());            
            var loader = kernel.Get<CustomerLoader>();
            loader.LoadCustomer();
            Console.ReadKey();
        }
    }

    public class ProgramModule : StandardModule
    {
        public override void Load()
        {
            // To get ninject to add the constructor parameter uncomment the line below
            //Bind<CustomerLoader>().ToSelf().WithArgument("fileName", "string argument file name");
            Bind<LiveFileName>().To<LiveFileName>();
        }
    }

    public class RepositoryModule : StandardModule
    {
        public override void Load()
        {
            Bind<ICustomerRepository>().To<CustomerRepository>().Using<SingletonBehavior>();
        }
    }

    public interface IFileNameContainer
    {
        string FileName { get; }
    }
    public class LiveFileName : IFileNameContainer
    {
        public string FileName
        {
            get { return "live file name"; }
        }
    }


    public class CustomerLoader
    {
        [Inject]
        public ICustomerRepository CustomerRepository { get; set; }
        private string _fileName;

        // To get ninject to add the constructor parameter uncomment the line below
        //public CustomerLoader(string fileName)
        //{
        //    _fileName = fileName;
        //}
        public CustomerLoader(IFileNameContainer fileNameContainer)
        {
            _fileName = fileNameContainer.FileName;
        }

        public void LoadCustomer()
        {
            Customer c = CustomerRepository.GetCustomer();
            Console.WriteLine(string.Format("Name:{0}\nAge:{1}\nFile name is:{2}", c.Name, c.Age, _fileName));
        }
    }

    public interface ICustomerRepository
    {
        Customer GetCustomer();
    }
    public class CustomerRepository : ICustomerRepository
    {
        public Customer GetCustomer()
        {
            return new Customer() { Name = "Ciaran", Age = 29 };
        }
    }
    public class Customer
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}
Ciaran
Looking good - that's pretty much what I meant. In the context of a real app I guess the IFilenameContainer stuff might instead by something general like DocumentLocation, and would be populated at the top level from the File|Open or the commandline etc. In general the concept of wrapping what the info that's being passed as raw data in params into something that you can get the information from at a higher level is what makes using parameters less commonplace (I've only used it once on my last project)
Ruben Bartelink