views:

434

answers:

2

I decided to start using Ninject and face an issue. Say I have the following scenario. I have an IService interface and 2 classes implementing this interface. And also I have a class, which has a constructor getting IService and an int. How can I create an instance of this class with Ninject (I dont want to hardwire this int, I want to pass it every time I get an instance)?

Here's some code illustrating the situation:

interface IService
{
    void Func();
}

class StandartService : IService
{
    public void Func()
    {
        Console.WriteLine("Standart");
    }
}

class AlternativeService : IService
{
    public void Func()
    {
        Console.WriteLine("Alternative");
    }
}


class MyClass
{
    public MyClass(IService service, int i)
    {
        this.service = service;
    }

    public void Func()
    {
        service.Func();
    }

    IService service = null;
}
class Program
{

    static void Main(string[] args)
    {
        IKernel kernal = new StandardKernel(new InlineModule(
            x => x.Bind<IService>().To<AlternativeService>(),
            x => x.Bind<MyClass>().ToSelf()));

        IService servise = kernal.Get<IService>();

        MyClass m = kernal.Get<MyClass>();
        m.Func();
    }
}
+1  A: 

I believe you can't. You should create MyClass by calling it's constructor and pass in an IService that you retrieve from the Ninject container.

 var m = new MyClass(kernal.Get<IService>(), 5);

Or you can create a factory for MyClasses:

public static MyClassFactory
{
    public static MyClass CreateInstance(int i)
    {
        return new MyClass(kernal.Get<IService>(), i);
    }
}

And now you don't call the constructor yourself, but use the factory:

 MyClass m = MyClassFactory.CreateInstance(5);

[Update:] As Ruben has shown, you can actually do this. Read his response carefully, as it has some great advise.

Steven
-1: While this solution may solve the issue at a superficial level, this is bad news as a general approach. Having a global Kernel object is an antipattern. Also, doing loads of Get calls is a very bad smell - that's not Dependency *Injection*, its Service Location. (and there's With ConstructorArgument and With ContextValue as in my answer). At the very minimum, one should be using a Provider to hook in any factory requirement of this nature.
Ruben Bartelink
@Ruben: I wasn't aware of the ConstructorArgument feature in Ninject, and I myself rather see calls to the Common Service Locator, rather than the use of a global Kernel object. I'm not sure this is Service Location, I rather think the solution is somewhere between Service Location and Dependency Injection. I also find the solution with the ConstructorArgument quite verbose.
Steven
@Steven. Using CSL is a completely different matter which is best left out of this - things are confusing enough already. The point is that you're generally using a DI container Wrong if you're asking it for stuff in lots of places. And having whats effectively a Singleton container (MyClass.CreateInstance requires `kernal` to be `static`) like in your snippet is also a Bad Idea - it means your code is far too intimate with its container.
Ruben Bartelink
The `ToProvider` method is the built-in way to do a factory like this cleanly. Please take the time to investigate this and offer a code sample that uses that (rather than cribbing about syntax and providing other diversions which ignore my central point which is that this is Doing It Wrong on a number of levels)
Ruben Bartelink
@Steven: Ta for the ack. And watch out for some +1s (but also -1s) on other answers from me - You have some quite good answers that arent like this one (or your brushing off of my comments)! One point though, it's good practice to either delete your answer if its a duplicate (or at the very least add something extra in as an edit if someone has (mistakenly IMNSHO (rather than left alone or downvoted))) upvoted you and you're not happy to delete it.
Ruben Bartelink
@Ruben: I wasn't aware of being able to delete my own answers. Thank you for the tip. Haha.. so you're screening my answers now :-). Thank you for the compliment. However, I don't understand what you mean by 'brushing off' (I'm not a native speaker).
Steven
Ik ben ook niet een native gespreeker (maarvan Nederlands!). We all screen eachothers answers around here (some more militantly than others - plenty people have 'taught me lessons', which has generally been helpful. My reference to brushing off was that your first response was (paraphrasing) "Yes, but you're only nitpicking and you wouldnt do that as the syntax is ugly and anyway my answer is fine - it's not that different".
Ruben Bartelink
In other words, if a beginner who understands far less than you sees references to CSL and arguing the toss over syntax, they're liable to think "Whatever. Sounds like Steven had it right and Ruben is just nitpicking his answer to make his seem cooler for some reason". Which is what I'd think if I didnt know much about DI and looked at the general qualify of your other answers.
Ruben Bartelink
@Ruben: Altijd grappig om andere Nederlanders tegen te komen :-). Thank you for the explanation. I didn't mean to brush off your comment, but after rereading my comment I definitely see your point. But of course you are nitpicking ;-), as I am also often 'mierenneuken', because I believe nitpicking is important in our profession.
Steven
+6  A: 

