1

I am implementing a RequiredIf validation attribute and the value being passed to the IsValid method is always null.

RequiredIfAttribute Class

    public class RequiredIfAttribute : ValidationAttribute
{
    private 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;
    }

    public override bool IsValid(object value)
    {
        return innerAttribute.IsValid(value);
    }
}

ViewModel

        [Required]
    [Display(Name = "Are You A Student?")]
    public bool? IsStudent { get; set; }

    [RequiredIf("IsStudent", true, ErrorMessage = "You must upload a photo of your student ID if you wish to register as a student.")]
    [Display(Name = "Student ID")]
    [FileExtensions("jpg|jpeg|png|pdf", ErrorMessage = "File is not in the correct format.")]
    [MaxFileSize(2 * 1024 * 1024, ErrorMessage = "You may not upload files larger than 2 MB.")]
    public HttpPostedFileBase StudentId { get; set; }

EditorTemplate

    @model bool?
@using System.Web.Mvc;
@{
    var options = new List<SelectListItem>
    {
        new SelectListItem { Text = "Yes", Value = "true", Selected =  Model.HasValue && Model.Value },
        new SelectListItem { Text = "No", Value = "false", Selected =  Model.HasValue && Model.Value }
    };

    string defaultOption = null;

    if (ViewData.ModelMetadata.IsNullableValueType)
    {
        defaultOption = "(Select)";
    }
}

@Html.DropDownListFor(m => m, options, defaultOption)

Every time the form is submitted, the RequiredIf error message is thrown and I have a feeling it has to do with the null value I described initially. What am I doing wrong? Thanks!

NOTE: The HTML appears to be rendering properly, so I don't think that's the problem.

    <select data-val="true" data-val-required="The Are You A Student? field is required." id="IsStudent" name="IsStudent"><option value="">(Select)</option>
<option value="true">Yes</option>
<option value="false">No</option>
</select>

2 Answers2

2

Because this is your code -

public class RequiredIfAttribute : ValidationAttribute
{
    private 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;
    }

    public override bool IsValid(object value)
    {
        return innerAttribute.IsValid(value);
    }
}

You are using a RequriedAtrribute. So to simulate that it behaves life a RequiredIf you have to implement some logic that will check whether the target property value is true or false. But you are not doing that and returning just from the innerattribute. So it is just a mere Required not RequiredIf -

public override bool IsValid(object value)
{
    return innerAttribute.IsValid(value);
}

modify this function to do some checking like -

public override bool IsValid(object value)
{
    //if the referred property is true then
        return innerAttribute.IsValid(value);
    //else
    return True
}
brainless coder
  • 6,310
  • 1
  • 20
  • 36
  • Thanks but my code works with other fields as is so I think you're barking up the wrong tree with that solution. It seems to be related to the fact that the field is nullable or that it is nullable and using a drop down as a selection mechanism. –  Jun 03 '14 at 17:54
  • @DomRocco you are barking up the wrong tree. Brainless Coder is 100% correct. – MemeDeveloper Mar 23 '19 at 16:17
0

I use the following code:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class StefAttribute : ValidationAttribute
{
    public WDCIAttribute()
        : base()
    {
        this.ErrorMessageResourceType = typeof(GlobalResources);
    }
}


[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class StefRequiredIfAttribute : StefAttribute
{
    private RequiredAttribute innerAttribute = new RequiredAttribute();
    public string DependentProperty { get; set; }
    public object TargetValue { get; set; }

    public WDCIRequiredIfAttribute()
    {
    }

    public WDCIRequiredIfAttribute(string dependentProperty, object targetValue)
        : base()
    {
        this.DependentProperty = dependentProperty;
        this.TargetValue = targetValue;
    }

    public override bool IsValid(object value)
    {
        return innerAttribute.IsValid(value);
    }
}

public class RequiredIfValidator : DataAnnotationsModelValidator<StefRequiredIfAttribute>
{
    public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, StefRequiredIfAttribute attribute)
        : base(metadata, context, attribute)
    {
    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        // get a reference to the property this validation depends upon
        var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);

        if (field != null)
        {
            // get the value of the dependent property
            object value = field.GetValue(container, null);

            // compare the value against the target value
            if (IsEqual(value) || (value == null && Attribute.TargetValue == null))
            {
                // match => means we should try validating this field
                if (!Attribute.IsValid(Metadata.Model))
                {
                    // validation failed - return an error
                    yield return new ModelValidationResult { Message = ErrorMessage };
                }
            }
        }
    }

    private bool IsEqual(object dependentPropertyValue)
    {
        bool isEqual = false;

        if (Attribute.TargetValue != null && Attribute.TargetValue.GetType().IsArray)
        {
            foreach (object o in (Array)Attribute.TargetValue)
            {
                isEqual = o.Equals(dependentPropertyValue);
                if (isEqual)
                {
                    break;
                }
            }
        }
        else
        {
            if (Attribute.TargetValue != null)
            {
                isEqual = Attribute.TargetValue.Equals(dependentPropertyValue);
            }
        }

        return isEqual;
    }
}

Which can be used in the model as follows:

public class PersonnelVM : EntityVM
{
    // . . .

    [DisplayName("Name")]
    [StefRequiredIf("IndividualOrBulk", PersonnelType.Bulk, ErrorMessageResourceName = GlobalResourceLiterals.Name_Required)]
    public string Name { get; set; }

    [DisplayName("PersonnelType")]
    public PersonnelType IndividualOrBulk { get; set; }

    // . . .
}
Stef Heyenrath
  • 9,335
  • 12
  • 66
  • 121
  • I'm not looking to implement client side validation with this and our server-side validation attributes have a very similar implementation. I appreciate you posting, but I don't see a solution here. –  Jun 03 '14 at 17:46