What’s your favorite IoC container framework? For quite long time I stick to Ninject. First time I read about it in Steve Sanders ASP.NET MVC2 book and it’s my favorite container since then. But this blog post does not aim particular IoC framework, rather approach you can use. For code examples I’ll still be using Ninject, through.
What’s are conventions?
Convention over configuration is very popular trend nowadays. Many frameworks trying to adopt it, including ASP.NET MVC. In short, that means you are relying on some front-know conventions, like names of files, folder or classes and system does something meaningful, based on those conventions. Like, all controllers in ASP.NET MVC should have “Controller” postfix, so the framework is able to find and instantiate it.
Conventions make a developer’s life a little easier, you are no longer spending efforts on configuration, instead following some simple rules.
What we have for IoC?
My “Shu” level of IoC containers learning was configuration based. This is something you learn at very beginning. So, in case of Ninject, sooner of later you have huge file with dependencies configuration:
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ISettingsManager>().To<SettingsManager>();
kernel.Bind<IDirectoryProvider>().To<DirectoryProvider>().InSingletonScope();
kernel.Bind<ISetupFactory>().To<SetupFactory>();
kernel.Bind<ITargetsObjectBuilder>().To<DefaultTargetsObjectBuilder>();
kernel.Bind<ITargetsBuilder>().To<TargetsBuilder>();
kernel.Bind<IBounceFactory>().To<BounceFactory>();
kernel.Bind<ITargetsRetriever>().To<TargetsRetriever>();
kernel.Bind<IConfigObjectBuilder>().To<ConfigObjectBuilder>();
kernel.Bind<ILoggerFactory>().To<LoggerFactory>();
kernel.Bind<IHashService>().To<HashService>();
kernel.Bind<IAuthentication>().To<Authentication>();
kernel.Bind<IConfigurationsFactory>().To<ConfigurationsFactory>();
// More, more, more...
}
As more your application grows, as more services, repositories, factories you have. As long RegisterServices
method becomes longer, as more you forgot to correct correct it after some new dependency added, so you see YSOD during application run.
But please, put your attention to interface and class names. What we have here - ISettingsManager
and SettingsManager
; IDirectoryProvider
and DirectoryProvider
.. ILoggerFactory
and LoggerFactory
. Do you see pattern here? Exactly, it is [“I” + entity name] for interfaces and [entity name] for implementation.
Here we go for “Ha” level of IoC and apply convention based configuration.
Convention over configuration
Let’s take an advance of the fact above and shrink our configuration code. First of all, you need to get great extension for Ninject, called ninject.extensions.conventions.
After the package is installed, the simples Ninject start-up code ever would be:
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Scan(scanner =>
{
scanner.FromCallingAssembly();
scanner.BindWithDefaultConventions();
}
);
}
What it does? It has an abstraction called “scanner”. You instruct scanner what to do. In this example, I said - take calling assembly and bind all interfaces to implementation class having Default conventions. The default conventions are ones that I shown above.
That’s it, all those lines of code are simply gone away, since we rely on conventions and Ninject is aware how to deal with those conventions. If you added new dependency and followed convention, nothing you should do manually any more.
I have some questions!?
Ok, typically you have something little more complex. I’ll try to predict some questions you might have.
I have a lot of assemblies in my application, but want only particular ones to be scanned?
You can easily use only assemblies that matches particular patterns, like:
private static void RegisterServices(IKernel kernel)
{
kernel.Scan(scanner =>
{
scanner.FromAssembliesMatching("Candidate.*");
scanner.BindWithDefaultConventions();
}
);
}
Is it only default conventions I can use?
No, you can specify your own rules. Scanner has BindWith
method, that receives IBindingGenerator
type. You can implement your own binding generator for custom conventions.
private static void RegisterServices(IKernel kernel)
{
kernel.Scan(scanner =>
{
scanner.FromAssembliesMatching("Candidate.*");
scanner.BindWith<MyCustomConventionsGenerator>();
}
);
}
What if I have several cases of convention violations?
You are still able to bind in exactly same way as before:
private static void RegisterServices(IKernel kernel)
{
kernel.Scan(scanner =>
{
scanner.FromAssembliesMatching("Candidate.*");
scanner.BindWith<MyCustomConventionsGenerator>();
}
);
// classes that violates conventions
kernel.Bind<IMyInterface>().To<MyImpl>();
}
What is I have dependencies in different assemblies?
Best thing is to go with Ninject modules. First, correct the scanner:
private static void RegisterServices(IKernel kernel)
{
kernel.Scan(scanner =>
{
scanner.FromAssembliesMatching("Candidate.*");
scanner.AutoLoadModules();
scanner.BindWith<MyCustomConventionsGenerator>();
}
);
}
Now, each assembly should include module instance:
namespace Candidate.Core
{
public class CoreModule : NinjectModule
{
public override void Load()
{
// place convention-violation classes here
// Bind<IMyInterface>().To<MyImpl>();
}
}
}
Another one good feature of Convention based IoC is - “Config and forget”. Once done, use always and forget about manual binding once and for all.
I’ve heard about this on XP Days 2011 conference, by Mark Seemann. I think it’s nice approach.. I adopt it and going to use in my projects.