Ninject default contextual binding

Timothy Strimple picture Timothy Strimple · May 13, 2011 · Viewed 9k times · Source

I have an interface with a few different concrete implementations. I am trying to give Ninject a default to use and only use the other implementation if a name matches. For instance, I have the following bindings.

Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");

What I would like is if the Named section doesn't match, to use the DefaultSomething implementation. When I pass in the explicitly bound guid, it works fine. When I pass in any other guid I get the "No matching bindings are available" exception.

Bind<ISomething>().To<OtherSomething>().Named("55abd8b8-097f-4e1c-8d32-95cc97910604");
Bind<ISomething>().To<DefaultSomething>()

Bind<ISomething>().To<DefaultSomething>()
Bind<ISomething>().To<OtherSomething>().When(ctx => ctx.Service != null && ctx.Service.Name == "55abd8b8-097f-4e1c-8d32-95cc97910604");

I have also tried using .When to check the binding and I have tried reversing the order like below however I am never able to bind unless I pass in the Guid that is explicitly named.

This article seems to indicate that default bindings work, so I must be doing something wrong. Any suggestions?


Edit: Here is a complete example showing the problem I am trying to solve. The desired behavior is for kernel.Get<INumber>("Three").Write() to return "Unknown Number"

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;

namespace NinjectTest
{
    interface INumber
    {
        string Write();
    }

    class UnknownNumber : INumber
    {
        public string Write()
        {
            return "Unknown Number";
        }
    }

    class One : INumber
    {
        public string Write()
        {
            return "1 = One";
        }
    }

    class Two : INumber
    {
        public string Write()
        {
            return "2 = Two";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            StandardKernel kernel = new StandardKernel();
            kernel.Bind<INumber>().To<UnknownNumber>();
            kernel.Bind<INumber>().To<One>().Named("One");
            kernel.Bind<INumber>().To<Two>().Named("Two");

            Console.WriteLine(kernel.Get<INumber>("One").Write());
            Console.WriteLine(kernel.Get<INumber>("Two").Write());
            Console.WriteLine(kernel.Get<INumber>("Three").Write());

            Console.ReadLine();
        }
    }
}

Answer

Remo Gloor picture Remo Gloor · May 14, 2011

You completely missunderstood named bindings:

Giving a binding a name is NOT a condition. You will still get all of them when requesting them without a constraint. Adding a name changes absolutely nothing on its own.

Requesting an instance using a name adds the constraint:

only bindings whose name matches the given one shall be returned

In your case, you gave me an instance whose binding's name is "three". And you expect it to return UnknownNumber, which does not even have a name.

This can be achieved by either

  1. passing a parameter and adding conditions to the bindings that check if the parameter matches, or
  2. passing a constraint that fits the name or the unnamed instance and declare the unnamed one implicit.

Option 1:

public class CustomerIdParameter : Parameter
{
    public CustomerIdParameter(string id) : base("CustomerId", (object)null, false)
    {
        this.Id = id;
    }
    public string Id { get; private set; }
}

kernel.Bind<ISomething>().To<Default>();
kernel.Bind<ISomething>().To<Other>()
      .When(r => r.Parameters.OfType<CustomerIdParameter>()
                             .Single().Id == "SomeName");

kernel.Get<IWeapon>(new CustomerIdParameter("SomeName")).ShouldBeInstanceOf<Sword>();

I leave it up to you to write the extension methods to make the definition and resolve easier.

Option 2:

Bind<ISomething>().To<Default>().Binding.IsImplicit = true;
Bind<ISomething>().To<Other>().Named("SomeName")

public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
{
    return kernel.Get<T>(m => m.Name == null || m.Name == name);
}

But honestly I think what you want to do doesn't seem to be a proper design:

  1. Keep your access to the kernel to an absolute minimum. What you're doing here is a ServiceLocator-like usage of Ninject.
  2. If no binding is available for an expected instance, I'd rather expect an exception than using a default instance because this is a bug.