Условная проверка ASP.NET MVC

129

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

Например, допустим, у нас есть следующая модель (человек и старший):

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" (true -> обязательно). Как реализовать условную проверку в ASP.NET MVC 2 с аннотациями данных?

Питер Стегнар
источник
1
Недавно я задал аналогичный вопрос: stackoverflow.com/questions/2280539/…
Дарин Димитров
Я запутался. SeniorОбъект всегда старший, так почему IsSenior может быть ложным в этом случае. Разве вам не нужно, чтобы свойство Person.Senior было нулевым, когда оно Person.IsSeniorравно false. Или почему бы не реализовать IsSeniorимущество следующим образом : bool IsSenior { get { return this.Senior != null; } }.
Стивен
Стивен: «IsSenior» переводится как поле флажка в представлении. Когда пользователь устанавливает флажок «IsSenior», поле «Senior.Description» становится обязательным.
Питер Стегнар
Дарин Димитров: В некотором роде, но не совсем. Видите ли, как вы добьетесь того, чтобы сообщение об ошибке добавлялось к определенному полю? Если вы выполняете проверку на уровне объекта, вы получаете ошибку на уровне объекта. Мне нужна ошибка на уровне собственности.
Питер Стегнар

Ответы:

151

Есть гораздо лучший способ добавить правила условной проверки в 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) .

viperguynaz
источник
если свойство имеет тип "int", для которого требуется значение, если заполнить это поле, проверка не работает ..
Джейхун Рагимов
2
К сожалению, Microsoft поместила это на неправильный уровень - проверка - это бизнес-логика, а этот интерфейс находится в DLL System.Web. Чтобы использовать это, вы должны сделать свой бизнес-уровень зависимым от технологии представления.
NightOwl888,
7
вы это сделаете, если вы его реализуете - см. полный пример на falconwebtech.com/post/…
viperguynaz
4
falconwebtech.com/post/… - @viperguynaz, это не работает
Смит Патель,
1
@RayLoveless, ты должен звонить ModelState.IsValid- а не звонить прямо в Validate
viperguynaz
63

Я решил это, обработав словарь "ModelState" , который содержится в контроллере. Словарь ModelState включает все элементы, которые необходимо проверить.

Вот решение:

Если вам нужно реализовать условную проверку на основе некоторого поля (например, если A = true, то требуется B), при сохранении сообщений об ошибках на уровне свойств (это неверно для настраиваемых валидаторов, которые находятся на уровне объекта), вы можете добиться этого обрабатывая "ModelState", просто удаляя из него ненужные проверки.

... В каком-то классе ...

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

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

... урок продолжается ...

... В некоторых действиях контроллера ...

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

...

Этим мы добиваемся условной проверки, оставляя все остальное без изменений.


ОБНОВИТЬ:

Это моя последняя реализация: я использовал интерфейс модели и атрибут действия, который проверяет модель, реализующую указанный интерфейс. Интерфейс прописывает метод Validate (ModelStateDictionary modelState). Атрибут действия просто вызывает Validate (modelState) на IValidatorSomething.

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

Питер Стегнар
источник
17
Обратной стороной является то, что одна часть вашей логики проверки находится в модели, а другая часть - в контроллере (ах).
Kristof Claes
Конечно, в этом нет необходимости. Я просто показываю самый простой пример. Я реализовал это с помощью интерфейса в модели и с атрибутом действия, который проверяет модель, реализующую упомянутый интерфейс. Интерфейс использует метод Validate (ModelStateDictionary modelState). Итак, наконец, вы ДЕЛАЕТЕ всю валидацию в модели. В любом случае, хорошее замечание.
Питер Стегнар,
Мне нравится простота этого подхода, пока команда MVC не создаст что-то лучше из коробки. Но работает ли ваше решение с включенной проверкой на стороне клиента?
Аарон
2
@Aaron: Я рад, что вам нравится решение, но, к сожалению, это решение не работает с проверкой на стороне клиента (поскольку каждый атрибут проверки требует своей реализации JavaScript). Вы могли бы помочь себе с помощью атрибута «Remote», поэтому для его проверки будет выполнен только вызов Ajax.
Питер Стегнар,
Можете ли вы расширить этот ответ? В этом есть некоторый смысл, но я хочу быть уверен, что понимаю это. Я столкнулся с этой конкретной ситуацией, и я хочу ее решить.
Richard B
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/…
Дэн Хьюнекс
Есть ли в asp.net mvc2 какое-либо эквивалентное решение? ValidationResult, классы ValidationContext недоступны в asp.net mvc2 (.net framework 3.5)
User_MVC
2
Это работает только на стороне сервера, как указано в связанном блоге
Pakman
2
Мне удалось заставить это работать на стороне клиента с помощью MVC5, но на клиенте он запускает проверку независимо от того, что такое DesiredValue.
Geethanga
1
@Dan Hunex: В MVC4 мне не удалось должным образом работать на стороне клиента, и он запускает проверку независимо от того, что такое DesiredValue. Любая помощь, пожалуйста?
Джек
34

Я использую этот удивительный nuget, который делает динамические аннотации 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; }
Korayem
источник
3
Библиотека ExpressiveAnnotation - это наиболее гибкое и универсальное решение из всех представленных здесь ответов. Спасибо, что поделился!
Судханшу Мишра
2
Я бился головой, пытаясь найти решение на целый день. ExpressiveAnnotations кажется мне исправлением!
Caverman
Библиотека ExpressiveAnnotation потрясающая!
Дуг Кнудсен
1
Также есть поддержка на стороне клиента!
Nattrass
1
Однако нет поддержки .NET Core, и, похоже, этого не произойдет.
gosr
18

Вы можете отключить валидаторы условно, удалив ошибки из ModelState:

ModelState["DependentProperty"].Errors.Clear();
Павел Чучува
источник
6

Теперь существует структура, которая выполняет эту условную проверку (среди других удобных проверок аннотаций данных) из коробки: http://foolproof.codeplex.com/

В частности, обратите внимание на валидатор [RequiredIfTrue ("IsSenior")]. Вы помещаете это непосредственно в свойство, которое хотите проверить, чтобы получить желаемое поведение ошибки проверки, связанной со свойством Senior.

Он доступен в виде пакета NuGet.

bojingo
источник
3

Вам необходимо пройти проверку на уровне Person, а не на уровне Senior, или Senior должен иметь ссылку на своего родительского Person. Мне кажется, что вам нужен механизм самопроверки, который определяет валидацию на человеке, а не на одном из его свойств. Я не уверен, но я не думаю, что DataAnnotations поддерживает это из коробки. Что вы можете сделать, создайте свое собственное Attribute, основанное наValidationAttribute этого, можно оформить на уровне класса, а затем создать настраиваемый валидатор, который также позволяет запускать эти валидаторы уровня класса.

Я знаю, что Validation Application Block сразу поддерживает самопроверку, но у 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));
        }
    }
}
Стивен
источник
«Вам необходимо проверить на уровне Person, а не на уровне Senior». Да, это вариант, но вы теряете возможность добавления ошибки к определенному полю, которое требуется в объекте Senior.
Питер Стегнар,
3

У меня была такая же проблема, мне нужна была модификация атрибута [Required] - поле make необходимо в зависимости от 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была именно тем, что мне нужно было увидеть. Спасибо.
twip
2

Ознакомьтесь с условной проверкой Саймона Инса в MVC .

Я работаю над его примером проекта прямо сейчас.

Мерритт
источник
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». Тогда вам просто нужно проверить правильность вашего действия с публикацией.


источник