6

I'm writing a simple task runner application.

I have a bunch of classes that Implement an ITask interface :

public interface ITask
{
   void Run();
}

I'm writing a simple console app that creates instances of ITasks and then calls Run() on each of them. The task implementations use constructor injection so I would like to use ninject.

I would like to be able to specifiy at runtime which tasks to run and therefore which implementation(s) of ITask to activate.

I was thinking that I could put the concrete types into my app.config then at run time I could get ninject to build me an ITask array from it. Failing this I could specify the tasks I want to run on the command line.

To me this sounds like a fairly straight forward case for ninject but I've been unable to find how to get ninject to bind from configuration or a string.

Can anybody point me in the right direction?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Twisted
  • 2,939
  • 4
  • 32
  • 54

2 Answers2

6

There are extensions for ninject that handle things like xml configuration.

I'd be careful about mixing up the programming bits from the config a la Spring XML config though - there's no need to move to XML config just to allow people to configure things in a .config file. I suggest reading an XML config section loader that serializes in a class that expresses that at a higher level instead.

You'd use the metadata mechanism on your Binding registrations and then indicate how to filter the set of tasks based on that.

e.g., repurposing @Ian Davis's answer (go read it and upvote it now!):

string metaDataKey = "key";
kernel.Bind<IWeapon>().To<Shuriken>().WithMetadata(metaDataKey, true);
kernel.Bind<IWeapon>().To<Sword>().WithMetadata(metaDataKey, false);
kernel.Bind<IWeapon>().To<Knife>();

bool? theOneIWant = null; // or true or false - i.e., the distillation of what your config says

Func<IMetadata> myConfigSaysIWantOneLikeThatPredicate= metadata => 
    metadata.Has(metaDataKey) == theOneIWant != null
    && metadata.Get<bool>(metaDataKey) == theOneIWant.Value

var weapons = kernel.Get<IEnumerable<IWeapon>>( myConfigSaysIWantOneLikeThatPredicate );
// the above will generate a single item given the bindings above, but you get the picture - this generates an arbitrary length list

foreach(var weapon in weapons)
    weapon.Fire();

If all you're looking for is to be able to name them, there's a shorthand replacement for WithMetadata called Named() and an overload for .Get<T>() with a name string parameter, which would let you achieve @dave thieben's simplicity without your invocations being hardwired to Type names.

EDIT: Sample, see comments:

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

namespace ninjectmess
{
    public class Class1
    {

Some junk classes

        public interface ITask
        {
        }

        public class Aasdsdaadsdsa : ITask
        {
        }
        public class Bdsadsadasdsadsadsa : ITask
        {
        }
        public class Cddsadasdsadasdas : ITask
        {
        }

the actual test

