3

Validation Rule Contract:

public interface IValidationRule
{
    bool IsValid();
}

Concrete Validation Rule:

public class MyClass : IValidationRule
{
    public bool IsValid()
    {
        return true;
    }
}

Composite:

public class ValidationRuleComposite : IValidationRule
{
    private readonly IEnumerable<IValidationRule> _validationRules;

    public ValidationRuleComposite(IEnumerable<IValidationRule> validationRules)
    {
        _validationRules = validationRules;
    }

    public bool IsValid()
    {
        return _validationRules.All(x => x.IsValid());
    }
}

When I ask the containter for IValidationRule I want to get ValidationRuleComposite. If I ask the container for a list of IValidationRule I want to get all implementations of IValidationRule except of the ValidationRuleComposite.

How can I achieve this with Ninject?

Rookian
  • 19,841
  • 28
  • 110
  • 180
  • Well, the problem would be asking for a single IValidationRule and expecting a specific one (the ValidationRuleComposite class). How does that make sense? Why not ask for the ValidationRuleComposite class in particular (or an interface that class has)? The rest of the problem should be trivial. What you need to remember is remove the ValidationRuleComposite from the list of IValidationRule's, or you'll have a "circular reference" situation. – Steen Tøttrup Feb 26 '15 at 10:03

5 Answers5

5

First you want to set up the bindings for the IEnumerable<IValidationRule> that will be injected into the composite. You can just bind them individually:

// Bind all the individual rules for injection into the composite
kernel.Bind<IValidationRule>().To<MyClass>().WhenInjectedInto<ValidationRuleComposite>();
kernel.Bind<IValidationRule>().To<RuleTwo>().WhenInjectedInto<ValidationRuleComposite>();

Or you can also setup the IEnumerable fairly easy with the convention binding extensions, so that you don't have to add a separate binding for each individual concrete rule. Just be sure to add the Exlcuding clause for the composite class like so:

using Ninject.Extensions.Conventions;

// Bind all the non-composite IValidationRules for injection into ValidationRuleComposite
kernel.Bind(x => x.FromAssemblyContaining(typeof(ValidationRuleComposite))
    .SelectAllClasses()
    .InheritedFrom<IValidationRule>()
    .Excluding<ValidationRuleComposite>()
    .BindAllInterfaces()
    .Configure(c => c.WhenInjectedInto<ValidationRuleComposite>()));

In my example the composite and the rest of the concretes are in the same assembly, but obviously you can vary your convention binding if they're somewhere else.

Finally, we need to set up the binding so that everywhere else an IValidationRule is request, Ninject provides the composite. There doesn't seem to be an elegant method existing for this, so I wrote my own When clause to avoid the cyclical injection:

// Now bind the composite to the interface for everywhere except itself
kernel.Bind<IValidationRule>().To<ValidationRuleComposite>()
    .When(x => x.Target == null
          || x.Target.Member.ReflectedType != typeof(ValidationRuleComposite));
Soldarnal
  • 7,558
  • 9
  • 47
  • 65
  • Sorry, I can't see the answer to the second part of the question "If I ask the container for a list of IValidationRule I want to get all implementations of IValidationRule except of the ValidationRuleComposite" outside the constructor of ValidationRuleComposite (that is the first part of the question: "When I ask the containter for IValidationRule I want to get ValidationRuleComposite"). Thank you very much in advance for your help! –  Mar 08 '15 at 16:16
  • Could you please show me the code to get the ValidationRuleComposite in your example? I get an "Error activating IValidationRule" if I do "kernel.Get()" while "kernel.Get()" obviously works but it would create a dependency (using the concrete class instead of its interface). The simpler "kernel.Bind().To();" seems to make the difference: I can get "kernel.Get()" after that.. Thanks for clarifying –  Mar 08 '15 at 17:18
  • There is a problem with the When expression. If the Target is null you need to return true. See my answer. – Rookian Mar 08 '15 at 20:01
1

Here I'm assuming that you want all the validation rules and not a partial list of them, as per the more generic pattern. I would slightly change the Composition class so that you can do a

kernel.Get<IValidationRuleComposite>()

and a

kernel.GetAll<IValidationRule>()

A simple example follows. The interfaces

public interface IValidationRule
{
    bool IsValid();
}
public interface IValidationRuleComposite : IValidationRule
{
    void ValidationRuleCompose(List<IValidationRule> validationRules);
}

and the rules

public class MyClass1 : IValidationRule
{
    public bool IsValid()
    {
        Debug.WriteLine("Valid 1");
        return true;
    }
}
public class MyClass2 : IValidationRule
{
    public bool IsValid()
    {
        Debug.WriteLine("Valid 2");
        return false;
    }
}

The composite rule

public class ValidationRuleComposite : IValidationRuleComposite
{

private List<IValidationRule> _validationRules;
public void ValidationRuleCompose(List<IValidationRule> validationRules)
{
    _validationRules = _validationRules.Union(validationRules).ToList();
}
public ValidationRuleComposite()
{
    _validationRules = new List<IValidationRule>();
}
public bool IsValid()
{
    Debug.WriteLine("Composite Valid");
    return _validationRules.All(x => x.IsValid());

}

}

