Unique Value Validation

Apr 24, 2012 at 8:53 AM
Edited Apr 24, 2012 at 8:57 AM

I want to check for a unique username on a user entity.

There are two situations that need to be accounted for when checking for a unique value, being a new record or an existing record. In the case of a new record if a search for the value returns a result then the value is already in use. If, on the other hand, the record is benig editied then if a search for the value produces result they must be checked to ensure that the current record is excluded from the results.

So, to create a fluent validator for a unique username, the id of the user entity must be passed to the validation code as well as the username.

I have achieved this with the following rule in my validator:

this
    .RuleFor(u => u.UserName)
    .NotEmpty()
    .SetValidator(new HasUniqueUserNameValidator<UserEditViewModel>())
    .WithMessage("A user with this username already exists.");

and written a suitable property validator since the user untity is passed to the property validator as the context parameter:

 

protected override bool IsValid(PropertyValidatorContext context)

 

My question is thus; is there a way of carrying out such a validation without having to create a separate property validator?

What I want to be able to do is:

this
    .RuleFor(u => u.UserName)
    .NotEmpty()
    .Must(HaveUniqueUserName)
    .WithMessage("A user with this username already exists.");

...
public bool HaveUniqueUserName(string userName)
{...

For this to work the user entity must be visible to the HaveUniqueUserName method.

Any suggestions?

Coordinator
Apr 28, 2012 at 11:10 AM

Yes, this is fully supported. You need to use the second overload for Must which takes a delegate including the object being validated as the first parameter. Check out this page on the documentation for details: http://fluentvalidation.codeplex.com/wikipage?title=Validators&referringTitle=Documentation&ANCHOR#Predicate

Apr 28, 2012 at 12:30 PM

Ah, that's where it is, thanks. I didn't think you would have missed this but I couldn't find it for looking too hard :)

May 26, 2013 at 11:51 PM
How was your PropertyValidator code?

I'm trying to do this right now!

Create a PropertyValidator
public class UniqueValidator<T> : PropertyValidator
    where T: class, IEntity
{
    public UniqueValidator(IRepository<T> repository)
        : base("{PropertyName} já existe!")
    { }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        if (context.PropertyValue == null || string.IsNullOrWhiteSpace(context.PropertyValue.ToString()))
            return true;

        // If {HowToGetId?} > 0, is edit
            // return repository.All().Any(p => p.Id != {HowToGetId?} && p.{HowToGetPropertyByExpression?} == nome)
        // return repository.All().Any(p => p.{HowToGetPropertyByExpression?} == nome)
    }
}
Then RuleBuilderExtensions
public static IRuleBuilderOptions<T, TElement> MustBeUnique<T, TElement, TEntity>(this IRuleBuilder<T, TElement> ruleBuilder, IRepository<TEntity> repository)
    where TEntity : class, IEntity
{
    return ruleBuilder.SetValidator(new UniqueValidator<TEntity>(repository));
}
And usage:
public class RegionalValidator : AbstractValidator<RegionalViewModel>
{
    public RegionalValidator(IRepository<Regional> repositorio)
    {
        RuleFor(p => p.Nome)
            .MustBeValidName()
            .MustBeUnique(repositorio); // How to pass Id of object?
    }
}
Basically the question is:

In PropertyValidation, how to get current property to use in Linq Expression?
How to pass (or get) Id of object?
May 29, 2013 at 12:13 PM
Jeremy provided the answer by posting the following link'

http://fluentvalidation.codeplex.com/wikipage?title=Validators&referringTitle=Documentation&ANCHOR#Predicate

If you read the SECOND section of this part of the documentation you will see;

Note that there is an additional overload for Must that also accepts an instance of the parent object being validated. This can be useful if you want to compare the current property with another property from inside the predicate:

Example.
RuleFor(customer => customer.Surname).Must((customer, surname) => surname != customer.Forename)

As you can see the customer entity is passed into the .Must() method as a parameter and is therefore available to the predicate.

So I did not need to create a separate custom validator, I could use an existing method to achieve the validation I required.
May 29, 2013 at 1:25 PM
Hello TetleyK, I read the second part of the documentation.
But as you can see in my code I do not use Must.

My goal is to encapsulate all this logic duplicate field in a custom validation.
I even try to create Expression linq to achieve this goal:
protected override bool IsValid(PropertyValidatorContext context)
{
    if (context.PropertyValue == null || string.IsNullOrWhiteSpace(context.PropertyValue.ToString()))
        return true;

    var currentId = Expression.Lambda<Func<int>>(PropertyId).Compile()(); // Get Current ID

    // p => p.Id != currentId
    ParameterExpression pId = Expression.Parameter(typeof(int), "Id");
    ConstantExpression cId = Expression.Constant(currentId, typeof(int));
    BinaryExpression notCurrent = Expression.NotEqual(pId, cId);
    Expression<Func<int, bool>> NotCurrentExpr =
        Expression.Lambda<Func<int, bool>>(
            notCurrent,
            new ParameterExpression[] { pId });
            
    // p.{PropertyName} == context.PropertyValue
    ParameterExpression pUnique = Expression.Parameter(typeof(string), context.PropertyName);
    ConstantExpression cUnique = Expression.Constant(context.PropertyValue, typeof(string));
    BinaryExpression checkUnique = Expression.Equal(pId, cId);
    Expression<Func<string, bool>> CheckUniqueExp =
        Expression.Lambda<Func<string, bool>>(
            checkUnique,
            new ParameterExpression[] { pUnique });

    if (currentId > 0)
    {
        var exp = Expression.And(NotCurrentExpr, CheckUniqueExp);
        return Repository.All().Provider.CreateQuery(exp);
    }
    return Repository.All().Any(checkUnique);
}
I've used this logic before (with Must and method overload) for validation of unique fields. But with so many fields with this same validation decided to encapsulate

Usage:
RuleFor(p => p.CNPJ)
    .MustBeUnique(p => p.Id, repository);
Jul 5, 2013 at 4:27 PM
@TetleyK and @JeremyS.. see this: https://gist.github.com/Ridermansb/22239f8fce8d0f58f71b

I know that this error is not related to FluentValidation, but as this code may be useful to other developers thought if you could help me.
The idea is to create a generic PropertyValidator that works with any property.