Use a single factory
I would use a single factory for all validators. The single factory can in turn expose methods for retrieving validators that are appropriate, which can be called by the class that receives the injection, instead of taking the dependency directly. E.g. instead of this
public MyClass(IInjected injected) //Constructor
{
_dependency = injected;
}
...follow this pattern:
public MyClass(IInjected injected) //Constructor
{
_dependency = injected.RetrieveDependency(arguments);
}
How to retrieve the right type
The One Factory can retrieve all of the validators from the IoC container by resolving an IEnumerable<IValidationType>
. Once it has the list, it can fulfill requests to return validators by selecting from the master list, using logic that you have complete control over.
The factory can expose different methods for finding the right validators. Here are three common patterns:
The way you do it now, e.g.
RetrieveValidations("Validation1")
Passing a type argument, e.g. RetrieveValidations<ValidationType1>()
Passing some item that the type system knows is associated with the item. E.g. a validator for text might use RetrieveValidationsFor<string>()
.
I recommend the last method since it will catch many errors at compile time that would not be caught with the other two techniques.
Extending the type system to mark which validators are correct
To avoid passing around magic strings to retrieve named registrations, you can use the type system to indicate which validators go with which types. In this example I have three validators that are good for strings, ints, and DateTimes; you can follow this pattern to support objects that your business is interested in.
public interface IValidationType
{
bool Validate();
}
public interface IValidatorFor<T> : IValidationType
{
}
public class ValidationType1 : IValidatorFor<string>, IValidationType
{
public bool Validate() { return true; }
}
public class ValidationType2 :IValidatorFor<int>, IValidationType
{
public bool Validate() { return true; }
}
public class ValidationType3 :IValidatorFor<DateTime>, IValidationType
{
public bool Validate() { return true; }
}
Examples
Here is an example factory, where I show the three different ways of passing the type information to get the right validator:
public class ValidationFactory : IValidationFactory
{
private readonly IList<IValidationType> _validations;
public ValidationFactory(ILifetimeScope container)
{
_validations = container.Resolve<IEnumerable<IValidationType>>().ToList();
}
public IEnumerable<T> RetrieveValidations<T>() where T : IValidationType
{
return _validations.OfType<T>();
}
public IEnumerable<IValidationType> RetrieveValidations(string validationName)
{
return _validations.Where( v => v.GetType().FullName.Contains(validationName) ); //or some other logic depending on your problem domain
}
public IEnumerable<IValidatorFor<T>> RetrieveValidationsFor<T>()
{
return _validations.OfType<IValidatorFor<T>>();
}
}
And some test classes to show you how the injection works:
public class Example1
{
protected readonly IEnumerable<IValidationType> _validations;
public Example1(IValidationFactory factory)
{
_validations = factory.RetrieveValidations<ValidationType1>().ToList();
}
public void Run()
{
Console.WriteLine("Example validators to be used:");
foreach (var f in _validations)
{
Console.WriteLine("{0}", f.GetType().FullName);
}
}
}
public class Example2
{
protected readonly IEnumerable<IValidationType> _validations;
public Example2(IValidationFactory factory)
{
_validations = factory.RetrieveValidations("2").ToList();
}
public void Run()
{
Console.WriteLine("Example2 validators to be used:");
foreach (var f in _validations)
{
Console.WriteLine("{0}", f.GetType().FullName);
}
}
}
public class Example3
{
protected readonly IEnumerable<IValidationType> _validations;
public Example3(IValidationFactory factory)
{
_validations = factory.RetrieveValidationsFor<DateTime>().ToList();
}
public void Run()
{
Console.WriteLine("Example3 validators to be used:");
foreach (var f in _validations)
{
Console.WriteLine("{0}", f.GetType().FullName);
}
}
}
Here is a small test program:
public class Application
{
protected readonly Example1 _example1;
protected readonly Example2 _example2;
protected readonly Example3 _example3;
public Application(Example1 example1, Example2 example2, Example3 example3)
{
_example1 = example1;
_example2 = example2;
_example3 = example3;
}
public void Run()
{
_example1.Run();
_example2.Run();
_example3.Run();
}
}
public class Program
{
public static IContainer CompositionRoot()
{
var builder = new ContainerBuilder();
builder.RegisterType<ValidationType1>().As<IValidationType>();
builder.RegisterType<ValidationType2>().As<IValidationType>();
builder.RegisterType<ValidationType3>().As<IValidationType>();
builder.RegisterType<ValidationFactory>().As<IValidationFactory>();
builder.RegisterType<Example1>();
builder.RegisterType<Example2>();
builder.RegisterType<Example3>();
builder.RegisterType<Application>();
return builder.Build();
}
public static void Main()
{
CompositionRoot().Resolve<Application>().Run();
}
}
And the output:
Example validators to be used:
Example.ValidationType1
Example2 validators to be used:
Example.ValidationType2
Example3 validators to be used:
Example.ValidationType3
See the full working example on DotNetFiddle
Note: Some people might say it is "bad" for a factory to access the container. This is normally true (it leads to something which looks an awful lot like the service locator anti-pattern) but for a factory it is okay. The very purpose of the factory is to assemble the dependencies to construct objects, and as such they serve as an extension of the composition root.