user2948533
user2948533

Reputation: 1173

How to conditionally validate in Data Annotation in MVC?

I have the following Model:

public class PasswordResetModel {

public bool PasswordCreationStatus { get; set; }

[Display(Name = "AcctNumber", ResourceType = typeof(Resources.Register))]
    [ValidateMustHaveAtLeastOne("AtLeastOneShoudlbeThere", "")]
    //Account number is numeric
    [RegularExpression(@"^[0-9]+$", ErrorMessageResourceType = typeof(Resources.Register),
      ErrorMessageResourceName = "AccountNumberRegularExpr")]
    [ScriptIgnore]
    public int? AccountNumber { get; set; }

    [Display(Name = "BirthDate", ResourceType = typeof(Resources.Register))]
    [ValidateMustHaveAtLeastOne("AtLeastOneShoudlbeThere", "")]
    [ScriptIgnore]
    public string BirthDate { get; set; }

    [Display(Name = "SSN", ResourceType = typeof(Resources.Register))]
    [ValidateMustHaveAtLeastOne("AtLeastOneShoudlbeThere", "")]
    //Accepts only four digits
    [RegularExpression(@"^\d{4}$", ErrorMessageResourceType = typeof(Resources.Register),ErrorMessageResourceName = "SSNRegularExpr")]
    [StringLength(4, ErrorMessageResourceType = typeof(Resources.Register),ErrorMessageResourceName = "SSNRegularExpr")]
    [ScriptIgnore]
    public string SSN { get; set; }
}

I want to make ensure at least either of the 3 (Account Number,SSN and BirthDate) is entered.For which ValidateMustHaveAtleastOne class is witten below.But I want to do the validation only if PasswordCreationStatus is false.

And the ValidateMustHaveAtLeastOne class looks like this:

[AttributeUsage(AttributeTargets.Property)]
  public class ValidateMustHaveAtLeastOne : ValidationAttribute, IClientValidatable {

    #region Construnctor
    public ValidateMustHaveAtLeastOne(string groupName, string validationMessage) {
      GroupName = groupName;
      ValidationMessage = validationMessage;
    }
    #endregion

    #region Properties
    /// <summary>
    /// Name of the group of the properties
    /// </summary>
    public string GroupName { get; private set; }

    /// <summary>
    /// Vaidation message
    /// </summary>
    public string ValidationMessage { get; private set; }

    #endregion

    #region Public overrides
    /// <summary>
    /// Validates the group of properties.
    /// </summary>
    /// <param name="value">The value to validate.</param>
    /// <param name="context">The context information about the validation operation.</param>
    /// <returns>An instance of the ValidationResult class.</returns>
    protected override ValidationResult IsValid(object value, ValidationContext context) {
      foreach (var property in GetGroupProperties(context.ObjectType)) {
        var propertyValue = property.GetValue(context.ObjectInstance, null);
        if (propertyValue != null) {
          return null;
        }
      }
      return new ValidationResult(ValidationMessage);
    }
    #endregion

    #region Implementation of IClientValidateable
    /// <summary>
    /// To enable client side implementation of same validtion rule.
    /// </summary>
    /// <param name="metadata">The model metadata.</param>
    /// <param name="context">The controller context.</param>
    /// <returns>The client validation rules for this validator.</returns>

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
      var groupProperties = GetGroupProperties(metadata.ContainerType);
      List<string> propertyNames = new List<string>();
      foreach (var property in groupProperties) {
        propertyNames.Add(property.Name);
      }
      var rule = new ModelClientValidationRule {
        ErrorMessage = this.ValidationMessage
      };
      rule.ValidationType = string.Format("group", GroupName.ToLower());
      rule.ValidationParameters["propertynames"] = string.Join(",", propertyNames);
      yield return rule; // yield key word is used as return type is  declared as IEnumerable<ModelClientValidationRule>
    }
    #endregion

        /// Returns the group of properties that implements this attribute
    /// </summary>
    /// <param name="type">Type of the Model</param>
    /// <returns>List of properties</returns>
    private IEnumerable<PropertyInfo> GetGroupProperties(Type type) {
      var propertyInfo = new List<PropertyInfo>();
      foreach (PropertyInfo property in type.GetProperties()) {
        if (property.GetCustomAttributes(typeof(ValidateMustHaveAtLeastOne), false).GetLength(0) > 0) {
          propertyInfo.Add(property);
        }
      }
      return propertyInfo;
    }


  }

I think its possible with minimum enhancement. Any help will be highly appreciated. Thanks in advance.

Upvotes: 0

Views: 1448

Answers (3)

Alex Art.
Alex Art.

Reputation: 8781

You can add a calculated property that is going to be valid only when at least one of the properties is provided:

public class PasswordResetModel 
{

    public bool PasswordCreationStatus { get; set; }

    [Display(Name = "AcctNumber", ResourceType = typeof(Resources.Register))]        
    //Account number is numeric
    [RegularExpression(@"^[0-9]+$", ErrorMessageResourceType = typeof(Resources.Register),
      ErrorMessageResourceName = "AccountNumberRegularExpr")]
    [ScriptIgnore]
    public int? AccountNumber { get; set; }

    [Display(Name = "BirthDate", ResourceType = typeof(Resources.Register))]        
    [ScriptIgnore]
    public string BirthDate { get; set; }

