Условно обязательное свойство с использованием аннотаций к данным

У меня есть такой класс:

public class Document
{
   public int DocumentType{get;set;}

   [Required]
   public string Name{get;set;}

   [Required]
   public string Name2{get;set;}
}

Теперь, если я добавлю аннотацию данных [Required] к свойствам Name и Name2, тогда все в порядке, и если Name или Name2 пусты, проверка выдаст ошибку.

Но я хочу, чтобы поле Name было обязательным, только если DocumentType равно 1, а Name2 требовалось, только если DocumentType равно 2.

public class Document
{
   public int DocumentType{get;set;}

   [Required(Expression<Func<object, bool>>)]
   public string Name{get;set;}

   [Required(Expression<Func<object, bool>>)]
   public string Name2{get;set;}
}

но я знаю, что не могу, это вызывает ошибку. Что мне делать по этому требованию?


person brtb    schedule 14.10.2014    source источник
comment
Реализуйте этот ivalidatableobject в своих моделях и запустите свой собственный код для этой модели - msdn.microsoft.com/en-us/library/   -  person Chris McKelt    schedule 14.10.2014
comment
для будущих зрителей - stackoverflow.com/questions/7390902/   -  person Chris McKelt    schedule 15.11.2014


Ответы (9)


Я думаю, что из коробки это пока невозможно.

Но я нашел эту многообещающую статью о Mvc.ValidationToolkit (также здесь, к сожалению это только альфа-версия, но вы, вероятно, также можете просто извлечь нужные вам методы из этот код и интегрируйте его самостоятельно), он содержит приятный звуковой атрибут RequiredIf, который, кажется, точно соответствует вашей причине:

  • вы загружаете проект из связанный zip и создайте его
  • получить собранную dll из папки сборки и сослаться на нее в проекте, который вы используете
  • к сожалению, это также требует ссылки на MVC (самый простой способ получить это - запустить MVC-проект в VS или install-package Microsoft.AspNet.Mvc)
  • в файлах, где вы хотите его использовать, вы добавляете using Mvc.ValidationToolkit;
  • тогда вы можете писать такие вещи, как [RequiredIf("DocumentType", 2)] или [RequiredIf("DocumentType", 1)], поэтому объекты действительны, если не указаны ни name, ни name2, пока DocumentType не равно 1 или 2
person DrCopyPaste    schedule 14.10.2014

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

Я написал RequiredIfAttribute, который требует определенного значения свойства, когда другое свойство имеет определенное значение (то, что вам нужно) или когда другое свойство имеет что-нибудь, кроме определенного значения.

Вот код, который может помочь:

