Ryan LanciauxNew Media Mercenary

Real Ultimate Power : Dependency Injection with Ninject

March 26, 2008 by ryan
Ninja
UPDATE: Nate Kohari (the author of Ninject) posted some excellent information in the comments (enough info that it could probably be a post on its own).

Recently Scott Hanselman compiled a list of Dependency Injection frameworks for .NET. I really didn't plan on trying anything new but Ninject really jumped out at me (honestly, it was more the reference to ninjas than anything). After seeing a couple examples, I thought I would check it out in a bit more detail.

Disclaimer: I've been playing around with Ninject for all of about 3 hours now ... it's very possible there's a better way to do some of this stuff :) So I would really appreciate any feedback.

On to the code

Okay, for this very contrived example we're going to be building car objects out of just an engine and a drive type (extremely accurate I know). Just like in my StructureMap example, I'm going to start by creating the interfaces followed by a default class that we're going to use as our skeleton car ... the interfaces are pretty basic so no need to spend too much time on them.

    public interface IDriveType

    {

        string DriveType{ get;}

    }

 

    public interface IEngine

    {

        string EngineType { get; }

    }



Both of the interfaces have only one string property that will be used for output. A little on the basic side but hey, we're looking at IoC not an accurate car construction. Next we're going to add all our implementations of the Engine

    class FourCylinder : IEngine

    {

        public string EngineType

        {

            get { return "4-Cylinder"; }

        }

    }

 

    class Rotary : IEngine

    {

        public string EngineType

        {

            get { return "Rotary"; }

        }

    }

 

    class SixCylinder : IEngine

    {

        public string EngineType

        {

            get { return "6-Cylinder"; }

        }

    }



And the drive type implementations...

    public class RWD : IDriveType

    {

        public string DriveType

        {

            get { return "Rear Wheel Drive"; }

        }

    }

 

    class FourWD : IDriveType

    {

        public string DriveType

        {

            get { return "Four Wheel Drive"; }

        }

    }

 

    class FWD : IDriveType

    {

        public string DriveType

        {

            get { return "Front wheel drive"; }

        }

    }



And finally the class we're going to use as our base car.

    public class BaseAuto

    {

        private IDriveType _driveType;

        private IEngine _engine;

 

        public string DriveType

        {

            get { return _driveType.DriveType; }

        }

 

        public string Engine

        {

            get { return _engine.EngineType; }

        }

 

        [Inject]

        public BaseAuto(IDriveType drive, IEngine engine)

        {

            _driveType = drive;

            _engine = engine;

        }

    }



You'll notice the [Inject] attribute above our constructor. This is basically telling Ninject to toss in an implementation of the IDriveType and IEngine interfaces to this constructor (more on  Constructor Injection over here).

Now we're on to the fun stuff. Ninject does not use XML to configure injections. Instead we're going to use a class called Module to define all that. As the documentation says, this class should implement IModule but thankfully (at least for the sake of testing), there's a pre-defined base implementation called StandardModule that we can extend.

    public class BaseModule : StandardModule

    {

        public override void Load()

        {

            Bind<IEngine>().To<Rotary>();

            Bind<IDriveType>().To<RWD>();

        }

    }



In the module definition, we're basically saying when we request an object from the Kernel (more on that in a sec.) we want the Rotary class in place of IEngine and the RWD instead of IDriveType. Simple enough, now lets take a look at the Kernel definition / initial code (I'm using winforms for the sake of example but you can really go w/ whatever project type you'd like). A lot of thought went into the naming of the form.

    public partial class Form1 : Form

    {

        private BaseAuto _rx8;

 

        public Form1()

        {

            InitializeComponent();

 

            IKernel kernel = new StandardKernel(new BaseModule());

 

            _rx8 = new BaseAuto(

                kernel.Get<Auto.IDriveType>(),

                kernel.Get<Auto.IEngine>()

                );

 

            MessageBox.Show("RX8: \n Drive Type: " + _rx8.DriveType +

                "\n Engine:" + _rx8.Engine);

        }

    }



And when we run it...



Just what we expected! Lets make things a little more interesting ... Say we wanted to add some other cars to our application? We probably don't want everything to be a rear-wheel drive rotary (Unless you really like RX-8's -- in that case you can buy my RX8 *shameless plug*). Anyways, we're going to accomplish this by changing up our Module a bit to bind to a different IEngine / IDriveType implementation depending on the context.

    public class BaseModule : StandardModule

    {

        public override void Load()

        {

            Bind<IEngine>().To<Rotary>()

                .Only(When.Context.Variable("carType").EqualTo("RX8"));           

            Bind<IDriveType>().To<RWD>()

                .Only(When.Context.Variable("carType").EqualTo("RX8"));

 

            Bind<IEngine>().To<SixCylinder>()

                .Only(When.Context.Variable("carType").EqualTo("Jetta"));

            Bind<IDriveType>().To<FWD>()

                .Only(When.Context.Variable("carType").EqualTo("Jetta"));

 

            Bind<IEngine>().To<FourCylinder>()

                .Only(When.Context.Variable("carType").EqualTo("WRX"));

            Bind<IDriveType>().To<FourWD>()

                .Only(When.Context.Variable("carType").EqualTo("WRX"));

        }

    }



