March 26, 2008 by
ryan
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: