Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject default contextual binding

Tags:

c#

ninject

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();
        }
    }
}
like image 338
Timothy Strimple Avatar asked May 13 '11 20:05

Timothy Strimple


2 Answers

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.
like image 178
Remo Gloor Avatar answered Sep 19 '22 18:09

Remo Gloor


You can also simply add a condition for your binding to not have a condition, like so:

kernel.Bind<IObject>().To<Object1>().When(
           x => x.ParentContext != null && !x.ParentContext.Binding.IsConditional)
          .InRequestScope();

kernel.Bind<IObject>().To<Object2>().InRequestScope()
          .Named("WCFSession");

When doing a standard Inject without a Name specified, the first binding will be used. When specifying a name, the named binding will be used. It's not the prettiest solution, but it works.

like image 30
ParaSwarm Avatar answered Sep 20 '22 18:09

ParaSwarm