Not too bad right? I mean, just looking at the code we can pretty much tell what's going on due to the Fluent Interface goodness. We can perform more powerful comparisons on our context variable but for now, this will work. Next we need to set up our context in the core part of our application
(where we're instantiating and requesting classes from our Kernel).

        public Form1()

        {

            InitializeComponent();

 

 

            IKernel kernel = new StandardKernel(new BaseModule());

 

            _rx8 = new BaseAuto(

                    kernel.Get<Auto.IDriveType>(

                    With.Parameters.ContextVariable("carType", "RX8")

                ),

                kernel.Get<Auto.IEngine>(

                    With.Parameters.ContextVariable("carType", "RX8")

                )

            );

 

            _jetta = new BaseAuto(

                    kernel.Get<Auto.IDriveType>(

                    With.Parameters.ContextVariable("carType", "Jetta")

                ),

                kernel.Get<Auto.IEngine>(

                    With.Parameters.ContextVariable("carType", "Jetta")

                )

            );

 

 

            _wrx = new BaseAuto(

                    kernel.Get<Auto.IDriveType>(

                    With.Parameters.ContextVariable("carType", "WRX")

                ),

                kernel.Get<Auto.IEngine>(

                    With.Parameters.ContextVariable("carType", "WRX")

                )

            );

 

        }



I've added some buttons so we don't get spammed with message boxes on form load... if we fire this off and click on the various buttons, we see we're getting the expected results!


All in all, I'm liking Ninject for more than just the name -- I can definitely see myself using this in small to medium sized apps. Please let me know what you think...

More info on Ninject:


kick it on DotNetKicks.com




Related posts

Comments

March 27. 2008 12:38

Nikola Malovic

Nice post

The same "configureless" goodies you can get with Unity and Structure Map, where both have special features.

The downside of attribute magic based injection is that you need to add additional references to assembly containing the Attribute type, which is something Unity tackled in lost CTP drop

My personal preference on this matter is Unity (regardless of the fact it is not the most popular one in ALT NET community) because it has the best documentation and "borrows" all of the best features of all the IoC frameworks + some extra things.

Nikola Malovic

March 27. 2008 13:28

David Mohundro

Nice post - quick question about the way the injection is happening.

When looking at IoC containers like Castle Windsor or StructureMap, I'm used to seeing the construction of the class that is using the dependencies (i.e. BaseAuto in your example) by the container. This way, the container manages the dependencies and users of the class don't have to be aware of which dependencies are needed.

Do you know if Ninject provides this support?

David Mohundro

March 27. 2008 16:16

Ryan Lanciaux

@Nikola:
Thanks for the feedback -- I'll have to take a look at Unity sometime in the near future. I'm really not really trying to pit one IoC framework against another.

@David:
Maybe it is a little confusing how I am doing this in the example. BaseAuto is not being injected into the WinForms app so that aspect is still pretty coupled. Basically I wanted to show that I didn't have to supply a specific implementation of Engine and DriveType into the constructor of BaseAuto -- Ninject was injecting a specific implementation of the interfaces based on context. Additionally, the WinForms app does not need to have a reference to the class library that contains the implementations such as Rotary, RWD, etc... Hopefully I understood the question right -- please let me know if that helped Smile

Ryan Lanciaux

March 27. 2008 20:13

David Mohundro

I was thinking in terms of the actual construction of BaseAuto itself - to create BaseAuto, you're still calling instance = new BaseAuto(...) and then having Ninject retrieve implementations registered with it for each specific dependency. I was wondering if there was a way to ask Ninject to create an instance of BaseAuto by calling something like (in StructureMap's case) ObjectFactory.GetInstance<BaseAuto>() and StructureMap looks at the constructors marked with the Inject attribute and pulls those dependencies for you so that your WinForms app doesn't need to know about the required dependencies.

Wow, that was hard to try to explain Smile Hope I made sense.

David Mohundro

March 28. 2008 04:23

pingback

Pingback from blog.cwa.me.uk

Reflective Perspective - Chris Alcock » The Morning Brew #61

blog.cwa.me.uk

March 28. 2008 09:38

pingback

Pingback from alvinashcraft.com

Dew Drop - March 28, 2008 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

March 28. 2008 11:52

Joel

@David

I think that if you make an interface for BaseAuto that you could have had Ninject make that for you.

Joel

