Fluent Validation rules, subsets and nesting
-
09-12-2019 - |
Question
Given a validator class that looks like this
public class SomeValidator : AbstractValidator<SomeObject>
{
public SomeValidator(){
RuleSet("First",
() => {
RuleFor(so => so.SomeMember).SetValidator(new SomeMemberValidator())
});
RuleSet("Second",
() => ... Code Does Not Matter ... );
RuleSet("Third",
() => ... Code Does Not Matter ... );
}
}
And another to do the inner member validation
public class SomeMemberValidator: AbstractValidator<SomeMember>
{
public SomeValidator(){
RuleSet("Fourth",
() => {
... Code Does Not Matter ...
});
}
}
Question is, I want to run specific rulesets: "First", "Second", and "Fourth". I don't want "Third" to run.
Given the Validate method signature only takes a single ruleset argument I don't see any way to do this. There is "*", but I don't want to run all the rules.
Please help.
Solution
You can execute more than one RuleSet, but I don't think you can execute the inner RuleSet.
validator.Validate(new ValidationContext<SomeObject>(person, new PropertyChain(), new RulesetValidatorSelector("First", "Second", "Fourth")));
The other option is to investigate the source code and try to think a way of doing it. The third parameter of the ValidationContext is an interface, IValidatorSelector
, maybe you can have some luck with a custom class.
OTHER TIPS
You could use validator constructor instead of RuleSet as a workaround for this problem. Just create enum inside of validator class and then use its value when creating validator.
I this way correct rules will be activated depending on what Mode is set in constructor.
public class UserValidator : AbstractValidator<User>
{
public enum Mode
{
Create,
Edit
}
public UserValidator()
{
// Default rules...
}
public UserValidator(UserValidator.Mode mode)
: this()
{
if (mode == Mode.Edit)
{
// Rules for Edit...
RuleFor(so => so.SomeMember)
.SetValidator(
new SomeMemberValidator(SomeMemberValidator.Mode.SomeMode))
}
if (mode == Mode.Create)
{
// Rules for Create...
RuleFor(so => so.SomeMember)
.SetValidator(
new SomeMemberValidator())
}
}
}
I think it's actually more flexible method than using RuleSet.
There is only one small problem regarding FluentValidation MVC integration:
User
class can't have attribute [Validator(typeof(UserValidator))]
because UserValidator will be then created using default constructor, before you can do anything in controller method.
Validator must be created and called manually. Like that for example:
public class UserController : Controller
{
[HttpPost]
public ActionResult Create(User userData)
{
var validator = new UserValidator(UserValidator.Mode.Create);
if (ValidateWrapper(validator, userData, this.ModelState))
{
// Put userData in database...
}
else
{
// ValidateWrapper added errors from UserValidator to ModelState.
return View();
}
}
private static bool ValidateWrapper<T>(FluentValidation.AbstractValidator<T> validator, T data, ModelStateDictionary modelState)
{
var validationResult = validator.Validate(data);
if (!validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
modelState.AddModelError(error.PropertyName, error.ErrorMessage);
return false;
}
return true;
}
}