0

Is there a Ninject equivalent to Autofac IIndex. In other words I need to get a collections of all registered items that have the name of registration. In Autofac you would do it like so EDIT: made a change to make things more clear.

  static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Add>().Keyed<ICommand>("add");
            builder.RegisterType<Add>().Keyed<ICommand>("sub");

            builder.RegisterType<Calculator>().As<Calculator>();
            var container = builder.Build();
            var calc = container.Resolve<Calculator>();
            calc.Run();
        }

        public class Calculator 
        {

            IIndex<string, ICommand> commands;

            public Calculator(IIndex<string, ICommand> commands)
            {
                this.commands = commands;
            }

            public void Run() 
            {

                var result = true;
                do
                {
                  var cmd = Console.ReadLine().Split(' ');
                   ICommand command;
                   if (commands.TryGetValue(cmd[0], out command))
                   {
                       result = command.Do(cmd.Skip(1).ToArray());
                   }
                   else
                   {
                       Console.WriteLine("Command not found");
                   }


                } while (!result);
            }
        }

        public interface ICommand
        {
            bool Do(string[] data);
        }

        public class Add : ICommand
        {
            public bool Do(string[] data)
            {
                Console.WriteLine(double.Parse(data[0]) + double.Parse(data[1]));

                return false;
            }
        }

        public class Substract : ICommand
        {
            public bool Do(string[] data)
            {
                Console.WriteLine(double.Parse(data[0]) - double.Parse(data[1]));

                return false;
            }
        }

But I was unable to find an how to do this in Ninject.

Filip Cordas
  • 2,531
  • 1
  • 12
  • 23
  • 1
    Just for the next time you ask a question. Try not to abstract your question to much, because as we can see, the more general you ask a question, the more general the answers will be. And as I have clearly spent a lot of time to help you, its not satisfying, that much of what I have written just missed the *actual question*. So please, if what I have posted lately does answer your question, rewarding it by checking as answer would really be great. - Maybe you should rethink your issue and later ask a more detailed question of your actual usecase. Have a nice evening man. – LuckyLikey Sep 12 '17 at 15:50

2 Answers2

2

As in the link provided, you are intending to look up a value by Key.

What you are searching for is called named binding. Bindings are Identified by a Name. How ever the Problem here is, that you need to use the common injection strategies of ninject.

Bind<IDeviceState>().To<OnlineState>().Named("Online");
Bind<IDeviceState>().To<OfflineState>().Named("Offline");

public Modem([Named("Online")] IDeviceState state){
    _state = state;
}

This way, you can choose which implementation of IDeviceState will be injected by the NamedAttribute, which is added right before the injection parameter. But this doesn't fully work out for you, because you want to create IDeviceStates on the fly. Thats why you need to use the Ninject.Extensions.Factory.

This will allow you to Inject a IDeviceStateFactory.

public interface IDeviceStateFactory
{
    IDeviceState GetOnline();

    IDeviceState GetOffline();
}

Those Factorys underlie a very specific syntax, where Get-Methods will resolve a Named Binding as described above. So GetOnline() will match the Bind<IDeviceState>().To<OnlineState>().Named("Online") binding and return an instance of OnlineState.

Just tell the IoCContainer that IDeviceStateFactory is a factory. An implementation of that interface will then automatically be provided by Ninject's Proxy system.

Bind<IDeviceStateFactory>().ToFactory();

So finally this is how you could imlpement your class Modem using this technique

public class Modem : IHardwareDevice
{
    IDeviceStateFactory _stateFactory;
    IDeviceState _currentState;

    public Modem(IDeviceStateFactory stateFactory)
    {
        _stateFactory = stateFactory;
        SwitchOn();
    }

    void SwitchOn()
    {
        _currentState = _stateFactory.GetOnline();
    }
}

Using a real Key

However if you really want to use your DeviceState-Enumeration like before, you could use the approach described here.

The Binding Would look pretty much like this:

const string EnumKey = "EnumKey";

Bind<IDeviceState>().To<OnlineState>()
        .WithMetadata(EnumKey, DeviceState.Online);

Use this Method to resolve using DeviceState.Online Key

IResolutionRoot.Get<IDeviceState>(x => x.Get<DeviceState>(EnumKey) == DeviceState.Online);

But in my opinion, there is no reason to do such complicated thing. I recommend you to just forget about the whole IIndex<TKey, TSource> mechanics from Autofac and do it the ninject way.

Your Edit: preserving IIndex (Solution involving Factory Extension)

Well, as IIndex and no such thing do exist in Ninject, you could create your own implementation of it, Inheriting from IDictionary, or just define it much alike it and only defining it as follows:

public interface IIndex<TKey, TValue>
{
    bool TryGetValue(TKey key, out TValue value);

    TValue this[TKey key] { get; set; }
}

The Factory from above then gets ICommandFactory

public interface ICommandFactory
{
    ICommand GetAdd();
    ICommand GetSubtract();
}

then you only need to create an implementation of IIndex using the ICommandFactory to resolve the actual command instances.

public class CommandIndex : IIndex<string, ICommand>
{
    private readonly ICommandFactory _factory;

