Skip to content

Dependency Injection With Ninject

bartelink edited this page Mar 13, 2011 · 2 revisions

Rather than spending time doing the "busy work" of creating and connecting your objects (and reworking the boilerplate whenever you change interdependencies between your components), you just get Ninject to figure it out for you.

h2. How Ninject constructs your types for you

Ninject doesn't do anything crazy like rewriting all your asssemblies and replacing all @new@s with a redirection to it - it simply get's asked (generally implicitly via one of the "extensions":http://ninject.org/extensions, e.g., Ninject.Web.Mvc) to resolve a service. How does it do this? By calling one of the constructors on your type, just like you would if you were doing "Dependency Injection By Hand":https://github.com/ninject/ninject/wiki/Dependency-Injection-By-Hand. (It doesn't use Reflection though - it uses .NET "expression compilation":http://www.infoq.com/articles/expression-compiler to make it almost as fast as coding it by hand.)

When asked to instantiate an object, Ninject will look at the type's available public constructors and picks the one with the most parameters it knows how to resolve -- or the parameterless one if there aren't any suitable ones (there's an attribute that can be influence it's selection - see "Constructor injection":https://github.com/ninject/ninject/wiki/Injection-Patterns for the nitty gritty).

What this means is that in the vast majority of cases you need to do little if anything to the service classes you want to resolve and have dependencies injected into in order for Ninject to be able to instantiate your objects; you normally don't change your classes or have any need to reference Ninject classes, attributes or namespaces.

h2. How Ninject decides what types to use for type and interface/abstract dependencies -- type bindings

The most common thing you need to do is when the parameter being injected is specified as an interface - in that case we need to let Ninject know which concrete type to use to satisfy the interface. So if we have a service class as follows:-

bc. class Samurai { readonly IWeapon _weapon; public Samurai(IWeapon weapon) { _weapon = weapon; } public void Attack(string target) { _weapon.Hit(target); } }

and we want to grab one like this:-

bc. IKernel kernel = new StandardKernel(); var samurai = kernel.Get();

In the above case, Ninject will select the single constructor defined as the one to use. Next, it tries to resolve a value for each of the constructor's arguments. But wait a second... @IWeapon@ is an interface. You can't create an instance of an interface itself, so how does Ninject know what implementation of @IWeapon@ to inject?

This is accomplished through type bindings. A type binding (or just a binding) is a mapping between a service type (generally an interface or abstract class), and an implementation type to be used to satisfy such a service requirement (or a way in which to decide which one). Bindings are typically expressed via Ninject's fluent interface. For example, to arm our @Samurai@ with a @Sword@, we could declare the following binding:

bc. Bind().To();

This binding means that whenever Ninject encounters a dependency on @IWeapon@, it will resolve an instance of @Sword@ and inject it. This dependency resolution process is a recursive one; that is, if @Sword@ has any dependencies of its own, they will also be resolved before the constructor of @Samurai@ is called. (Also, if the dependencies of @Sword@ have dependencies as well, they will be resolved in turn, and so on.) In this way, Ninject can wire up an entire graph of objects for you, with minimal work on your end. You just need to set up the path, and Ninject will follow it.

The concept of type bindings is common in dependency injection containers / frameworks. However, some existing frameworks rely on XML mapping files to set up the bindings between types. Through its fluent interface, Ninject allows you to take advantage of the features of your language (like type-safety) and your IDE (like IntelliSense and code completion).

h2. Advanced bindings

If you're just trying to get the hang of dependency injection, we suggest skipping [straight to the next section on Injection Patterns]([Injection Patterns]) and coming back here later - this covers advanced aspects and capabilities of the Ninject type binding mechanism. It's only important to understand this if you're going to have extremely complex scenarios or you're trying to compare DI containers having learned another one one first.

h3. Using the overloads with @Type@ parameters instead of the generic methods

If you prefer, there are equivalent overloads of the @Bind()@ and @To()@ methods that are not generic methods, which can be useful for automating bulk binding of items you are discovering or scanning from other sources, e.g., one could do the binding above as follows instead:-

bc. Bind(typeof(IWeapon)).To(typeof(Sword));

h3. Skipping the type binding bit -- Implicit self-binding of concrete types

TDD and interface-based programming styles often mean you're generally most commonly depending on interfaces. However, if the type you're resolving is a concrete type (like @Samurai@ above), Ninject will automatically create a default association via a mechanism called implicit self binding. It's as if there's a registration like this:

bc. Bind().To();

for every concrete type you ever try to resolve. Bear in mind that only concrete types can be self-bound; abstract types and interfaces won't work (though there are a variety of "Ninject extensions":http://ninject.org/extensions available that e.g., allow one to plug in "Convention over Configuration":http://msdn.microsoft.com/en-us/magazine/dd419655.aspx based policies). Or... as with most things in Ninject, you can plug in your own strategy if you have specific requirements (but that customizability isn't in your face until you need it).

Implicit self-bindings are generated in the default "Object Scope":http://github.com/ninject/ninject/wiki/Object-Scopes (we'll discuss that shortly). If necessary, you can override that by adding a binding of your own of the form:

bc. Bind().ToSelf().// TODO add your rule overrides at the end, e.g. .InSingletonScope();

h3. Registering more than one type binding for a service -- contextual binding

You can also have multiple bindings for a given service type, controlled by conditions that examine the context in which the instance is being resolved. This is the contextual binding system mentioned earlier, and is the key to unlocking the full power of Ninject. Don't worry, it's discussed in excruciating detail later. :-)

h3. Registering more than one type binding for a service -- plugin models

In some cases, you want your service to get a list of n related dependencies rather than just one. This is accomplished by performing 0..n type bindings and then having your service type take a parameter of type @T[]@, @List@ or @Enumerable@

Continue reading: Injection Patterns