/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
    #region Properties

    /// <summary>
    /// Gets or sets the other property name that will be used during validation.
    /// </summary>
    /// <value>
    /// The other property name.
    /// </value>
    public string OtherProperty { get; private set; }

    /// <summary>
    /// Gets or sets the display name of the other property.
    /// </summary>
    /// <value>
    /// The display name of the other property.
    /// </value>
    public string OtherPropertyDisplayName { get; set; }

    /// <summary>
    /// Gets or sets the other property value that will be relevant for validation.
    /// </summary>
    /// <value>
    /// The other property value.
    /// </value>
    public object OtherPropertyValue { get; private set; }

    /// <summary>
    /// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
    /// </summary>
    /// <value>
    ///   <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
    /// </value>
    /// <remarks>
    /// How this works
    /// - true: validated property is required when other property doesn't equal provided value
    /// - false: validated property is required when other property matches provided value
    /// </remarks>
    public bool IsInverted { get; set; }

    /// <summary>
    /// Gets a value that indicates whether the attribute requires validation context.
    /// </summary>
    /// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
    public override bool RequiresValidationContext
    {
        get { return true; }
    }

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
    /// </summary>
    /// <param name="otherProperty">The other property.</param>
    /// <param name="otherPropertyValue">The other property value.</param>
    public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
        : base("'{0}' is required because '{1}' has a value {3}'{2}'.")
    {
        this.OtherProperty = otherProperty;
        this.OtherPropertyValue = otherPropertyValue;
        this.IsInverted = false;
    }

    #endregion

    /// <summary>
    /// Applies formatting to an error message, based on the data field where the error occurred.
    /// </summary>
    /// <param name="name">The name to include in the formatted message.</param>
    /// <returns>
    /// An instance of the formatted error message.
    /// </returns>
    public override string FormatErrorMessage(string name)
    {
        return string.Format(
            CultureInfo.CurrentCulture,
            base.ErrorMessageString,
            name,
            this.OtherPropertyDisplayName ?? this.OtherProperty,
            this.OtherPropertyValue,
            this.IsInverted ? "other than " : "of ");
    }

    /// <summary>
    /// Validates the specified value with respect to the current validation attribute.
    /// </summary>
    /// <param name="value">The value to validate.</param>
    /// <param name="validationContext">The context information about the validation operation.</param>
    /// <returns>
    /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
    /// </returns>
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (validationContext == null)
        {
            throw new ArgumentNullException("validationContext");
        }

        PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
        if (otherProperty == null)
        {
            return new ValidationResult(
                string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
        }

        object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);

        // check if this value is actually required and validate it
        if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
            this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
        {
            if (value == null)
            {
                return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
            }

            // additional check for strings so they're not empty
            string val = value as string;
            if (val != null && val.Trim().Length == 0)
            {
                return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
            }
        }

        return ValidationResult.Success;
    }
}
person Robert Koritnik    schedule 27.12.2014
comment
молодец, нравится, добавлю параметр not, проверяя, только если это не что-то, а только если это так;) - спасибо - person Pakk; 10.11.2015
comment
Не могли бы вы рассказать, как использовать этот атрибут? Я не могу понять в своей ViewModel, как получить данные и передать их атрибуту. - person Philippe; 17.11.2016
comment
@Pakk Это уже часть этого кода. См. Свойство IsInverted, которое действует как инвертированное if ... - person Robert Koritnik; 18.11.2016
comment
@Philippe: это атрибут, который обычно запускается непосредственно фреймворками, поэтому вам действительно не нужно передавать ему какие-либо данные. Вы просто декларативно устанавливаете его в своей модели данных POCO и все. Классическим примером может служить интернет-магазин. Во время создания счета пользователей спрашивают, является ли это покупкой для личных или корпоративных клиентов. Если они отметят company, станет обязательным множество других полей. Такие свойства модели данных (связанные с этими полями) будут иметь этот атрибут [RequiredIf('IsCompany', true)], где IsCompany: bool обычно привязан к флажку. Надеюсь это поможет. - person Robert Koritnik; 18.11.2016
comment
Молодец, один вопрос: можно ли добавить к этому ненавязчивую валидацию? - person Sirwan Afifi; 26.08.2017
comment
@RobertKoritnik Как бы вы сказали RequiredIf ('Something', Not Null ????) - person Pomster; 15.11.2018
comment
Это здорово ... но могу ли я использовать его в раскрывающемся списке и проверить, выбраны ли 2 из 5 элементов? - person Stephen; 25.04.2019
comment
Как получить OtherPropertyDisplayName, не передавая его в качестве еще одного строкового параметра атрибута? - person Vasily Hall; 28.02.2020

Условно обязательное свойство с аннотациями к данным

 [RequiredIf(dependent Property name, dependent Property value)]

e.g. 


 [RequiredIf("Country", "Ethiopia")]
 public string POBox{get;set;}
 // POBox is required in Ethiopia
 public string Country{get;set;}

 [RequiredIf("destination", "US")]
 public string State{get;set;}
 // State is required in US

 public string destination{get;set;}



public class RequiredIfAttribute : ValidationAttribute
{
    RequiredAttribute _innerAttribute = new RequiredAttribute();
    public string _dependentProperty { get; set; }
    public object _targetValue { get; set; }

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        this._dependentProperty = dependentProperty;
        this._targetValue = targetValue;
    }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var field = validationContext.ObjectType.GetProperty(_dependentProperty);
        if (field != null)
        {
            var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
            if ((dependentValue == null && _targetValue == null) || (dependentValue.Equals(_targetValue)))
            {
                if (!_innerAttribute.IsValid(value))
                {
                    string name = validationContext.DisplayName;
                    return new ValidationResult(ErrorMessage=name + " Is required.");
                }
            }
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult(FormatErrorMessage(_dependentProperty));
        }
    }
}
person Community    schedule 08.03.2017
comment
отлично работает +1 - person snr; 07.04.2021

Ознакомьтесь с Fluent Validation

https://www.nuget.org/packages/FluentValidation/

Описание проекта Небольшая библиотека проверки для .NET, которая использует свободный интерфейс и лямбда-выражения для создания правил проверки для ваших бизнес-объектов.

https://github.com/JeremySkinner/FluentValidation

person Chris McKelt    schedule 14.10.2014
comment
Спасибо, что порекомендовали FluentValidation. Люблю это. - person Stephen McDowell; 06.12.2018

Я всегда использовал реализованный IValidatableObject из System.ComponentModel.DataAnnotations;