and a main

        StandardKernel kernel = new StandardKernel();
        kernel.Bind<IValidationRule>().To<MyClass1>();
        kernel.Bind<IValidationRule>().To<MyClass2>();
        kernel.Bind<IValidationRuleComposite>().To<ValidationRuleComposite>();

        IValidationRuleComposite try1 = kernel.Get<IValidationRuleComposite>();

        IEnumerable<IValidationRule> rules = kernel.GetAll<IValidationRule>();
        foreach(IValidationRule trycomp in rules)
            { Debug.WriteLine("trycomp: " + trycomp.GetType().ToString()); trycomp.IsValid(); };

        try1.ValidationRuleCompose(rules.ToList());
        Console.WriteLine("{0}",try1.IsValid());
        Debug.WriteLine("try1: " + try1.GetType().ToString());

EDIT

Equivalent alternative, preserving your composite constructor

public interface IValidationRuleCompositeConstr : IValidationRule
{

}
public class ValidationRuleCompositeOriginal : IValidationRuleCompositeConstr
{
    private readonly IEnumerable<IValidationRule> _validationRules;

    public ValidationRuleCompositeOriginal(IEnumerable<IValidationRule> validationRules)
    {
        _validationRules = validationRules;
    }

    public bool IsValid()
    {
        return _validationRules.All(x => x.IsValid());
    }
}

with corresponding usage:

    StandardKernel kernel = new StandardKernel();
    kernel.Bind<IValidationRule>().To<MyClass1>();
    kernel.Bind<IValidationRule>().To<MyClass2>();
    kernel.Bind<IValidationRuleCompositeConstr>().To<ValidationRuleCompositeOriginal>();

    IEnumerable<IValidationRule> rules = kernel.GetAll<IValidationRule>();
    Ninject.Parameters.ConstructorArgument therules = new Ninject.Parameters.ConstructorArgument("therules", rules);
        IValidationRuleCompositeConstr try2 = kernel.Get<IValidationRuleCompositeConstr>(therules);
        Debug.WriteLine("Second Class");
        Debug.WriteLine (string.Format("{0}",try2.IsValid()));
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
1

With the help of Soldarnal I came to the following solution:

public static class KernelExtensions
{
    public static void BindComposite<TComposite, TCompositeElement>(this StandardKernel container) where TComposite : TCompositeElement
    {
        container.Bind(x => x.FromAssemblyContaining(typeof(TComposite))
            .SelectAllClasses()
            .InheritedFrom<TCompositeElement>()
            .Excluding<TComposite>()
            .BindAllInterfaces()
            .Configure(c => c.WhenInjectedInto<TComposite>()));

        container.Bind<TCompositeElement>().To<TComposite>()
          .When(IsNotCompositeTarget<TComposite>);
    }

    private static bool IsNotCompositeTarget<TComposite>(IRequest x)
    {
        if (x.Target == null)
            return true;
        return x.Target.Member.ReflectedType != typeof(TComposite);
    }
}

Usage:

var container = new StandardKernel();
container.BindComposite<ValidationRuleComposite, IValidationRule>();
Rookian
  • 19,841
  • 28
  • 110
  • 180
0

I don't know how you could do that directly with Ninject, but you could use Ninject to create a class which then creates your validation rules.

public class ValidationRuleFactory : IValidationRuleFactory
{
    public IValidationRule CreateComposite()
    {
        var rules = CreateRules();
        return new ValidationRuleComposite(rules);
    }

    private IEnumerable<IValidationRule> CreateRules()
    {
        //return all other rules here.
        //I would hard code them and add new ones here as they are created.
        //If you don't want to do that you could use reflection.
    }
}

as this class doesn't hold any state you can then create it with singleton scope.

kernel.Bind<IValidationRuleFactory>().To<ValidationRuleFactory>().InSingletonScope();

Then you inject this class and use it to create your composite

public class MyClass()
{
    private readonly IValidationRuleFactory _validationRuleFactory;

    public MyClass(IValidationRuleFactory validationRuleFactory)
    {
        _validationRuleFactory = validationRuleFactory;
    }

    public bool CheckValid()
    {
        var composite = _validationRuleFactory.CreateComposite();
        return composite.IsValid();
    }
}
Andy Nichols
  • 2,952
  • 2
  • 20
  • 36
0

You wire up your concrete instances of ValidationRule in Ninject, like this.

this.Kernel.Bind<ValidationRule1>().ToSelf();
this.Kernel.Bind<ValidationRule2>().ToSelf();
this.Kernel.Bind<IValidationRule>().To<ValidationRuleComposite>()
    .WithConstructorArgument("validationRules", 
        new IValidationRule[] { 
            this.Kernel.Get<ValidationRule1>(), 
            this.Kernel.Get<ValidationRule2>() 
        });

Now, whenever you have a service that takes an IValidationRule in its constructor, you will get the ValidationRuleComposite concrete type with both ValidationRule1 and ValidationRule2 injected.

As far as I know, Ninject doesn't play nice when it comes to injecting multiple instances of the same type. In this case, we avoid doing that so resolving IValidationRule always results in the composite type.

However, you could build your own scanning convention using Reflection that automatically finds all of the types, excludes any that have the suffix "Composite" in the name, then loops through the types to first bind them to self and then create an array of instances to inject. Have a look at this example of a custom scanning implementation, and its usage.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • _As far as I know, Ninject doesn't play nice when it comes to injecting multiple instances of the same type_ How so @NightOwl888? Ninject has no problem resolving `public SomeConstructor(IEnumerable items) {}` – Steen Tøttrup Feb 26 '15 at 10:07