With.ConstructorArgument exists for this purpose:- http://stackoverflow.com/questions/1374098/with-parameters-constructorargument-with-ninject-2-0

See http://stackoverflow.com/questions/2153770/inject-value-into-injected-dependency for more details and examples of how to use the context, providers and arguments to pass stuff like this around more correctly.

EDIT: As Steven has elected to pretend my comment is irrelevant, I'd best make clear what I'm saying with some examples:

MyClass m = kernal.Get<MyClass>( With.Parameters.ConstructorArgument( "i", 2 ) );

which to my eyes is very clear and states exactly what's happening.

If you're in a position where you can determine the parameter in a more global way you can register a provider and do it like this:

class MyClassProvider : SimpleProvider<MyClass>
{
    protected override MyClass CreateInstance( IContext context )
    {
        return new MyClass( context.Kernel.Get<IService>(), CalculateINow() );
    }
}

And register it like this:

x => x.Bind<MyClass>().ToProvider( new MyClassProvider() )

NB the CalculateINow() bit is where you'd put in your logic as in the first answer.

Or make it more complex like this:

class MyClassProviderCustom : SimpleProvider<MyClass>
{
    private Func<int> _calculateINow;
    public MyClassProviderCustom( Func<int> calculateINow )
    {
        _calculateINow = calculateINow;
    }

    protected override MyClass CreateInstance( IContext context )
    {
        return new MyClass( context.Kernel.Get<IService>(), _calculateINow() );
    }
}

Which you'd register like so:

x => x.Bind<MyClass>().ToProvider( new MyClassProviderCustom( (  ) => new Random( ).Next( 9 ) ) )

As stated earlier, if you need to pass a different parameter each time and you have multiple levels in the dependency graph, you might need to do something like this.

A final consideration is that because you havent specified a Using<Behavior>, it's going to default to the default as specified / defaulted in the options for the kernel (TransientBehavior in the sample) which might render fact that the factory calculates i on the fly moot [e.g., if it the object was being cached]

Now, to clarify some other points in the comments that are being FUDed and glossed over. Some important things to consider about using DI, be it Ninject or whatever else is to:

  1. have as much as possible done by constructor injection so you dont need to use container specific attributes and tricks. There's a good blog post on that called yourt showing your container

  2. minimise code going to the container and asking for stuff - otherwise your code is coupled to a) the specific container (which the CSL can minimise) b) the way in which your entire project is laid out. There are good blog posts on that showing that CSL isnt doing what you think it does. This general topic is referred to as Service Location vs Dependency Injection

  3. minimise use of statics and singletons

  4. dont assume there is only one [global] container and that it's OK to just demand it whenever you need it like a nice global variable. The correct use of multiple modules and Bind..ToProvider gives you a structure to manage this. That way each separate subsystem can work on it's own and you wont have low leve components being tied to top-level components etc.

If someone wants to fill in the links to the blogs I'm referring to, I'd appreciate that (they're all already linked from other posts on SO though, so all of this is just duplication UI've introduced with the aim of avoiding the confusion of a misleading answer.)

Now, if only Joel could come in and really set me straight on what's nice syntax and/or the right way to do this!

Ruben Bartelink
+1 for this great answer.
Steven