Пользовательская проверка на основе другого значения

Я делаю форму бронирования для ресторана, в которой запрашивается название ресторана, дата приема пищи и количество человек.

У меня есть класс бронирования, у которого есть идентификатор, идентификатор ресторана, дата и количество людей:

public class Booking
{
    public int Id { get; set; }
    public int IDRestaurant{ get; set; }
    [CustomPlaceValidator]
    public int Nbpeople { get; set; }
    [CustomDateValidator]
    public DateTime Date { get; set; }
}

А также класс Resto, у которого есть ID, имя, номер телефона и номер таблицы:

public class Resto
{
    public int Id { get; set; }
    [Required(ErrorMessage = "Le nom du restaurant doit être saisi")]
    public string Nom { get; set; }
    [Display(Name = "Téléphone")]
    [RegularExpression(@"^0[0-9]{9}$", ErrorMessage = "Le numéro de téléphone est incorrect")]
    public string Telephone { get; set; }
    [Range(0, 9999)]
    public int Size { get; set; }
}

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

public class CustomPlaceValidator : ValidationAttribute
{
    private IDal dal = new Dal();
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        int nb = 0;
        if (dal.GetAllBooking() != null)
        {
            foreach (var booking in dal.GetAllBooking())
                nb += booking.Nbpeople;
            if (nb ..... ) return ValidationResult.Success;
            return new ValidationResult("The restaurant is full for this date.");
        }
        return ValidationResult.Success;

    }

}

(Это черновик, тесты явно не закончены)

Как я могу получить значение других свойств для моей проверки?


person Matthieu Veron    schedule 30.06.2017    source источник
comment
Если вам нужна проверка на стороне клиента, требующая доступа к базе данных, используйте RemoteAttribute для вызова ajax — Как реализовать удаленную проверку в ASP.NET MVC . Не помещайте код доступа к базе данных внутри ValidationAttribute   -  person    schedule 01.07.2017


Ответы (3)


Это не подходит для атрибута проверки. Во-первых, атрибут проверки должен быть независимым или, по крайней мере, автономным. Поскольку логика здесь зависит от двух разных свойств (количество человек и дата бронирования), атрибуту проверки потребуется слишком много знаний о домене, чтобы выполнить необходимую проверку. Другими словами, его нельзя использовать повторно, а если его нельзя использовать повторно, то нет смысла использовать атрибут.

Во-вторых, атрибут проверки не должен делать что-то вроде запроса к базе данных. Только контроллер должен нести ответственность за работу с вашим DAL. Когда вы начнете засорять доступ к базе данных в своем приложении, вы начнете сталкиваться со всевозможными проблемами в очень короткие сроки. Если вы используете DI-контейнер для внедрения вашего DAL туда, куда он должен идти, менее проблематично использовать его вне контроллера, но важно то, что атрибуты действительно плохо работают с внедрением зависимостей. Вы можете заставить его работать с некоторыми контейнерами DI, но это никогда не бывает легко, и вы, вероятно, пожалеете об этом позже. Итак, опять же, это действительно не должно быть чем-то, что обрабатывает атрибут проверки.

На мой взгляд, лучший подход — просто создать закрытый/защищенный метод на вашем контроллере для обработки этой проверки. Что-то типа:

public void ValidateCapacity(Booking booking)
{
    var restaurant = dal.GetRestaurant(booking.IDRestaurant);
    var existingBookings = dal.GetBookings(booking.IDRestaurant, booking.Date);
    var available = restaurant.Size - existingBookings.Sum(b => b.Nbpeople);
    if (booking.Nbpeople > available)
    {
        ModelState.AddModelError("Nbpeople", "There is not enough capacity at the restaurant for this many people on the date you've selected");
    }
}

Затем в своем почтовом действии для бронирования просто вызовите это перед проверкой ModelState.IsValid.

person Chris Pratt    schedule 30.06.2017
comment
Спасибо, действительно, это кажется более подходящим для того, что я хочу сделать! - person Matthieu Veron; 30.06.2017

Я смотрю на этот вопрос: Групповая проверка сообщения для нескольких свойств вместе в одно сообщение asp.net mvc

Мое предположение примерно такое:

public class Booking
{
    public int Id { get; set; }
    public int IDRestaurant{ get; set; }
    [CustomPlace("IDRestaurant", "Date", ErrorMessage = "the restaurant is full")]
    public int Nbpeople { get; set; }
    [CustomDateValidator]
    public DateTime Date { get; set; }
}

и пользовательская проверка:

public class CustomPlaceAttribute : ValidationAttribute
{
    private readonly string[] _others
    public CustomPlaceAttribute(params string[] others)
    {
        _others= others;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
           // TODO: validate the length of _others to ensure you have all required inputs
           var property = validationContext.ObjectType.GetProperty(_others[0]);
           if (property == null)
           {
               return new ValidationResult(
                string.Format("Unknown property: {0}", _others[0])
               );
           }
           // This is to get one of the other value information. 
           var otherValue = property.GetValue(validationContext.ObjectInstance, null);

           // TODO: get the other value again for the date -- and then apply your business logic of determining the capacity          
    }
}

Тем не менее, вызов базы данных для validationAttribute выглядит немного запутанным.

person hsoesanto    schedule 30.06.2017
comment
Спасибо, но на самом деле, прочитав 3 ответа, я понял, что это не обязательно разумно. - person Matthieu Veron; 30.06.2017

То, о чем вы просите, - это проверка перекрестного свойства. Если вы не категорически против реализации интерфейса в ваших объектах данных, вам следует обратить внимание на следующее:

https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.ivalidatableobject.aspx

Простой пример реализации класса небольшого прямоугольника, где мы хотим, чтобы его площадь не превышала 37 (какой бы ни была эта единица измерения).

public class SmallRectangle : IValidatableObject
{
    public uint Width { get; set; }
    public uint Height { get; set; }
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var area = Width * Height;
        if (area > 37)
        {
            yield return new ValidationResult($"The rectangle is too large.");
        }
    }
}

Альтернативы

Второй параметр функции IsValid в вашем ValidationAttribute предоставляет вам ValidationContext, у которого есть свойство ObjectInstance, которое вы можете привести к типу вашего объекта и получить доступ к другим его членам. Это, однако, сделает ваш атрибут проверки специфичным для вашего класса. Я бы вообще советовал против этого.

Вы также можете использовать другой подход к проверке, например, использовать библиотеку проверки, такую ​​как FluentValidations, см.: https://github.com/JeremySkinner/FluentValidation

Другая перспектива

И последнее, но не менее важное: я хотел бы отметить, что обычно проверка должна использоваться для проверки целостности данных. Запрос на бронирование, в котором запрашивается больше мест, чем доступно, не является недействительным. Это не может быть удовлетворено, но это действительный запрос, на который, к сожалению, будет дан отрицательный ответ. Дать такой отрицательный результат, на мой взгляд, не обязанность валидации, а бизнес-логика.

person cel sharp    schedule 30.06.2017
comment
Спасибо за решения И за замечание, которое заставило меня понять, что это не обязательно правильный метод! - person Matthieu Veron; 30.06.2017