问题
Can I add more than one validator to an object? For example:
public interface IFoo
{
int Id { get; set; }
string Name { get; set; }
}
public interface IBar
{
string Stuff { get; set; }
}
public class FooValidator : AbstractValidator<IFoo>
{
public FooValidator ()
{
RuleFor(x => x.Id).NotEmpty().GreaterThan(0);
}
}
public class BarValidator : AbstractValidator<IBar>
{
public BarValidator()
{
RuleFor(x => x.Stuff).Length(5, 30);
}
}
public class FooBar : IFoo, IBar
{
public int Id { get; set; }
public string Name { get; set; }
public string Stuff { get; set; }
}
public class FooBarValidator : AbstractValidator<FooBar>
{
public FooBarValidator()
{
RuleFor(x => x)
.SetValidator(new FooValidator())
.SetValidator(new BarValidator());
}
}
Running the test.
FooBarValidator validator = new FooBarValidator();
validator.ShouldHaveValidationErrorFor(x => x.Id, 0);
I get an InvalidOperationException
:
Property name could not be automatically determined for expression x => x. Please specify either a custom property name by calling 'WithName'.
Is there any way to implement this or am I trying to use FluentValidation in a way that it's not meant to be used?
回答1:
RuleFor is trying to create a property-level rule. You can additionally use the AddRule function to add a general-purpose rule.
Using this, I created a composite rule proof of concept. It takes in a set of other validators and runs them. The yield break
code came straight from FluentValidator
's DelegateValidator
. I wasn't sure what to do with it so I grabbed that from the source. I didn't trace its full purpose, but everything seems to work as is :)
Code
public interface IFoo
{
int Id { get; set; }
string Name { get; set; }
}
public interface IBar
{
string Stuff { get; set; }
}
public class FooValidator : AbstractValidator<IFoo>
{
public FooValidator()
{
RuleFor(x => x.Id).NotEmpty().GreaterThan(0);
}
}
public class BarValidator : AbstractValidator<IBar>
{
public BarValidator()
{
RuleFor(x => x.Stuff).Length(5, 30);
}
}
public class FooBar : IFoo, IBar
{
public int Id { get; set; }
public string Name { get; set; }
public string Stuff { get; set; }
}
public class CompositeValidatorRule : IValidationRule
{
private IValidator[] _validators;
public CompositeValidatorRule(params IValidator[] validators)
{
_validators = validators;
}
#region IValidationRule Members
public string RuleSet
{
get; set;
}
public IEnumerable<ServiceStack.FluentValidation.Results.ValidationFailure> Validate(ValidationContext context)
{
var ret = new List<ServiceStack.FluentValidation.Results.ValidationFailure>();
foreach(var v in _validators)
{
ret.AddRange(v.Validate(context).Errors);
}
return ret;
}
public IEnumerable<ServiceStack.FluentValidation.Validators.IPropertyValidator> Validators
{
get { yield break; }
}
#endregion
}
public class FooBarValidator : AbstractValidator<FooBar>
{
public FooBarValidator()
{
AddRule(new CompositeValidatorRule(new FooValidator(), new BarValidator()));
}
}
Base Test Case:
[TestMethod]
public void TestValidator()
{
FooBarValidator validator = new FooBarValidator();
var result = validator.Validate(new FooBar());
}
I hope this helps.
回答2:
Another possibility would be to override Validate:
public override ValidationResult Validate(ValidationContext<FooBar> context)
{
var fooResult = new FooValidator().Validate(context.InstanceToValidate);
var barResult = new BarValidator().Validate(context.InstanceToValidate);
var errors = new List<ValidationFailure>();
errors.AddRange(fooResult.Errors);
errors.AddRange(barResult.Errors);
return new ValidationResult(errors);
}
回答3:
You could use RuleSets to apply different types of validation if that helps with what you are trying to do:
FluentValidation RuleSets
来源:https://stackoverflow.com/questions/13198471/fluentvalidation-multiple-validators