        [Fact]
        public void TestMethod()
        {
            var k = new StandardKernel();
            k.Bind<ITask>().To<Aasdsdaadsdsa>().Named( "A" );
            k.Bind<ITask>().To<Bdsadsadasdsadsadsa>().Named( "B" );
            k.Bind<ITask>().To<Cddsadasdsadasdas>().Named( "C" );

            var wanted = new string[] { "A", "C" };

            var tasks = k
                .GetAll<ITask>( metadata => wanted.Contains( metadata.Name ))
                .ToList();
            Assert.Equal( 2, tasks.Count );
            tasks.ForEach( Console.WriteLine );
        }
    }
}
Community
  • 1
  • 1
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • This looks like the kind of thing I'm looking for. I've got to admit the meta data looks a bit confusing, I'm not sure how I would specify, for example, that I want a sword and a knife. I'll have a play in the debugger :-) – Twisted Feb 08 '11 at 10:56
  • 1
    @Twisted: I suggest looking in the Ninject tests - they are very readable. For all the fancy terminology, all the metadata is is a Dictionary which is stored alongside the Binding in the list the Kernel maintains. `.WithMetadata` stashes it, and `Get<>()` gets passed a predicate that checks the stuff it wants is in the Dictionary. The `.Named()` shortcut and the name based overloads are just sugar to avoid having to put in a key. Bottom line: go look at the tests - they'll serve as excellent clear examples. – Ruben Bartelink Feb 08 '11 at 11:02
  • 1
    @Twisted and a +1 on your Q belatedly - it was worthy of it in the first instance and it;s great to see followups with answerers! – Ruben Bartelink Feb 08 '11 at 14:10
  • @Twisted:if you want a sword and a knife, you either ask for sharp things and the predicate has 2 parts or you put on a sharp marker on both the knife and sword and look for a sharp marker. The metadata is the markers. The param to Get is the predicate. The ninject source has the samples in the tests. The key is that you ask Get for a List or IEnumerable so it can resolve >1 items in a batch – Ruben Bartelink Feb 08 '11 at 20:42
  • I cannot tag / group my tasks in that way. I have to run an arbitrary list of tasks supplied by the user. – Twisted Feb 09 '11 at 17:53
  • heres where I'm at: kernel.Bind().To().Named("Sneak"); kernel.Bind().To().Named("Duel"); kernel.Bind().To().Named("Brawl"); var commands = new [] {"Sneak","Brawl"} I'd like to : weapons = kernel.Get>(commands); foreach (IWeapon weapon in weapons) weapon.Fire(); But I have to: foreach (string command in commands) { weapon = kernel.Get(command); weapon.Fire(); } – Twisted Feb 09 '11 at 17:59
  • I hope you can read that last comment! It kills all my line feeds! – Twisted Feb 09 '11 at 18:01
  • @Twisted: There's an overload of Get that'll let you say `weapons=kernel.Get>( binding=>commands.Contains(binding.Metadata.Name))`. Not sure if you need to create a Request, but if you did, it'd be easy to wrap it up as an extension. If you dont have it in 10 hrs, I'll be sitting on intellisense and will put in the real incantation then. – Ruben Bartelink Feb 09 '11 at 22:00
  • @Twisted: BTW good way to do followups like your example is as edits to your original quesitn, and then just stick a quick comment pointing it out. – Ruben Bartelink Feb 09 '11 at 22:00
  • I tried this but it just throws an exception. If I breakpoint it then the I can see that binding.Name is null and I cannot find anything in the binding structure that would be of use: var taskList = kernel.Get>(binding => args.Contains(binding.Name)); – Twisted Feb 10 '11 at 14:06
  • @Twisted: Sorry I misled you. Resolving `IEnumerable` works when injecting into other things being resolved. When doing a top-level resolve, you use `GetAll` instead of `Get` to get what you want - see the sample I edited into the answer. The other diff is that GetAll returns a IEnumerable, which you'll quickly want to materializa with a `ToList()` if you're going to enumerate it >1 times. Clearly @dave thieben's answer is the winner unless there's something very interesting you havent told us about your real life case! (public nuget repo versions of ninject and xunit) – Ruben Bartelink Feb 10 '11 at 16:09
  • @Johannes Rudolph: I'd have used SubSpec for the sample if there was a nuget package for it - just need to stick it up on teamcity.codebetter and add a package def! – Ruben Bartelink Feb 10 '11 at 16:20
3

I like Ruben's answer, but it could be as simple as using the Type names of the tasks you want on the command line:

public static void Main( string[] args )
{
    var kernel = new StandardKernel();
    var tasks = new List<ITask>();
    foreach(var taskName in args)
    {
        tasks.Add( kernel.Get( Type.GetType( taskName ) ) );
    }

    doSomething(tasks);
}
Dave Thieben
  • 5,388
  • 2
  • 28
  • 38
  • Nice, but my upvote button's broken too :P Yes, my answer is definitely over-complete - I wouldnt do anything as comple as I described if the scenario is as straightforward as tthe OP puts it. +1 – Ruben Bartelink Feb 04 '11 at 22:53
  • Thanks for this. It hadn't occured to me to ask ninject for the implemention instead of ITask. I'm going to try out rubens answer first as I would like to understand the ninject .With.... methods better. +1 – Twisted Feb 08 '11 at 11:01