Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Runtime Binding: A simple task runner with ninject?

Tags:

ninject

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?

like image 320
Twisted Avatar asked Feb 04 '11 09:02

Twisted


1 Answers

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 );
        }
    }
}
like image 64
Ruben Bartelink Avatar answered Sep 21 '22 12:09

Ruben Bartelink