Умовна перевірка ASP.NET MVC


129

Як використовувати анотації даних, щоб зробити умовну перевірку на моделі?

Наприклад, скажімо, що у нас є така модель (Person and Senior):

public class Person
{
    [Required(ErrorMessage = "*")]
    public string Name
    {
        get;
        set;
    }

    public bool IsSenior
    {
        get;
        set;
    }

    public Senior Senior
    {
        get;
        set;
    }
}

public class Senior
{
    [Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
    public string Description
    {
        get;
        set;
    }
}

І такий погляд:

<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>

<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>

<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>

Я хотів би бути умовно необхідним полем властивості "Senior.Description" на основі вибору властивості "IsSenior" (правда -> обов'язково). Як реалізувати умовну перевірку в ASP.NET MVC 2 за допомогою анотацій даних?


1
Нещодавно я задавав подібне запитання: stackoverflow.com/questions/2280539/…
Дарин Димитров

Я збентежений. SeniorОб'єкт завжди старший, так чому IsSenior може бути помилковим в цьому випадку. Не потрібно, щоб властивість 'Person.Senior' було недійсним, коли Person.IsSeniorце неправда. Або чому б не реалізувати IsSeniorмайно наступним чином : bool IsSenior { get { return this.Senior != null; } }.
Стівен

Стівен: "IsSenior" перекладається на поле прапорець у вікні перегляду. Коли користувач перевіряє прапорець "IsSenior", тоді поле "Senior.Description" стає обов'язковим.
Петро Стегнар

Дарин Димитров: Добре, але не зовсім. Розумієте, як би ви домоглися того, що повідомлення про помилку додається до конкретного поля? Якщо ви перевірите на рівні об'єкта, ви отримаєте помилку на рівні об'єкта. Мені потрібна помилка на рівні власності.
Пітер Стегнар

Відповіді:


150

Є набагато кращий спосіб додати правила умовної перевірки в MVC3; має ваша модель успадкувати IValidatableObjectта реалізувати Validateметод:

public class Person : IValidatableObject
{
    public string Name { get; set; }
    public bool IsSenior { get; set; }
    public Senior Senior { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
        if (IsSenior && string.IsNullOrEmpty(Senior.Description)) 
            yield return new ValidationResult("Description must be supplied.");
    }
}

Детальніше читайте на сторінці представлення ASP.NET MVC 3 (попередній перегляд 1) .


якщо властивість типу "int", це вимагає значення, якщо заповнити це поле,
перевірити

2
На жаль, Microsoft поставила це в неправильному шарі - перевірка - це бізнес-логіка, і цей інтерфейс знаходиться в DLL System.Web. Для того, щоб використовувати це, ви повинні надати бізнес-шару залежність від технології презентації.
NightOwl888

7
якщо ви реалізуєте це - дивіться повний приклад на falconwebtech.com/post/…
viperguynaz

4
falconwebtech.com/post/… - @viperguynaz це не працює
Smit Patel

1
@RayLoveless вам слід дзвонити ModelState.IsValid- не дзвонити Перевірити безпосередньо
viperguynaz

63

Я вирішив це, обробляючи словник "ModelState" , який міститься в контролері. Словник ModelState включає всіх членів, які мають бути затверджені.

Ось таке рішення:

Якщо вам потрібно реалізувати умовну перевірку на основі якогось поля (наприклад, якщо A = вірно, тоді потрібно B), зберігаючи повідомлення про помилки на рівні властивостей (це не вірно для користувацьких валідаторів, які знаходяться на рівні об'єкта), ви можете досягти цього обробляючи "ModelState", просто видаляючи з нього небажані перевірки.

... У якомусь класі ...

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

[Required(ErrorMessage = "*")] 
public string DepentedProperty
{
  get;
  set;
}

... клас продовжується ...

... У деяких діях контролера ...

if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
   this.ModelState.Remove("DepentedProperty");
}

...

Цим ми досягаємо умовної валідації, залишаючи все те ж саме.


ОНОВЛЕННЯ:

Це моя остаточна реалізація: я використав інтерфейс моделі та атрибут action, який підтверджує модель, яка реалізує згаданий інтерфейс. Інтерфейс призначає метод Validate (ModelStateDictionary modelState). Атрибут в дії просто викликає Validate (modelState) на IValidatorSomething.

Я не хотів ускладнювати цю відповідь, тому не згадав остаточні деталі впровадження (які, врешті-решт, мають значення у виробничому коді).


17
Мінус полягає в тому, що одна з частин вашої логіки перевірки розташована в моделі, а інша частина в контролері.
Крістоф Клайс

Ну звичайно це не обов’язково. Я лише показую найосновніший приклад. Я реалізував це за допомогою інтерфейсу на моделі та з атрибутом action, який підтверджує модель, яка реалізує згаданий інтерфейс. Інтерфейс персистує метод Validate (ModelStateDictionary modelState). Отже, нарешті, Ви робите всю перевірку в моделі. У всякому разі, хороший момент.
Пітер Стегнар

Мені подобається простота такого підходу в середній час, поки команда MVC не створить щось краще. Але чи працює ваше рішення з включеною валідацією на стороні клієнта ??
Аарон

2
@Aaron: Я радий, що вам подобається рішення, але, на жаль, це рішення не працює з валідацією на стороні клієнта (оскільки кожен атрибут перевірки потребує його виконання). Ви можете допомогти собі з атрибутом "Віддалений", тому для підтвердження його буде випробовано лише Ajax-дзвінок.
Пітер Стегнар

Чи можете ви розширити цю відповідь? Це має певний сенс, але я хочу переконатися, що я на цьому кристал. Я зіткнувся з такою точною ситуацією, і хочу вирішити її.
Річард Б

36

У мене була та ж проблема вчора, але я це зробив дуже чисто, що працює як для клієнтської, так і для перевірки на стороні сервера.

Умова: Виходячи зі значення іншої властивості в моделі, ви хочете зробити ще одну властивість необхідною. Ось код

public class RequiredIfAttribute : RequiredAttribute
{
    private String PropertyName { get; set; }
    private Object DesiredValue { get; set; }

    public RequiredIfAttribute(String propertyName, Object desiredvalue)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        Object instance = context.ObjectInstance;
        Type type = instance.GetType();
        Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
        if (proprtyvalue.ToString() == DesiredValue.ToString())
        {
            ValidationResult result = base.IsValid(value, context);
            return result;
        }
        return ValidationResult.Success;
    }
}

Тут PropertyName - це властивість, на якій ви хочете зробити свій стан DesiredValue - це особливе значення PropertyName (властивості), для якого ваша інша власність повинна бути підтверджена для необхідної

Скажіть, у вас є наступне

public class User
{
    public UserType UserType { get; set; }

