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

У меня есть два отдельных типа:

public class Person
{
    public string Name { get; set; }
    public bool IsActive { get; set; }

    public Contact ContactDetails { get; set; }
}

public class Contact
{
    [RequiredIfActive]
    public string Email { get; set; }
}

Что мне нужно, так это выполнить условную декларативную проверку поля внутренней модели на основе некоторого состояния поля родительской модели - в этом конкретном примере необходимо заполнить Email, если включена опция IsActive.

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

public class RequiredIfActiveAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
                                                ValidationContext validationContext)
    {
        /* validationContext.ObjectInstance gives access to the current 
           Contact type, but is there any way of accessing Person type? */

Изменить:

Я знаю, как можно реализовать условную проверку с помощью Fluent Validation, но я НЕ спрашиваю об этом (мне не нужна поддержка относительно Fluent Validation). Однако я хотел бы знать, существует ли какой-либо способ доступа к родительской модели изнутри System.ComponentModel.DataAnnotations.ValidationAttribute.


person jwaliszko    schedule 16.08.2013    source источник
comment
Свободная проверка намного лучше, чем проверка MVC по умолчанию. Я перешел на беглую проверку 3 месяца назад, после того как использовал проверку mvc по умолчанию в течение 4 лет.   -  person Softlion    schedule 08.09.2013
comment
@Softlion: Этот вопрос в основном вызван моим любопытством, я не пытаюсь что-либо проверить. Я также не пытаюсь упростить свою жизнь в этом конкретном контексте. Вот почему предложения Fluent Validation не касаются моего вопроса ни на каком уровне.   -  person jwaliszko    schedule 09.09.2013


Ответы (2)


Мое предложение

Перейдите в Инструменты => Диспетчер пакетов библиотек => Консоль диспетчера пакетов и установите Fluent Validation.

введите здесь описание изображения

Методы действий

[HttpGet]
public ActionResult Index()
{
    var model = new Person
    {
        Name = "PKKG",
        IsActive = true,
        ContactDetails = new Contact { Email = "[email protected]" }
    };
    return View(model);
}
[HttpPost]
public ActionResult Index(Person p)
{
    return View(p);
}

Свободные правила проверки

public class MyPersonModelValidator : AbstractValidator<Person>
{
    public MyPersonModelValidator()
    {
        RuleFor(x => x.ContactDetails.Email)
            .EmailAddress()
            .WithMessage("Please enter valid email address")
            .NotNull().When(i => i.IsActive)
            .WithMessage("Please enter email");
    }
}

Просмотреть модели

[Validator(typeof(MyPersonModelValidator))]
public class Person
{
    [Display(Name = "Name")]
    public string Name { get; set; }

    [Display(Name = "IsActive")]
    public bool IsActive { get; set; }

    public Contact ContactDetails { get; set; }
}

public class Contact
{
    [Display(Name = "Email")]
    public string Email { get; set; }
}

Просмотреть

@{
    var actionURL = Url.Action("Action", "Controller", new { area = "AreaName" },
                           Request.Url.Scheme);
}
@using (Html.BeginForm("Action", "Controller", FormMethod.Post, 
                                                    new { @action = actionURL }))
    @Html.EditorFor(i => i.Name);
    @Html.ValidationMessageFor(i => i.Name);

    @Html.EditorFor(i => i.IsActive);
    @Html.ValidationMessageFor(i => i.IsActive);

    @Html.EditorFor(i => i.ContactDetails.Email);
    @Html.ValidationMessageFor(i => i.ContactDetails.Email);
    <button type="submit">
        OK</button>
}
person Imad Alazani    schedule 07.09.2013
comment
Я ценю ваши усилия, но, как было четко указано в самом вопросе (и в комментариях, которые теперь удалены), Fluent Validation - это не решение, которое я ищу в моем конкретном случае. - person jwaliszko; 07.09.2013

Это невозможно сделать с помощью атрибута Contact.Email, поскольку, как вы уже обнаружили, родитель Person недоступен из контекста атрибута во время выполнения. Чтобы включить этот сценарий с помощью атрибута проверки, атрибут должен украшать класс Person. У вас есть два варианта для этого с атрибутами System.ComponentModel.DataAnnotations: CustomValidationAttribute или пользовательский подкласс ValidationAttribute, предназначенный для Person.

Вот как могут выглядеть два класса при использовании CustomValidationAttribute:

[CustomValidation(typeof(Person), "ValidateContactEmail")]
public class Person
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public Contact ContactDetails { get; set; }

    public static ValidationResult ValidateContactEmail(Person person, ValidationContext context)
    {
        var result = ValidationResult.Success;
        if (person.IsActive)
        {
            if ((person.ContactDetails == null) || string.IsNullOrEmpty(person.ContactDetails.Email))
            {
                result = new ValidationResult("An e-mail address must be provided for an active person.", new string[] { "ContactDetails.Email" });
            }
        }

        return result;
    }
}

public class Contact
{
    public string Email { get; set; }
}
person Nicole Calinoiu    schedule 10.09.2013
comment
Не могли бы вы уделить немного времени, чтобы привести пример кода для более подробной информации, если это возможно? - person Imad Alazani; 10.09.2013
comment
Я добавил пример для CustomValidationAttribute. Нужен ли он также для пользовательского подкласса ValidationAttribute? - person Nicole Calinoiu; 10.09.2013