问题
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?
回答1:
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));
回答2:
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>();
回答3:
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()));
回答4:
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();
}
}
回答5:
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.
来源:https://stackoverflow.com/questions/28692658/how-to-use-composite-design-pattern-with-ninject