C# 编写通用FluentValidation自定义验证器以检查唯一约束

C# 编写通用FluentValidation自定义验证器以检查唯一约束,c#,asp.net-mvc,validation,generics,fluentvalidation,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 AdminDetail

对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");
RuleFor(ad=>ad.Email).NotNull().SetValidator(新的UniquePropertyValidator(userDetails.Email).WithMessage(“此电子邮件id已注册”);

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

您是否研究过使用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;
    }
} 
var dbContext=newuserdbcontext();
规则(ud=>ud.Email)
.NotNull()
.SetValidator(
新UniquePropertyValidator
(ud,ud=>ud.Email,()=>dbcontext.userDetails)
.WithMessage(“此电子邮件id已注册”);
公共类UniquePropertyValidator{
公共UniquePropertyValidator(T实体、Func propertyAccessorFunc、Func collectionAccessorFunc){
_实体=实体;
_propertyAccessorFunc=propertyAccessorFunc;
_collectionAccessorFunc=collectionAccessorFunc;
}
公共bool验证(){
//通过执行lambda获取所有实体
变量实体=_collectionAccessorFunc();
//通过执行lambda获取我们正在验证的实体的值
var propertyValue=_propertyAccessorFunc(_实体);
//通过对对象执行propertyAccessorFunc来查找匹配的实体
//集合中的实体,并将其与实体的结果进行比较
//正在验证。警告SingleOrDefault将在以下情况下引发异常
//多个项与提供的谓词匹配
//http://msdn.microsoft.com/en-us/library/vstudio/bb342451%28v=vs.100%29.aspx
var matchingEntity=entities.SingleOrDefault(e=>\u propertyAccessorFunc(e)==propertyValue);
返回matchingEntity==null;
}
} 

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

尝试使用下面建议的实现时,我遇到一个错误,LINQ to Entities不支持调用(即在
Where
子句中执行
Func
),是否有解决方法

public class UniqueFieldValidator<TObject, TViewModel, TProperty> : PropertyValidator where TObject : Entity where TViewModel : Entity
{
    private readonly IDataService<TObject> _dataService;
    private readonly Func<TObject, TProperty> _property;

    public UniqueFieldValidator(IDataService<TObject> dataService, Func<TObject, TProperty> property)
        : base("La propiedad {PropertyName} tiene que ser unica.")
    {
        _dataService = dataService;
        _property = property;
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        var model = context.Instance as TViewModel;
        var value = (TProperty)context.PropertyValue;

        if (model != null && _dataService.Where(t => t.Id != model.Id && Equals(_property(t), value)).Any())
        {
            return false;
        }

        return true;
    }
}

public class ArticuloViewModelValidator : AbstractValidator<ArticuloViewModel>
{
    public ArticuloViewModelValidator(IDataService<Articulo> articuloDataService)
    {
        RuleFor(a => a.Codigo).SetValidator(new UniqueFieldValidator<Articulo, ArticuloViewModel, int>(articuloDataService, a => a.Codigo));
    }
}
public类UniqueFieldValidator:PropertyValidator where-TObject:Entity where-TViewModel:Entity
{
私有只读IDataService(数据服务);
私有只读Func_属性;
公共UniqueFieldValidator(IDataService数据服务,Func属性)
:base(“La propiedad{PropertyName}tiene que ser unica.”
{
_数据服务=数据服务;
_财产=财产;
}
受保护的覆盖布尔值有效(PropertyValidatorContext上下文)
{
var model=context.Instance作为TViewModel;
var value=(TProperty)context.PropertyValue;
if(model!=null&&u dataService.Where(t=>t.Id!=model.Id&&Equals(_-property(t),value)).Any())
{
返回false;
}
返回true;
}
}
公共类ArticuloViewModelValidator:AbstractValidator
{
公共ArticuloViewModelValidator(IDataService articuloDataService)
{
(a=>a.Codigo).SetValidator(新的UniqueFieldValidator(articuloDataService,a=>a.Codigo));
}
}

我们只需使用LINQ to实体即可解决此问题。
以下是一种静态方法,用于确定给定值在指定的
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);
    }
}
DbContext
类:

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

注意:
\u context
是一个类型为
ApplicationDbContext

的对象。您可能不想预先创建数据库上下文。是的,很好的一点,通常我可能会使用依赖项注入构造验证器,但我不想让问题变得更复杂,因为它已经在等待快速回答了(我自己也不能这么说..一直在忙着另一个项目)。除了
UniquePropertyValidator(..)
中的参数外,我可以理解你建议的大部分内容。我马上就去看看。另外,
ud=>ud。Email
将是属性值吗?我如何接受它作为一个函数(
函数属性AccessorFucn
)?@MridulKashyap构造函数的签名有一个拼写错误。应该是func,它会接受一个T并返回一个字符串。已在EditUnderstand中修复。我不知道如何处理lambda表达式部分。但在询问之前,我想自己查找。我会让你们知道我在哪里使用它。感谢所有的帮助非常感谢。
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");