    public CommandIndex(ICommandFactory factory)
    {
        _factory = factory;
    }

    public bool TryGetValue(string key, out ICommand value)
    {
        switch (key)
        {
            case "Add":
                value = _factory.GetAdd();
                break;
            case "Subtract":
                value = _factory.GetSubtract();
                break;
            default:
                value = null;
                break;
        }
        return value != null;
    }

    public ICommand this[string key]
    {
        get
        {
            ICommand value;
            TryGetValue(key, out value);
            return value;
        }
        set { throw new NotSupportedException();}
    }
}

But I really need to ask you: Is this whole thing necessary? Man just use the factory. It really gets much less complicated.

Another attempt of preserving IIndex without Factory Extension

Another Attempt with propably less coding is directly using the IKernel which is propably a slightly bad practice. However you can then resolve named bindings like this:

public class NamedBindingIndex<TValue> : IIndex<string, TValue>
{
    private readonly IKernel _kernel;

    public NamedBindingIndex(IKernel kernel)
    {
        _kernel = kernel;
    }

    public bool TryGetValue(string key, out TValue value)
    {
        value = _kernel.Get<TValue>(key); // eventually catch an Exception here
        return value != null;
    }


    public TValue this[string key]
    {
        get
        {
            TValue value;
            TryGetValue(key, out value);
            return value;
        }
        set { throw new NotSupportedException(); }
    }
}

Just bind it like this

Bind<IIndex<string, ICommand>>().To<NamedBindingIndex<ICommand>>();

And then your Solution should be working pretty much as same. Another plus is, that this very last attempt doesn't need no Ninject.Extensions.Factory.

LuckyLikey
  • 3,504
  • 1
  • 31
  • 54
  • Yes this is not the same. IIndex returns key value pairs not collections – Filip Cordas Sep 12 '17 at 12:18
  • This is not a bind time condition it's a runtime condition so that I can run different methods based on keys. The example is just copy from [Autofac site](http://docs.autofac.org/en/latest/advanced/keyed-services.html) I have different use case. – Filip Cordas Sep 12 '17 at 12:24
  • @Filip The Answer does now show how to Inject different implementations depending on _**names**_ which is almost the same as keys. It's the Ninject way of doing this. – LuckyLikey Sep 12 '17 at 13:48
  • Sorry but as previously mentioned the example is just from Autofac site it's not a real use case. And the point of this is to use a string for the registration so you can execute one based on user input. Most likely I will need to write a custom binder to do this but I was wondering if ninject supported this feature. I will probably just end up extending my interface with a name property so I can select the right one at run time. – Filip Cordas Sep 12 '17 at 14:36
  • @Filip I just added a way of doing it with the Key you mentionned. - How do you mean *based on user input*. This is a whole new input to your question. However as the solution I provided is absolutely independent from any *user input*, it should be possible to solve your issue this way. Other ways just point out more clearly what you are searching for. As *Jon Skeet* describes in his [blog](https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/) it is really frustrating to give good answers, if you are trying to reach a goal which you do not even mention. – LuckyLikey Sep 12 '17 at 14:45
  • Ok I modified the example to make things more clear but this was more of a question of transferring IIndex to ninject. And is there an equivalent to it in ninject. – Filip Cordas Sep 12 '17 at 14:53
  • 1
    @FilipCordas propably what you search for is `_kernel.Get(key)` from the very last example of my answer. – LuckyLikey Sep 12 '17 at 15:29
0

For anyone else with a similar problem I was still unable to find anything similar to IIndex and this was as close as I could do.

public static class NinjectExtender
    {
        public const string Index = "INDEX";

        public static IBindingWithOrOnSyntax<TImplementation> Keyed<T, TImplementation, TKey>(this IKernel binding, TKey key) where TImplementation : T
            {
                if (!binding.CanResolve<IIndex<TKey, T>>()) 
                {
                    binding.Bind<IIndex<TKey, T>>().ToMethod(context => new Index<TKey,T>(context));
                }
                return binding.Bind<T>().To<TImplementation>().WithMetadata(Index, key);
            }

    }

    public class Index<T, M> : IIndex<T, M>
    {
        private readonly IContext context;

        public Index(IContext context)
        {
            this.context = context;
        }

        public bool TryGetValue(T key, out M value)
        {
            try
            {
                value = this[key];
                return true;
            }
            catch (KeyNotFoundException)
            {
                value = default(M);
                return false;
            }
        }

        public M this[T key]
        {
            get
            {
                try
                {
                    var service = context.Kernel.Get<M>(binding => binding.Get<T>(NinjectExtender.Index).Equals(key));

                    if (!service.Equals(default(M)))
                    {
                        return service;
                    }
                    else
                    {
                        throw new KeyNotFoundException();
                    }
                }
                catch (Exception)
                {
                    throw new KeyNotFoundException();
                }
            }
        }
    }

I am a bit unsure in if the context is handled in the same way as Autofac but I am not having issues as of now.

kernel.Keyed<ICommand, Add, string>("add");
Filip Cordas
  • 2,531
  • 1
  • 12
  • 23