Написание универсального 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 в качестве параметра типа и свойства в качестве аргумента (или некоторого его варианта, в зависимости от того, что работает). и метод может проверить свойство по таблице и вернуть, является ли оно уникальным или нет.

2 7

2 ответа:

Вы рассматривали возможность использования лямбд и дженериков? Я не использовал 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;
    }
} 

Я также пытался найти элегантное решение для этого валидатора, но решение, предоставленное до сих пор, похоже, извлекает все данные, а затем проверяет уникальность. На мой взгляд, это не очень хорошо.

При попытке использовать реализацию, предложенную ниже, я получаю ошибку, что LINQ to Entities не поддерживает Invoke (т. е. выполнение Func<> внутри предложения Where). Есть ли обходной путь?

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));
    }
}