Пример ниже

  public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (this.SendInAppNotification)
            {
                if (string.IsNullOrEmpty(this.NotificationTitle) || string.IsNullOrWhiteSpace(this.NotificationTitle))
                {
                    yield return new ValidationResult(
                        $"Notification Title is required",
                        new[] { nameof(this.NotificationTitle) });
                }
            }
person chris castle    schedule 28.06.2019
comment
Престижность. Я всегда предпочитаю использовать то, что включено в .NET Framework, прежде чем добавлять внешние библиотеки или писать конкретную логику. - person SteveB; 29.06.2021

Ознакомьтесь с проверкой MVC с защитой от дурака. У него есть аннотация данных в модели, например RequiredIf (зависимое свойство, зависимое значение), если я правильно помню. Вы можете загрузить Foolproof из:
Visual Studio (2017) -> Инструменты -> Диспетчер пакетов Nuget -> Управление пакетами Nuget для решения. Ссылка на mvcfoolproof.unobtrusive.min.js в дополнение к файлам jquery.

person Jaggan_j    schedule 27.12.2017

ознакомьтесь с справочником по Git в библиотеке ExpressiveAnnotations .net

Он имеет атрибуты проверки 'RequiredIf' и 'AssertThat'.

person DiTap    schedule 14.07.2020

Я решил эту проблему, расширив _1 _, заимствуя некоторую логику из CompareAttribute < / a> и отличное решение Роберта:

/// <summary>
/// Provides conditional <see cref="RequiredAttribute"/> 
/// validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : RequiredAttribute
{
    /// <summary>
    /// Gets or sets a value indicating whether other property's value should
    /// match or differ from provided other property's value (default is <c>false</c>).
    /// </summary>
    public bool IsInverted { get; set; } = false;

    /// <summary>
    /// Gets or sets the other property name that will be used during validation.
    /// </summary>
    /// <value>
    /// The other property name.
    /// </value>
    public string OtherProperty { get; private set; }

    /// <summary>
    /// Gets or sets the other property value that will be relevant for validation.
    /// </summary>
    /// <value>
    /// The other property value.
    /// </value>
    public object OtherPropertyValue { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
    /// </summary>
    /// <param name="otherProperty">The other property.</param>
    /// <param name="otherPropertyValue">The other property value.</param>
    public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
        : base()
    {
        OtherProperty = otherProperty;
        OtherPropertyValue = otherPropertyValue;
    }

    protected override ValidationResult IsValid(
        object value, 
        ValidationContext validationContext)
    {
        PropertyInfo otherPropertyInfo = validationContext
            .ObjectType.GetProperty(OtherProperty);
        if (otherPropertyInfo == null)
        {
            return new ValidationResult(
                string.Format(
                    CultureInfo.CurrentCulture, 
                    "Could not find a property named {0}.", 
                validationContext.ObjectType, OtherProperty));
        }

        // Determine whether to run [Required] validation
        object actualOtherPropertyValue = otherPropertyInfo
            .GetValue(validationContext.ObjectInstance, null);
        if (!IsInverted && Equals(actualOtherPropertyValue, OtherPropertyValue) ||
            IsInverted && !Equals(actualOtherPropertyValue, OtherPropertyValue))
        {
            return base.IsValid(value, validationContext);
        }
        return default;
    }
}

Пример использования:

public class Model {
    public bool Subscribe { get; set; }
    
    [RequiredIf(nameof(Subscribe), true)]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
}

Таким образом, вы получите все стандартные Required функции проверки.

N.B .: Я использую .NET 5, но я попытался удалить языковые функции, добавленные в C # 9.0, для более широкой совместимости.

person Connor Low    schedule 24.05.2021

Я не могу дать вам именно то, о чем вы просите, но вы не задумывались о чем-то вроде следующего?

public abstract class Document // or interface, whichever is appropriate for you
{
    //some non-validted common properties
}

public class ValidatedDocument : Document
{
    [Required]
    public string Name {get;set;}
}

public class AnotherValidatedDocument : Document
{
    [Required]
    public string Name {get;set;}

    //I would suggest finding a descriptive name for this instead of Name2, 
    //Name2 doesn't make it clear what it's for
    public string Name2 {get;set;}
}

public class NonValidatedDocument : Document
{
    public string Name {get;set;}
}

//Etc...

Обоснованием является переменная int DocumentType. Вы можете заменить это использованием конкретных подклассов для каждого «типа» документа, с которым вам нужно иметь дело. Это дает вам гораздо лучший контроль над аннотациями свойств.

Также кажется, что только некоторые из ваших свойств необходимы в разных ситуациях, что может быть признаком того, что ваш класс документа пытается сделать слишком много и поддерживает указанное выше предложение.

person Patrick Allwood    schedule 14.10.2014