Skip to content

Component Extension Points

Joe Wasson edited this page Mar 4, 2012 · 1 revision

IBindingResolver

The IBindingResolver interface is used by Ninject to locate bindings for resolution by the Kernel. It is primarily used to map the type requested to a similar type in the binding map. Learn more about building an IBindingResolver Component.

IMissingBindingResolver

When a binding can’t be found in the bindings map Ninject moves on to using IMissingBindingResolver components to try and locate a binding. The main difference between IBindingResolver and IMissingBindingResolver is that the latter creates bindings out of thin air to handle the request. Learn more about building an IMissingBindingResolver Component.

IPipeline

The pipeline consists of a list of IActivationStrategy components that are run whenever an object is created. The built in Pipeline class is a simple coordinator that makes use of an IActivationCache component to keep from activating an instance multiple times. It’s unlikely a new IPipeline implementation will be needed, as most tasks can be accomplished by implementing one of the IActivation* interfaces, but you can use it to pull strategies from a different location (instead of kernel.Components) or to selectively run strategies based on the context.

IPipeline is a simple interface: a property that lists the strategies in play and methods for activation and deactivation.

IActivationCache

The activation cache simply holds sets of references that have been activated and deactivated. One thing to note is that the IActivationCache is used in two places: IPipeline takes it to check whether it should activate a given instance, and ActivationCacheStrategy (an implementation of IActivationStrategy) takes it to actually add the instance to the cache. Be aware of this separation and its implications (for instance, your IActivationCache implementation should be thread safe).

IActivationStrategy

Objects created by Ninject are activated after creation and deactivated when they are manually released. These strategies can do pretty much anything they want, for example property and method injection are actually implemented as strategies. When building your own IActivationStrategy you can derive from ActivationStrategy to simplify your implementation.

A good example of an activation strategy is the built-in StartableStrategy. It checks to see if the instance is of type IStartable and if so calls Start or Stop on it.

public class StartableStrategy : ActivationStrategy
{
  public override void Activate(IContext context, InstanceReference reference)
  {
    reference.IfInstanceIs<IStartable>(x => x.Start());
  }

  public override void Deactivate(IContext context, InstanceReference reference)
  {
    reference.IfInstanceIs<IStartable>(x => x.Stop());
  }
}

ISelector

When Ninject needs to know which members of a class should be injected it looks to the ISelector component. In addition to holding the constructor scorer and the injection heurestics (see below), it also returns a list of injectable constructors, properties, and methods. Extension is pretty straightforward, get a list of the members, filter them, and then return them. If there are no constructors then the convention is to return null; for methods and properties return an empty IEnumerable.

For our example, let’s say that some of our classes take a special debug type that we only use during testing and debugging. For production we want to use the version of the constructor that does not take that type.

public class MyClass
{
  public MyClass() { }
  public MyClass(DebugType dT) { }
}

Since Ninject likes to pick the constructor with the most arguments it will always choose the second constructor. Here’s an example of how we would skip the debug constructor in production mode:

public class NoDebugSelector : Selector
{
  public NoDebugSelector(IConstructorScorer constructorScorer,
                         IEnumerable<IInjectionHeuristic> injectionHeuristics)
      : base(constructorScorer, injectionHeuristics) {}

  public override IEnumerable<ConstructorInfo> SelectConstructorsForInjection(Type type)
  {
    if(type == null) throw new ArgumentNullException("type");
    return type.GetConstructors(this.Flags)
               .Where(c => !c.GetParameters()
                             .Select(p => p.ParameterType)
                             .Contains(typeof (DebugType)))
               .ToArray();
  }
}

Note that a more DI convention would be to wrap DebugType in in interface or proxy and bind Ninject to return the real or stub verion of the interface depending on the configuration. Occaisonally, though, the above pattern is unavoidable.

Sometimes it may be simpler to modify the heurestics or the constructor scorer.

IInjectionHeuristic

When the ISelector is looking to decide whether it should select a property or method for injection it looks to the IInjectionHeuristic interface. This interface has a single method which takes a reflection MemberInfo and returns a boolean indicating whether it should inject. The standard heurestic chooses any member that has the [Inject] attribute. Here is an example that always injects our logging type s othat we don’t have to specifically add the [Inject] attribute every time:

public class MyHeuristic : NinjectComponent, IInjectionHeuristic
{
  public bool ShouldInject(MemberInfo member)
  {
    var propertyInfo = member as PropertyInfo;
    return propertyInfo != null
      && propertyInfo.PropertyType == typeof (ILog);
  }
}

IConstructorScorer

Ninject runs each constructor through the IConstructorScorer interface to determine which constructor it should use to instantiate the class. After scoring each constructor, Ninject chooses the one with the highest score. By adjusting the score you can affect which constructor can be chosen. For instance, to continue our scenario from the ISelector example we could instead choose to implement it like this:

public class MyScorer : StandardConstructorScorer
{
  public override int Score(IContext context, ConstructorInjectionDirective directive)
  {
    if(directive.Targets.Select(t => t.Type).Contains(typeof(DebugType)))
    {
      return Int32.MinValue;
    }
    return base.Score(context, directive);
  }
}

Ninject only allows one

IConstructorScorer
to be registered at a time.

IPlanner

IPlanningStrategy

IInjectorFactory

ICache

ICachePruner

IModuleLoader

IModuleLoaderPlugin