Skip to content

Building an IBindingResolver Component

Joe Wasson edited this page Mar 4, 2012 · 7 revisions

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. Given a type it returns a collection of bindings that can potentially satisfy that type. The StandardBindingResolver simply looks up values in the supplied dictionary:

/// <summary>
/// Resolves bindings that have been registered directly for the service.
/// </summary>
public class StandardBindingResolver : NinjectComponent, IBindingResolver
{
    /// <summary>
    /// Returns any bindings from the specified collection that match the specified service.
    /// </summary>
    /// <param name="bindings">The multimap of all registered bindings.</param>
    /// <param name="service">The service in question.</param>
    /// <returns>The series of matching bindings.</returns>
    public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, Type service)
    {
        return bindings[service].ToEnumerable();
    }
}

By creating additional binding resolvers you can affect how Ninject deals with those bindings. For instance, the OpenGenericBindingResolver that is included in StandardKernel adds support for a binding where the generic parameters aren’t specified: kernel.Bind(typeof(IRepository<>)).To(typeof(InMemoryRepository<>)

Let’s take a look at the source:

/// <summary>
/// Returns any bindings from the specified collection that match the specified service.
/// </summary>
public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, Type service)
{
    if (!service.IsGenericType || !bindings.ContainsKey(service.GetGenericTypeDefinition()))
        return Enumerable.Empty<IBinding>();

    return bindings[service.GetGenericTypeDefinition()].ToEnumerable();
}

The first if statement simply checks to see if the class can handle the type of binding, if not it returns an empty enumerable (returning null might work, but I would not tempt the fates).

The meat of this method merely backs the generic type out to its open-generic form and looks for a matching binding and returns the result (that may be empty). Support for instantiating the open generic lives in StandardProvider.

Creating Our Own Resolver

Let’s build a binding resolver that can deal with covariance. Covariance is the ability for a generic type to stand in for a generic type with a more general type argument. For instance, with IEnumerable<out T>, IEnumerable<Ninja> can be substituted for IEnumerable<IWarrior> because all Ninjas are IWarriors and T has been marked for covariance with the out keyword.

public class CovariantBindingResolver : NinjectComponent, IBindingResolver
{
    /// <summary>
    /// Returns any bindings from the specified collection that match the specified service.
    /// </summary>
    public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, Type service)
    {
        if (service.IsGenericType)
        {
            var genericType = service.GetGenericTypeDefinition();
            var genericArguments = genericType.GetGenericArguments();
            if (genericArguments.Count() == 1 
             && genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Covariant))
            {
                var argument = service.GetGenericArguments().Single();
                    return bindings.Where(kvp => kvp.Key.IsGenericType
                                              && kvp.Key.GetGenericTypeDefinition().Equals(genericType)
                                              && argument.IsAssignableFrom(kvp.Key.GetGenericArguments().Single()))
                               .SelectMany(kvp => kvp.Value);
            }
        }

        return Enumerable.Empty<IBinding>();
    }
}

Note: I’m using the .NET 4.0 HasFlag() method for brevity. Replace with your choice of implementation for .NET 3.5 or earlier.

The implementation of this is pretty straight forward, we make sure the type is generic and has only one parameter (this can be modified to support any number of parameters). We also check to make sure that parameter is covariant. Once that has been established we walk all of the elements in the binding map looking for keys (types) that satisfy our requirements: same generic type definition and its type parameter is a subclass of the one we’re looking for.

The last thing we need to do is hook it up. You can do this by sub-classing StandardKernel and overriding AddComponents or by adding the component where you build up your kernel.

kernel.Components.Add<IBindingResolver, CovariantBindingResolver>();

As IBindingResolver components are intended to deal with the binding map and don’t have any other insight into the request there aren’t a whole lot of reasons you’d build one.

Back to component extension points.