I had someone send me an e-mail today asking me about configuring Ninject in a real application since most other DI frameworks just have a default config section or file that they just magically read from in order set themselves up. Well, the answer is quite simple, and I think that as a community we have become so used to config files and xml settings that we can forget how easy it is to just configure things in code. I’m going to lay out my DI container that I use in a few of my projects, and it is so simple that you will have a “duh!” moment.
I think another part of it is also that Ninject does not have a single “Factory” that you can request objects from, since in Ninject you can have multiple kernels and multiple modules per kernel. Below is a simple setup with a single kernel and module, but I’m sure you can see how it flesh it out a bit more if you wanted multiple.
First thing I want to do is to make my application DI framework agnostic (You may or may not care about this). So, I do this by creating a class called DIFactory. This class will look something like this (and yes I made a class with static methods, some people might use instance methods. I have actually done this in a few projects, but since our DIFactory is used in tests and we rarely mock it, I am just showing this with static methods):
public static class DIFactory { public static T Resolve<T>() { } public static T Resolve<T>(string name) { } }
This covers the very basics of what most DI frameworks provide. You can probably already see where this is going, but lets take it further. What this class will allow us to do is just to call:
MyClass variable = DIFactory.Resolve<MyClass>();
And now you can resolve a class by type or by type and name. You can add overloads for passing in a Type object if you need them as well. In the case of Ninject, we could implement this class fully like this:
public static class DIFactory { private static IKernel kernel = null; public static T Resolve<T>() { if (kernel == null) { CreateKernel(); } return kernel.Get<T>(); } public static T Resolve<T>(string name) { if (kernel == null) { CreateKernel(); } return kernel.Get<T>(With.Parameters.ContextVariable("name", name)); } private static void CreateKernel() { IModule module = new CustomModule(); kernel = new StandardKernel(module); } }
So, now that we can resolve any class by calling one of these two methods. Notice that we are only using a very small part of the flexibility that Ninject gives us, but we have to make the call on how important it is to support standard DI framework functions and allow for us to easily switch it out in the future, or to use Ninject specific powers and tightly tie our app to the framework. This is almost always a tradeoff in any tool that we use.
As far as the custom module you see above, it would look something like this:
internal class CustomModule : StandardModule { public override void Load() { Bind<ICaffeinated>().To<DietCoke>().Always(); Bind<IEdible>().To<Cheetos>().Always(); Bind<ISugary>().To<Cookies>().Always(); Bind<IEditor>().To<Emacs>() .Only(When.Context.Variable("name").EqualTo("Linux")); Bind<IEditor>().To<VisualStudio() .Only(When.Context.Variable("name").EqualTo("Windows")); } }
This custom module was slightly modified from one of my presentations. And was also used on my screencast series over at Dimecasts.net. Pretty cool stuff. Well, I hope that this cleared something up for a few people, and I hope that this might inspire you to get our and use Ninject in your next application.
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.
Nice article Justin. 🙂 I’m going to add a link to it in the Ninject dojo. Thanks for writing it!
Ninject 1.5 is coming out within the next couple of weeks, and it will have direct support for string-based identifiers, so you won’t have to use the context variables to make it happen (although that’s a great solution in 1.0).
Thanks for this post, it is very helpful. I do have one question. Say somewhere in a class I have the following line:
[b]IEmailService emailer = DIFactory.Resolve<IEmailService>();
emailer.Send(to,message);[/b]
Have I not now got tight coupling to the CustomModule that DIFactory uses? What if I want to make an NUnit assembly that swaps out the implementation of IEmailService for a test stub? Doesn’t your DIFactory itself need its dependencies (i.e. Modules) injected somehow?
Apologies if I am missing something really obvious here!
I’m not sure that you are missing anything, but what you are asking could easily be accomplished by having a method on the DIFactory that just sets your Kernel. When in the context of tests you would use one kernel and in your application you could use another.
In the example above, yes, I am instantiating the kernel inside of the DIFactory and therefore they are coupled.
OK thanks Justin