C# 编写通用FluentValidation自定义验证器以检查唯一约束
对C#、ASP.NET MVC和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
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");