I am using FluentValidation to validate my service operations. My code looks like:
using FluentValidation;
IUserService
{
void Add(User user);
}
UserSe
If I understood it correctly, I see no problem whatsoever in doing this as long as it is abstracted as an infrastructure concern just like your repo abstracts the persistence technology.
As an example, I have created in for my projects an IObjectValidator that returns validators by object type, and a static implementation of it, so that I'm not coupled to the technology itself.
public interface IObjectValidator
{
void Validate(T instance, params string[] ruleSet);
Task ValidateAsync(T instance, params string[] ruleSet);
}
And then I implemented it with Fluent Validation just like this:
public class FluentValidationObjectValidator : IObjectValidator
{
private readonly IDependencyResolver dependencyResolver;
public FluentValidationObjectValidator(IDependencyResolver dependencyResolver)
{
this.dependencyResolver = dependencyResolver;
}
public void Validate(T instance, params string[] ruleSet)
{
var validator = this.dependencyResolver
.Resolve>();
var result = ruleSet.Length == 0
? validator.Validate(instance)
: validator.Validate(instance, ruleSet: ruleSet.Join());
if(!result.IsValid)
throw new ValidationException(MapValidationFailures(result.Errors));
}
public async Task ValidateAsync(T instance, params string[] ruleSet)
{
var validator = this.dependencyResolver
.Resolve>();
var result = ruleSet.Length == 0
? await validator.ValidateAsync(instance)
: await validator.ValidateAsync(instance, ruleSet: ruleSet.Join());
if(!result.IsValid)
throw new ValidationException(MapValidationFailures(result.Errors));
}
private static List MapValidationFailures(IEnumerable failures)
{
return failures
.Select(failure =>
new ValidationFailure(
failure.PropertyName,
failure.ErrorMessage,
failure.AttemptedValue,
failure.CustomState))
.ToList();
}
}
Please note that I have also abstracted my IOC container with an IDependencyResolver so that I can use whatever implementation I want. (using Autofac at the moment).
So here is some bonus code for autofac ;)
public class FluentValidationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
// registers type validators
builder.RegisterGenerics(typeof(IValidator<>));
// registers the Object Validator and configures the Ambient Singleton container
builder
.Register(context =>
SystemValidator.SetFactory(() => new FluentValidationObjectValidator(context.Resolve())))
.As()
.InstancePerLifetimeScope()
.AutoActivate();
}
}
The code could be missing some of my helpers and extensions, but I believe it would be more than enough to get you going.
I hope I have helped :)
EDIT:
Since some fellow coders prefer not to use the "service locator anti pattern", here is a very simple example on how to remove it and still be happy :)
The code provides a dictionary property that should be filled with all your validators by Type.
public class SimpleFluentValidationObjectValidator : IObjectValidator
{
public SimpleFluentValidationObjectValidator()
{
this.Validators = new Dictionary();
}
public Dictionary Validators { get; private set; }
public void Validate(T instance, params string[] ruleSet)
{
var validator = this.Validators[typeof(T)];
if(ruleSet.Length > 0) // no ruleset option for this example
throw new NotImplementedException();
var result = validator.Validate(instance);
if(!result.IsValid)
throw new ValidationException(MapValidationFailures(result.Errors));
}
public Task ValidateAsync(T instance, params string[] ruleSet)
{
throw new NotImplementedException();
}
private static List MapValidationFailures(IEnumerable failures)
{
return failures
.Select(failure =>
new ValidationFailure(
failure.PropertyName,
failure.ErrorMessage,
failure.AttemptedValue,
failure.CustomState))
.ToList();
}
}