April 1. 2008 10:36

Nate Kohari

Hi Ryan! Nice article. Ninject can actually activate your BaseAuto directly, without having to call Get() for each of the arguments:

IKernel kernel = new StandardKernel(new BaseModule());
BaseAuto auto = kernel.Get<BaseAuto>();

The types that you request from the kernel don't necessarily need to be interfaces. If the kernel encounters a request for a concrete type (either via Get() or an [Inject] attribute), it will create an "implicit self-binding" for the type. This just means that if you ask for a BaseAuto, and there's no binding that indicates that a different type should be returned, Ninject will try to activate an instance of BaseAuto.

Since you've already got your injection constructor set up for BaseAuto, Ninject will automagically resolve arguments for the constructor when you do kernel.Get<BaseAuto>(). Behind the scenes, Ninject basically calls Get() again on the kernel to resolve objects for IDriveType and IEngine, and then calls the BaseAuto constructor with those objects as arguments.

Great use of context variables to control your bindings, also. Unfortunately, child contexts don't inherit their parents' variables (yet) -- so if you do what I said above and just call Get<BaseAuto>(), you won't be able to control the implementation types of IDriveType and IEngine with context variables.

I'm planning to add the option to inherit variables by v1.0, but another way of accomplishing this is:

class MazdaRX8 : BaseAuto {
[Inject]
public MazdaRX8([Tag("RearWheelDrive")] IDriveType driveType, [Tag("RotaryEngine")] IEngine engine)
: base(driveType, engine)
{}
}

Then, in your module:

class CarModule : StandardModule {
public override void Load() {
Bind<IDriveType>().To<RWD>().Only(When.Context.Member.Tag == "RearWheelDrive");
Bind<IEngine>().To<Rotary>().Only(When.Context.Member.Tag == "RotaryEngine");
}
}

Then when you do kernel.Get<MadzaRX8>(), you'll get a car that has the correct IDriveType and IEngine. (You can also create custom attributes, like [Rotary], if you want to avoid typos using [Tag].)

Also, if you'd rather not couple to the MadzaRX8 class directly, you can add another binding:

Bind<BaseAuto>().To<MadzaRX8>().Only(When.Context.Variable("carType") == "RX8");

Then when do you do:

kernel.Get<BaseAuto>(With.Parameters.ContextVariable("carType", "RX8"));

And you'll get a MazdaRX8 back, with the correct drive type and engine also.

Like I said, great post! If you have other questions, feel free to post them on the Ninject user group: http://groups.google.com/group/ninject !

Nate Kohari

April 1. 2008 10:54

Ryan Lanciaux

@Nate: Thank you so much -- I'm going to refactor this a bit in the near future to make some of those changes! I completely didn't realize that the types didn't need to be interfaces.

Ryan Lanciaux

December 7. 2008 03:18

pingback

Pingback from arload.wordpress.com

Dependency를 관리하는 방법 « arload - load to architect

arload.wordpress.com

January 15. 2009 16:04

Willie

I can definitely see myself using this in small to medium sized apps

Any reason why? Like, why not a large app? Is there a downside to using it in a large scale application?

Willie

January 15. 2009 16:13

Ryan Lanciaux

At the time of writing, I was thinking it may have a big impact on performance. Although there is a small effect on performance, its relatively negligible; especially when you factor in improved testability / extensibility of your codebase.

Ryan Lanciaux

February 13. 2009 23:35

Alvaro

Thanks for the nice examples, but I must ask: what's the point of all this error-prone complexity?

I mean, it seems to me that what you basically need is this:

_rx8 = new BaseAuto(new Rotary(), new RWD());
_jetta = new BaseAuto(new SixCylinder(), new FWD());
_wrx = new BaseAuto(new FourCylinder(), new FourWD());

Instead, you're adding a lot of complex-looking code that the compiler can't type-check for you (like it does for the 3 lines above), so if you miss one of your Bind statements, you won't know it until the program blows up at run-time. So the question again is, why?

Thanks!

Alvaro

February 14. 2009 00:18

ryan

@Alvaro: Thanks for your comment -- what are the errors you are mentioning?

This example, due to its simplicity, does not naturally lend itself to show the full benefit of IoC. Lets say BaseAuto had a lot more properties than two. This would make it so you wouldn't have to remember an BaseAuto with a drivetype of RWD, a Engine type of Rotary, a Gas tank size of ___, a tire tread of ___ and a headlamp bulb of ____ was an RX8. That is just one very contrived example please check out this link if you're interested in seeing more examples and benefits : http://martinfowler.com/articles/injection.html.

ryan

July 13. 2009 21:40

pingback

Pingback from answerspluto.com

list of urls - 5 « Answers Pluto

answerspluto.com

Comments are closed




© 2008 Ryan Lanciaux :: powered by BlogEngine.NET