编写通用的FluentValidation自定义验证器来检查唯一约束

时间:2014-12-09 08:10:05

标签: c# asp.net-mvc validation generics fluentvalidation

对C#,ASP.NET MVC和FluentValidation来说真的很新。

我有一个类似的用户模型:

public class UserDetails{
    public int ID { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
}

目前,我一直在使用FluentValidation验证用户名和电子邮件,例如:

 public AdminDetailsValidator(){
        RuleFor(ad => ad.UserName).NotNull().Must(UniqueUserName(UserName)).WithMessage("UserName not Available");
        RuleFor(ad => ad.Email).NotNull().Must(UniqueEmail(Email)).WithMessage("This Email id has already been registered"); ;
    }

    public bool UniqueUserName(string un)
    {
        if (UserDbContext.userDetails.SingleOrDefault(p => p.UserName == un) == null)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public bool UniqueEmail(string em)
    {
        if (UserDbContext.userDetails.SingleOrDefault(p => p.Email == em) == null)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

但我宁愿想要一个更通用的UniqueValidator,我可以使用多个类和属性。或者至少,我不必为每个房产单独设置一个功能。所以我查看了自定义验证器。但我不知道,我如何使用该功能满足我的需求。 我想做这样的事情:

RuleFor(ad => ad.Email).NotNull().SetValidator(new UniquePropertyValidator<UserDbContext>(userDetails.Email).WithMessage("This Email id has already been registered");

这甚至可以做到吗?我想将DbContext作为类型参数和属性作为参数传递(或者它的一些变体,无论哪个有效)。并且该方法可以针对表检查属性并返回它是否唯一。

3 个答案:

答案 0 :(得分:0)

您是否考虑过使用lambdas和泛型?我没有使用FluentValidation,因此这可能不是验证器的正确方法。

var dbContext = new UserDbContext();
RuleFor(ud => ud.Email)
    .NotNull()
    .SetValidator(
        new UniquePropertyValidator<UserDetails>
         (ud, ud => ud.Email, () => dbcontext.userDetails)
    .WithMessage("This Email id has already been registered");

public class UniquePropertyValidator<T> {
    public UniquePropertyValidator(T entity, Func<T,string> propertyAccessorFunc, Func<IEnumerable<T>> collectionAccessorFunc) {
        _entity = entity;
        _propertyAccessorFunc =  propertyAccessorFunc;
        _collectionAccessorFunc =collectionAccessorFunc;
    }

    public bool Validate(){
       //Get all the entities by executing the lambda
       var entities = _collectionAccessorFunc();

       //Get the value of the entity that we are validating by executing the lambda
       var propertyValue = _propertyAccessorFunc(_entity);

       //Find the matching entity by executing the propertyAccessorFunc against the 
       //entities in the collection and comparing that with the result of the entity 
       //that is being validated. Warning SingleOrDefault will throw an exception if
       //multiple items match the supplied predicate
       //http://msdn.microsoft.com/en-us/library/vstudio/bb342451%28v=vs.100%29.aspx
       var matchingEntity = entities.SingleOrDefault(e => _propertyAccessorFunc(e) == propertyValue);
       return matchingEntity == null;
    }
} 

答案 1 :(得分:0)

我一直在努力为这个验证器找到一个优雅的解决方案,但到目前为止提供的解决方案似乎是获取所有数据然后检查唯一性。在我看来,这不是很好。

当尝试使用下面提出的实现时,我收到LINQ to Entities不支持Invoke(即在 template<typename _InputIterator, typename _Tp> inline _Tp accumulate(_InputIterator __first, _InputIterator __last, _Tp __init) { // concept requirements __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>) __glibcxx_requires_valid_range(__first, __last); for (; __first != __last; ++__first) __init = __init + *__first; return __init; } 子句中执行Func<>)的错误。有没有解决方法?

Where

答案 2 :(得分:0)

我们可以简单地通过使用 LINQ to Entities 来解决这个问题。
这是一个静态方法,用于确定给定值在指定的 DbSet 中是否唯一:

static class ValidationHelpers
{
    /// <summary>
    /// Determines whether the specified <paramref name="newValue"/> is unique inside of
    /// the given <paramref name="dbSet"/>.
    /// </summary>
    /// <param name="dbSet"></param>
    /// <param name="getColumnSelector">
    /// Determines the column, with which we will compare <paramref name="newValue"/>
    /// </param>
    /// <param name="newValue">
    /// Value, that will be checked for uniqueness
    /// </param>
    /// <param name="cancellationToken"></param>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TColumn"></typeparam>
    /// <returns></returns>
    public static async Task<bool> IsColumnUniqueInsideOfDbSetAsync<TEntity, TColumn>(DbSet<TEntity> dbSet,
        Expression<Func<TEntity, TColumn>> getColumnSelector,
        TColumn newValue,
        CancellationToken cancellationToken)
        where TEntity : class
    {
        return !await dbSet
            .Select(getColumnSelector)
            .AnyAsync(column => column.Equals(newValue), cancellationToken);
    }
}

使用示例

我们有以下实体:

public class Category
{
    // ...
    public string Title { get; set; }
    // ...
}

还有一个 DbContext 类:

public interface ApplicationDbContext
{
    // ...
    public DbSet<Category> Category { get; set; }
    // ...
}

假设用户想要创建一个新类别。我们要验证此类别的标题的唯一性:

RuleFor(c => c.Title)
    .MustAsync
    (
        (newTitle, token) => ValidationHelpers.IsColumnUniqueInsideOfDbSetAsync
            (_context.Category, c => c.Title, newTitle, token)
    )
    .WithMessage("{PropertyName} must be unique");

注意_contextApplicationDbContext 类型的对象。