    [Display(Name = "SSN", ResourceType = typeof(Resources.Register))]      
    //Accepts only four digits
    [RegularExpression(@"^\d{4}$", ErrorMessageResourceType = typeof(Resources.Register),ErrorMessageResourceName = "SSNRegularExpr")]
    [StringLength(4, ErrorMessageResourceType = typeof(Resources.Register),ErrorMessageResourceName = "SSNRegularExpr")]
    [ScriptIgnore]
    public string SSN { get; set; }

    [Required(ErrorMessage = "Must Have At Least One of the properties")]
    public string MustHaveAtLeastOneIsValid {
       get{
           return  this.SSN != null || this.BirthDate != null || this.AccountNumber.HasValue ? "valid": null;
       }
    }
}

Upvotes: 0

user1672994
user1672994

Reputation: 10849

Updated your Validation to validate if the dependent property is matched with target value then do the validation.

[AttributeUsage(AttributeTargets.Property)]
public class ValidateMustHaveAtLeastOne : ValidationAttribute, IClientValidatable {

#region Construnctor
public ValidateMustHaveAtLeastOne(string groupName, string validationMessage, string dependentProperty, object targetValue) {
  GroupName = groupName;
  ValidationMessage = validationMessage;

  DependentProperty = dependentProperty;
  TargetValue = targetValue;
}
#endregion

#region Properties
/// <summary>
/// Name of the group of the properties
/// </summary>
public string GroupName { get; private set; }

/// <summary>
/// Vaidation message
/// </summary>
public string ValidationMessage { get; private set; }

/// <summary>
/// Gets or sets the dependent property.
/// </summary>
/// <value>
/// The dependent property.
/// </value>
public string DependentProperty { get; set; }

/// <summary>
/// Gets or sets the target value.
/// </summary>
/// <value>
/// The target value.
/// </value>
public object TargetValue { get; set; }

#endregion

#region Public overrides
/// <summary>
/// Validates the group of properties.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="context">The context information about the validation operation.</param>
/// <returns>An instance of the ValidationResult class.</returns>
protected override ValidationResult IsValid(object value, ValidationContext context) {
    var containerType = validationContext.ObjectInstance.GetType();
    var field = containerType.GetProperty(this.DependentProperty);
    if (field != null)
    {
        // get the value of the dependent property
        var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);

        // compare the value against the target value
        if ((dependentvalue == null && this.TargetValue == null)
            || (dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
        {
            foreach (var property in GetGroupProperties(context.ObjectType)) {
            var propertyValue = property.GetValue(context.ObjectInstance, null);
            if (propertyValue != null) {
              return null;
                }
          }
          return new ValidationResult(ValidationMessage);
        }
    }

  return ValidationResult.Success;
}
#endregion

#region Implementation of IClientValidateable
/// <summary>
/// To enable client side implementation of same validtion rule.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>The client validation rules for this validator.</returns>

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
  var groupProperties = GetGroupProperties(metadata.ContainerType);
  List<string> propertyNames = new List<string>();
  foreach (var property in groupProperties) {
    propertyNames.Add(property.Name);
  }
  var rule = new ModelClientValidationRule {
    ErrorMessage = this.ValidationMessage
  };
  rule.ValidationType = string.Format("group", GroupName.ToLower());
  rule.ValidationParameters["propertynames"] = string.Join(",", propertyNames);

  string depProp = this.BuildDependentPropertyId(metadata, context as ViewContext);
  // find the value on the control we depend on; if it's a bool, format it javascript style
  string targetValue = (this.TargetValue ?? string.Empty).ToString();
  if (this.TargetValue.GetType() == typeof(bool))
  {
     targetValue = targetValue.ToLower();
  }

  rule.ValidationParameters["dependentproperty"] = depProp;
  rule.ValidationParameters["targetvalue"] = targetValue;
  yield return rule; // yield key word is used as return type is  declared as IEnumerable<ModelClientValidationRule>
}
#endregion

    /// Returns the group of properties that implements this attribute
/// </summary>
/// <param name="type">Type of the Model</param>
/// <returns>List of properties</returns>
private IEnumerable<PropertyInfo> GetGroupProperties(Type type) {
  var propertyInfo = new List<PropertyInfo>();
  foreach (PropertyInfo property in type.GetProperties()) {
    if (property.GetCustomAttributes(typeof(ValidateMustHaveAtLeastOne), false).GetLength(0) > 0) {
      propertyInfo.Add(property);
    }
  }
  return propertyInfo;
}

 private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
    string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);

    // This will have the name of the current field appended to the beginning, because the TemplateInfo's context has had this fieldname appended to it.
    var thisField = metadata.PropertyName + "_";
    if (depProp.StartsWith(thisField, StringComparison.OrdinalIgnoreCase))
    {
        depProp = depProp.Substring(thisField.Length);
    }

    return depProp;
} 

}

Usage

 [ValidateMustHaveAtLeastOne("AtLeastOneShoudlbeThere", "", "PasswordCreationStatus", false)]

Upvotes: 1

Hakan Fıstık
Hakan Fıstık

Reputation: 19511

You may use an already done (tested) library for validation with some conditions.

There is a lot of libraries for this, but maybe the most famous one is foolproof

in the above link you could see the usage and documentation for this.

I think the following validation attribute is suitable for your case.

[RequiredIfTrue("PasswordCreationStatus")]

Upvotes: 1

Related Questions