Ensuring Unique Property Value Using FluentValidation
FluentValidation is a small portable validation library with fluent interface. It makes it really easy to create validators for a specific type of object:
public class PlayerValidator : AbstractValidator<Player>
{
public PlayerValidator()
{
RuleFor(player => player.Name).NotEmpty()
.WithMessage("Name must not be empty.");
RuleFor(player => player.Name).Length(0, 100)
.WithMessage("Name must not exceed 100 characters.");
}
}
The fluent API makes validators easy to understand. It's really simple to use them for validating any instance of the supported object:
var validator = new PlayerValidator();
var result = validator.Validate(player);
if (!result.IsValid)
{
// iterate through result.Errors collection
}
You can find more information about basic usage and even more advanced scenarios in very detailed documentation. I'll rather focus on extending the library with your own custom validation, instead.
We will create a rule for validating, that a specific property of the object is unique in a collection of such objects. Such a validation can be very useful for editing a list of distinct objects, such as players in our case. Let's start with a basic validation algorithm:
public bool IsNameUnique(IEnumerable<Player> players,
Player editedPlayer, string newValue)
{
return players.All(player =>
player.Equals(editedPlayer) || player.Name != newValue);
}
We need to check every item in the collection and make sure it doesn't have the same property value as is the new property value of the edited object. Of course, we skip the check for the object we are editing. It's pretty easy to include this specific validation into our validator:
public class PlayerValidator : AbstractValidator<Player>
{
private readonly IEnumerable<Player> _players;
public PlayerValidator(IEnumerable<Player> players)
{
_players = players;
RuleFor(player => player.Name).NotEmpty()
.WithMessage("Name must not be empty.");
RuleFor(player => player.Name).Length(0, 100)
.WithMessage("Name must not exceed 100 characters.");
RuleFor(player => player.Name).Must(IsNameUnique)
.WithMessage("Name must be unique");
}
public bool IsNameUnique(Player editedPlayer, string newValue)
{
return _players.All(player =>
player.Equals(editedPlayer) || player.Name != newValue);
}
}
In such cases we can use the built-in predicate validator Must
, and supply it with the predicate. Ours is almost identical to the original validation method above except that we are keeping the collection in a class level field initialized in the validator constructor instead of passing it to the predicate every time.
Although this works just fine for single cases, you might want to create a more generic solution that you can use for validating any object, thus avoid having to reimplement a similar method in each validator. This is where things get a bit more complicated, although the process is still well documented.
It turns out we need to create a custom class, derived from PropertyValidator
. There's only a single method to implement: IsValid
. It automatically receives information about the object being validated, the property being validated and its new value. The rest of required information - in our case the collection of items - will need to be provided in a different way. Again we will inject it into the validator through the constructor. This is how a working PropertyValidator
could look like:
public class UniqueValidator<T> : PropertyValidator
where T: class
{
private readonly IEnumerable<T> _items;
public UniqueValidator(IEnumerable<T> items)
: base("{PropertyName} must be unique")
{
_items = items;
}
protected override bool IsValid(PropertyValidatorContext context)
{
var editedItem = context.Instance as T;
var newValue = context.PropertyValue as string;
var property = typeof(T).GetTypeInfo().GetDeclaredProperty(context.PropertyName);
return _items.All(item =>
item.Equals(editedItem) || property.GetValue(item).ToString() != newValue);
}
}
The only difference worth pointing out, is the usage of reflection in our IsValid
method. This is required to get the value of the property from other objects of the same type, since we only have its name.
Now that we have a custom PropertyValidator
, we can rewrite the PlayerValidator
without including the actual validation logic for ensuring uniqueness:
public class PlayerValidator : AbstractValidator<Player>
{
public PlayerValidator(IEnumerable<Player> players)
{
_players = players;
RuleFor(player => player.Name).NotEmpty()
.WithMessage("Name must not be empty.");
RuleFor(player => player.Name).Length(0, 100)
.WithMessage("Name must not exceed 100 characters.");
RuleFor(player => player.Name).SetValidator(new UniqueValidator<Player>(players))
.WithMessage("Name must be unique");
}
}
Still, the usage of UniqueValidator
is somewhat more complex than the built-in validators. We can fix that as well, by writing a corresponding extension method, enabling fluent usage:
public static IRuleBuilderOptions<TItem, TProperty> IsUnique<TItem, TProperty>(
this IRuleBuilder<TItem, TProperty> ruleBuilder, IEnumerable<TItem> items)
where TItem : class
{
return ruleBuilder.SetValidator(new UniqueValidator<TItem>(items));
}
Although the method body is pretty straightforward, the method signature could certainly use some additional explanation. The most important part to understand are the generic type parameters. The first one is the type of the object being validated and the second one the type of the property. IRuleBuilderOptions
and IRuleBuilder
interfaces are the key to the fluent chaining of calls, while the items collection is our own additional parameter required by UniqueValidator
.
Unique validation in our PlayerValidator
now really looks nice and fluent. You could easily think IsUnique
validator was actually already built-in.
public class PlayerValidator : AbstractValidator<Player>
{
public PlayerValidator(IEnumerable<Player> players)
{
RuleFor(player => player.Name).NotEmpty()
.WithMessage("Name must not be empty.");
RuleFor(player => player.Name).Length(0, 100)
.WithMessage("Name must not exceed 100 characters.");
RuleFor(player => player.Name).IsUnique(players)
.WithMessage("Name must be unique");
}
}
FluentValidation might seem a little scary to get into at first, but there's really very little to learn, if built-in validators are all you need. Even writing custom validators is not as difficult as it might seem and is definitely worth getting into if you need additional validations and want to reuse them in different objects or even projects. In any case; take a closer look at this library, before trying to come up with your own validation framework.