    [RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
    public string Password
    {
        get;
        set;
    }
}

Нарешті, але не в останню чергу, зареєструйте адаптер для вашого атрибуту, щоб він міг перевірити клієнтську сторону (я помістив його у global.asax, Application_Start)

 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));

Це був початковий вихідний пункт miroprocessordev.blogspot.com/2012/08/…
Dan Hunex

Чи є рівнозначне рішення в asp.net mvc2? Класи ValidationResult, ValidationContext недоступні в asp.net mvc2 (.net Framework 3.5)
User_MVC

2
Це працює лише на сервері, як зазначено у пов'язаному блозі
Пакман

2
Мені вдалося змусити це працювати на стороні клієнта з MVC5, але в клієнті він запускає перевірку незалежно від того, що таке DesiredValue.
Geethanga

1
@Dan Hunex: У MVC4 мені не вдалося працювати належним чином на стороні клієнта, і це запускає перевірку незалежно від того, що таке DesiredValue. Будь-яка допомога PLS?
Джек

34

Я використовував цей дивовижний нукет, який робить динамічні анотації ExpressiveAnnotations

Ви можете перевірити будь-яку логіку, про яку мрієте:

public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }

3
Бібліотека ExpressiveAnnotation є найбільш гнучким і загальним рішенням з усіх відповідей тут. Дякую, що поділились!
Sudhanshu Mishra

2
Я стукав головою, намагаючись знайти рішення на солідний день. Експресивні анотації, схоже, для мене виправлення!
Каверман

Бібліотека ExpressiveAnnotation - дивовижна!
Дуг Кнудсен

1
У нього також є підтримка на стороні клієнта!
Наттрасс

1
Немає підтримки для .NET Core, але схоже, що це станеться.
gosr

18

Ви можете відключити валідатори умовно, видаливши помилки з ModelState:

ModelState["DependentProperty"].Errors.Clear();


