Как повторно использовать данные в FluentValidation
например, у меня есть валидатор с двумя правилами проверки:
// Rule 1
RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email) != 0)
.WithMessage("User with provided Email was not found in database!");
// Rule 2
RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email) >= 1)
.WithMessage("There are multiple users with provided Email in database!");
Как вы можете видеть, есть два вызова базы данных с одним и тем же методом. Как я могу вызвать его один раз и повторно использовать данные для других правил?
Еще одна проблема при отображении сообщений об ошибках:
RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email) >= 1)
.WithMessage("There are multiple users with following Email '{0}' in database!",
(model, email) => { return email; });
Есть ли лучший способ отображения сообщений об ошибках, не все время писать эти лямбда-выражения для извлечения свойства? Например, сохранить модель где-то, а затем использовать ее позже.
просто и легко реализовать решения было бы неплохо!
2 ответа:
Для #1, боюсь, нет способа сделать это. Валидаторы должны быть без состояния, чтобы их можно было повторно использовать в потоках (на самом деле, настоятельно рекомендуется создавать экземпляры валидаторов в виде синглетов, поскольку их создание очень дорого. Интеграция MVC делает это по умолчанию). Не связывайтесь со статическими полями, так как вы столкнетесь с потоковыми проблемами.
(Edit: в этом конкретном простом случае вы можете просто объединить правила в один вызов Must, но в целом вы не можете разделите состояние между правилами)
Для #2 это зависит от используемого валидатора свойств. Большинство валидаторов свойств фактически позволяют использовать заполнитель {PropertyValue}, и значение будет вставлено автоматически. Однако в этом случае вы используете валидатор" Must " (PredicateValidator), который не поддерживает заполнители.
У меня есть список валидаторов, которые поддерживают пользовательские заполнители здесь: https://github.com/JeremySkinner/FluentValidation/wiki/c.-Built-In-Validators
Часть 1
Вы хотите уменьшить число вызовов базы данных с 2 до 1, поэтому вам нужно использовать поле для сохранения результата вызова базы данных, потому что правила валидатора код на самом деле работают в "runtime".
Класс валидатора:
public class MyValidator : Validator<UserAccount> { private int? _countOfExistingMails; private string _currentEmail; private object locker = new object(); public MyValidator() { CallEmailValidations(); // other rules... } }
Вот отдельный метод для проверки почты вызовов. Поскольку
Must
принимает выражение в качестве параметра, вы можете передать имя метода с его аргументами:public void CallEmailValidations() { RuleFor(o => o.Email).Must(x => EmailValidation(x, 0)) .WithMessage("User with provided Email was not found in database!"); RuleFor(o => o.Email).Must(x => EmailValidation(x, 1)) .WithMessage("There are multiple users with provided Email in database!"); }
И тело метода валидации сам по себе:
public bool EmailValidation(string email, int requiredCount) { var isValid = false; lock(locker) { if (email != _currentEmail || _currentEmail == null) { _currentEmail = email; _countOfExistingMails = (int)GetDataDataFromDB(email); } if (requiredCount == 0) { isValid = _countOfExistingMails != 0; // Rule 1 } else if (requiredCount == 1) { isValid = _countOfExistingMails <= 1; // Rule 2 } } // Rule N... return isValid; }
обновление : Этот код работает, но лучшим подходом является реализация кэширования в методе уровня доступа к данным.
Часть 2
Вот переписанное правило:
RuleFor(o => o.Email).Must((email) => GetDataDataFromDB(email) >= 1) .WithMessage("There are multiple users with following Email '{0}' in database!", m => m.Email)
Из "C# в глубину":
Когда лямбда-выражение нуждается только в одном параметре, и это параметр может быть неявно типизирован, C# 3 позволяет опустить скобки, так что теперь у него есть это форма
GOTCHAS:
Не передавайте явно
this
лямбда-выражениям. Насколько я знаю,это может вызвать проблемы с подготовкой. Нет никакой причины создавать экстра-закрытие.Я полагаю, что вы используете
DataContext
в некоторой форме внутри методаGetDataDataFromDB
. Таким образом, вы должны контролировать время жизни вашего контекста, потому что объект validator создается как синглетный.