I'm struggling with implementing a validator for a class, where only one property should be set.
Let's say we have the following class:
public class SomeClass
{
public DateTime SomeDate {get; set;}
public IEnumerable<int> FirstOptionalProperty {get; set;}
public IEnumerable<int> SecondOptionalProperty {get; set;}
public IEnumerable<int> ThirdOptionalProperty {get; set;}
}
This class has one mandatory property - SomeDate
. Other properties are optional and only one can be set e.g if FirstOptionalProperty
is set - SecondOptionalProperty
and ThirdOptionalProperty
should be null, if SecondOptionalProperty
is set - FirstOptionalProperty
and ThirdOptionalProperty
should be null and so forth.
In other words: if one of IEnumerable props is set - other IEnumerables should be null.
Any tips/ideas on implementing validator for such type of class? The only thing I came up with is writing chunks of When
rules, but this way of writing code is error prone and result looks ugly.
You can take advantage of the Must
overload that gives you access to the whole class object so that you can do a property validation against other properties. See FluentValidation rule for multiple properties for more details.
public class SomeClassValidator : AbstractValidator<SomeClass>
{
private const string OneOptionalPropertyMessage = "Only one of FirstOptionalProperty, SecondOptionalProperty, or ThirdOptionalProperty can be set.";
public SomeClassValidator()
{
RuleFor(x => x.FirstOptionalProperty)
.Must(OptionalPropertiesAreValid)
.WithMessage(OneOptionalPropertyMessage);
RuleFor(x => x.SecondOptionalProperty)
.Must(OptionalPropertiesAreValid)
.WithMessage(OneOptionalPropertyMessage);
RuleFor(x => x.ThirdOptionalProperty)
.Must(OptionalPropertiesAreValid)
.WithMessage(OneOptionalPropertyMessage);
}
// this "break out" method only works because all of the optional properties
// in the class are of the same type. You'll need to move the logic back
// inline in the Must if that's not the case.
private bool OptionalPropertiesAreValid(SomeClass obj, IEnumerable<int> prop)
{
// "obj" is the important parameter here - it's the class instance.
// not going to use "prop" parameter.
// if they are all null, that's fine
if (obj.FirstOptionalProperty is null &&
obj.SecondOptionalProperty is null &&
obj.ThirdOptionalProperty is null)
{
return true;
}
// else, check that exactly 1 of them is not null
return new []
{
obj.FirstOptionalProperty is not null,
obj.SecondOptionalProperty is not null,
obj.ThirdOptionalProperty is not null
}
.Count(x => x == true) == 1;
// yes, the "== true" is not needed, I think it looks better
}
}
You can tweak the check function. As this currently stands, if you set 2 or more of the optional properties, ALL of them will throw an error. That may or may not be fine for your needs.
You could also make a RuleFor
ONLY for the first optional property, rather than all of them, as all the properties will be executing the same IsValid code and return the same message, your user might just get a bit confused if they get an error message for OptionalProperty1 but they didn't supply that one.
The downside to this approach is that you need to know at compile time what all your properties are (so you can write the code for it), and you need to maintain this validator if you add/remove optional entries. This downside may or may not be important to you.
one thing that comes to my mind is to use reflection here:
SomeClass someClass = new SomeClass
{
SomeDate = DateTime.Now,
FirstOptionalProperty = new List<int>(),
//SecondOptionalProperty = new List<int>() // releasing this breakes the test
};
var info = typeof(SomeClass).GetProperties()
.SingleOrDefault(x =>
x.PropertyType != typeof(DateTime) &&
x.GetValue(someClass) != null);
basically if info
is null
then more than 1 of the optional properties was instantiated
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With