6

Зараз існує рамка, яка робить цю умовну перевірку (серед інших зручних перевірок анотації даних) поза вікном: http://foolproof.codeplex.com/

Зокрема, подивіться на валідатор [RequiredIfTrue ("IsSenior")]. Ви ставите це безпосередньо на властивість, яку хочете перевірити, тож ви отримаєте бажану поведінку помилки перевірки, пов’язаної з властивістю "Старший".

Він доступний як пакет NuGet.


3

Вам потрібно перевірити на рівні Особи, а не на рівні Старший, або Старший повинен мати посилання на свою батьківську Особу. Мені здається, що вам потрібен механізм самоперевірки, який визначає валідацію щодо Особи, а не одного з її властивостей. Я не впевнений, але я не думаю, що DataAnnotations підтримує це не так. Що ви можете створити власним, Attributeщо випливає з ValidationAttributeцього, можна прикрасити на рівні класу, а потім створити спеціальний валідатор, який також дозволяє запускати валідатори рівня класу.

Я знаю, що блок Validation Application підтримує самовивірку поза межами коробки, але VAB має досить круту криву навчання. Тим не менш, ось приклад використання VAB:

[HasSelfValidation]
public class Person
{
    public string Name { get; set; }
    public bool IsSenior { get; set; }
    public Senior Senior { get; set; }

    [SelfValidation]
    public void ValidateRange(ValidationResults results)
    {
        if (this.IsSenior && this.Senior != null && 
            string.IsNullOrEmpty(this.Senior.Description))
        {
            results.AddResult(new ValidationResult(
                "A senior description is required", 
                this, "", "", null));
        }
    }
}

"Вам потрібно перевірити на рівні Персона, а не на рівні Старший" Так, це варіант, але ви втрачаєте можливість того, що помилка додається до певного поля, необхідного в об'єкті Старший.
Петро Стегнар

3

У мене була така ж проблема, потрібна модифікація атрибута [Required] - зробити поле, необхідне залежно від http запиту. Я не використовую ненав'язливу перевірку, просто MicrosoftMvcValidation.js з коробки. Ось. Реалізуйте свій спеціальний атрибут:

public class RequiredIfAttribute : RequiredAttribute
{

    public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
    {

    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {

    //You can put your logic here   

        return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
    }


}

Тоді вам потрібно впровадити власний провайдер, щоб використовувати його як адаптер у вашій global.asax

public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{

    ControllerContext ccontext;
    public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
       : base(metadata, context, attribute)
    {
        ccontext = context;// I need only http request
    }

//override it for custom client-side validation 
     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
     {       
               //here you can customize it as you want
         ModelClientValidationRule rule = new ModelClientValidationRule()
         {
             ErrorMessage = ErrorMessage,
    //and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"    
             ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
         };
         return new ModelClientValidationRule[] { rule };
      }
}

І змініть ваш global.asax за допомогою рядка

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));

і ось воно

[RequiredIf]
public string NomenclatureId { get; set; }

Основна перевага для мене полягає в тому, що мені не доведеться кодувати валідатор користувацького клієнта, як у випадку ненав'язливої ​​перевірки. він працює так само, як [Обов’язково], але лише у випадках, які вам потрібно.


Частина про розширення DataAnnotationsModelValidatorбула саме те, що мені потрібно було побачити. Дякую.
щебетати


0

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

  1. Складіть умовну першу частину дії контролера
  2. Виконайте логіку для видалення помилки з ModelState
  3. Виконайте решту існуючої логіки (як правило, перевірка стану штату, потім все інше)

Приклад:

public ActionResult MyAction(MyViewModel vm)
{
    // perform conditional test
    // if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")

    // Do typical model state validation, inside following if:
    //     if (!ModelState.IsValid)

    // Do rest of logic (e.g. fetching, saving

У своєму прикладі зберігайте все так, як є, і додайте логіку, запропоновану до дії Вашого контролера. Я припускаю, що ваш ViewModel, переданий в дію контролера, містить об'єкти Person та Senior Person з даними, заповненими в них з інтерфейсу користувача.


0

Я використовую MVC 5, але ви можете спробувати щось подібне:

public DateTime JobStart { get; set; }

[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }

У вашому випадку ви б сказали щось на кшталт "IsSenior == true". Тоді вам просто потрібно перевірити перевірку дії вашої публікації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.