Okay, so remember when we said type bindings were from a service type to an implementation type? Well, we lied. You caught us. Type binding is really a little bit more complicated than we let on. Rather than being from one type to another, bindings are actually from a service type to a provider. Simply put, a provider is an object that can create instances of another type. It's like a factory, but specifically designed to work with Ninject.

When you define a binding from a service type to an implementation type, like the binding from IWeapon to Sword in the previous example, you're really declaring a binding from IWeapon to a special built-in provider called StandardProvider. This StandardProvider is like a "super-factory", in that it can create instances of all sorts of types. It also understands how to resolve the arguments to inject into constructors.

All of this stuff happens behind the scenes, though, and you rarely need to worry about it. However, sometimes you might want to do some sort of complex custom initialization to your objects, rather than letting Ninject work its magic. If you need to (or if you're just a control freak!), you can create your own provider and bind directly to it:

C#:
Bind<IWeapon>().ToProvider(new SwordProvider());

VB.NET:
Bind(Of IWeapon)().ToProvider(New SwordProvider())

Providers just need to implement the IProvider interface (in Ninject.Core.Activation):

C#:
interface IProvider {
  Type Prototype { get; }
  bool IsCompatibleWith(Type type);
  Type GetImplementationType(IContext context);
  object Create(IContext context);
}

Although this might look kind of complicated, the majority of custom providers don't need to implement the first two bits. The magic really happens in the Create() method. To simplify matters, Ninject also has an abstract type called SimpleProvider<T> that gives you the basics, and adds in strong-typing as well. Here's what SimpleProvider<T> looks like:

C#:
abstract class SimpleProvider<T> {
  // Simple implementations of the junk that you don't care about...

  public object Create(IContext context) {
    return CreateInstance(context);
  }

  protected abstract T CreateInstance(IContext context);
}

By using SimpleProvider<T>, custom providers just need to override the CreateInstance() method. Here's an example of a simple provider for our Sword class:

C#:
class SwordProvider : SimpleProvider<Sword> {
  protected override Sword CreateInstance(IContext context) {
    Sword sword = new Sword();
    // Do some complex initialization here.
    return sword;
  }
}

VB.NET:
Public Class SwordProvider
  Inherits SimpleProvider(Of Sword)
  Protected Overrides Function Sword CreateInstance(context as IContext)
    Dim sword As New Sword()
    'Do some complex initialization here.
    Return Sword
  End Function
End Class

This brings up another hidden complexity in Ninject, the context. See, Ninject is pretty smart. When it's resolving a big complex graph of objects, it keeps track of where it is, what's being injected, who's asking for it, etc. All of this information is stored in the context, represented by the IContext interface. If you need it, your provider can access all of this information to make decisions about how to resolve the dependencies. If you don't need it (like in the SwordProvider above), you can just ignore it.

Each time an instance is resolved, a new child context is created and the kernel's Get() is called again. Through this (somewhat) recursive operation, Ninject forms a stack of activation contexts, all of which are available throughout the system during the activation process.

Continue reading: The Activation Process

Last edited Oct 12, 2009 at 4:24 PM by NotMyself, version 2

Comments

No comments yet.