18

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.

Peter Majeed
  • 5,304
  • 2
  • 32
  • 57
Ciaran O'Neill
  • 2,572
  • 6
  • 34
  • 40

2 Answers2

18

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 of Service Location, which is an antipattern. 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 (unless you want to influence its Scope to be something other than .InTransientScope()).

A final style point:- you're using property injection. There are rarely good reasons for this, so you should be using constructor injection instead.

And do go buy Dependency Injection in .NET by @Mark Seemann, who has stacks of excellent posts around here which cover lots of important but subtle considerations in and around the Dependency Injection area.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • 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()? – Ciaran O'Neill Aug 18 '09 at 16:09
  • Yes - there are overloads of Get and you can also put them into the Bind – Ruben Bartelink Aug 18 '09 at 16:11
  • 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 Aug 18 '09 at 16:17
  • 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 O'Neill Aug 18 '09 at 16:25
  • Hmmm, do you have any links to tutorials/blog posts on that subject? – Ciaran O'Neill Aug 18 '09 at 16:30
  • 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 Aug 18 '09 at 16:34
  • 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(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 O'Neill Aug 18 '09 at 16:36
  • Demos are just that... You sure there isnt a cleaner overload of Get for passing in params? If not, you could say Get(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 Aug 18 '09 at 16:43
  • 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 Aug 18 '09 at 16:51
  • @RubenBartelink so in essence, you didn't quite answer his question (though he believed you did), you just said "the way you are doing it with Get or Inject is wrong, an antipattern, but I don't know how you do it for class library or console app, there has to be an interceptor for that but I have no clue about it." And yeah, to be fair, you did provide a reference to two or three books. Why was this ever accepted for the answer I don't know but it sure doesn't answer his question which goes simply: "I have it working in web app but not in class library or console app. How do I do that?" – mare Jun 21 '12 at 23:31
  • @mare I respectfully disagree. The OP wasnt causing Ninject to be used. I explained exactly how one would. Trust me, answers that are crap, dont solve it and don't help people don't get 9 upvotes. Have a look at some of yours and some others of mine. So... 1. Do you have a question? 2. What exact piece is missing in the answer part that you feel could be added to make it answer the question for you? Are you saying it's critical that I spoon feed out a working `Main` saying duplicating Ciaran's answer beside that? Are you saying I should be ashamed to not have included the stuff in OPs answer? – Ruben Bartelink Jun 22 '12 at 05:56
  • @mare: Rereading the comments, [this question](http://stackoverflow.com/questions/2227548) covers the thing the OP was actually not getting. But the answer **does** fit the question and is correct. Its only in the comments that things get confused. Finally once again though, do you have an actual question I can help you with/address and/or a concrete thing that you feel the answer should include? – Ruben Bartelink Jun 22 '12 at 08:04
  • The only thing that bothered me is there were 5 or more "you should this, you should that" and not a single piece of code that shows HOW it should be done. Picture or an example is worth a thousand words. I'm not discussing my answers, they aren't perfect I know. And I'm probably less skilled than you. The thing is, nothing in your answer led Ciaran to the answer he posted himself. There's nothing in comments either saying that he should be making a Module. I bet he spent few hours on figuring it out. With your answer or without it. – mare Jun 22 '12 at 13:04
7

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 O'Neill
  • 2,572
  • 6
  • 34
  • 40
  • 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 Aug 21 '09 at 08:10