Search code examples
jqueryasp.net-mvcasp.net-mvc-4unobtrusive-validation

ASP.NET MVC Custom validation for a List<string>


I am using ASP.NET MVC 5 and on the client side want to use JQuery unobtrusive validation.

Following is my model:

 public class CompanyModel
    {
        public CompanyModel()
        {
            Employees = new List<EmployeeModel>();              
        }
        public int CompanyId{ get; set; }

        public List<EmployeeModel> Employees { get; set; }        
    }

    public class EmployeeModel
    {
        public EmployeeModel()
        {
            Values = new List<string>();
        }

        public string Id { get; set; }

        public string Name { get; set; }

        [RequiredIf("IsRequired", true, "Atleast one value is required")]
        public List<string> Values { get; set; }

        public bool IsRequired { get; set; }
    }

I was able to implement the RequiredIf custom attribute successfully on the server side. But I'm struggling to get the client side validation going...

In the view I loop through the employees list and the values collection is bound

@for (var index = 0; index < Model.Employees.Count; index++)
{
      /// some other code

    @for (int i = 0; i < Model.employees[index].Values.Count; i++)
    {
        @Html.TextBoxFor(m => m.Employees[index].Values[i], new {@autocomplete = "false" })
    }    
 }

The IsRequired property is a hidden field:

@Html.HiddenFor(m => m.Employees[index].IsRequired)

The following is the code I have so far for the GetClientValidationRules method.

  public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
            {
                var rule = new ModelClientValidationRule
                {
                    ErrorMessage = ErrorMessage,
                    ValidationType = "requiredif"
                };               

                rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
                rule.ValidationParameters["dependentpropertyvalue"] = DependentPropertyValue.ToString().ToLower();

                yield return rule;            
            }

I don't see the validation html (data-val-*) attributes added to the values in the HTML markup. And I don't expect them too as I think I am missing something. How do I get all the elements of the values collection populated with the data-val-requiredif attributes in html.

Any ideas?

FYI: the dependentpropertyId in html is populated like so CompanyModel.Employees_0_IsRequired for Employee[0].


Solution

  • There is little point including the GetClientValidationRules() method in your validation attribute, because its purpose is to add the data-val-* attributes to the form control generated for that property, ad you don't generate an input for property Values (and if you did, binding would fail). One way to solve this is to handle the forms .submit() event, check if at least one item in the collection has a value, and if not cancel the submit and display the error.

    Modify your view to include the validation message place holder and add a class name to the inputs for selection

    @for (var index = 0; index < Model.Employees.Count; index++)
    {
        ....
        <div class="value-group"> // necessary for relative selectors
            @for (int i = 0; i < Model.employees[index].Values.Count; i++)
            {
                @Html.TextBoxFor(m => m.Employees[index].Values[i], new { @class="value", autocomplete = "false" })
            }
            @Html.ValidationMessageFor(m => m.Employees[index].Values)
       </div>
    }
    

    And include the following script

    $('form').submit(function() { // use an id selector if you have added one to the form
        var isValid = true;
        var groups = $('.value-group');
        $.each(groups, function(index, item) {
            var group = $(this);
            var inputs = group.find('.value');
            if (inputs.filter(function () { return $(this).val().length > 0; }).length == 0) {
                isValid = false;
                group.find('span:last').append($('<span></span>').text('At least one value is required')).addClass('field-validation-error').removeClass('field-validation-valid');
            }
        });
        return isValid;
    });
    

    You may also want to add another script to handle the change event of each input to remove the associated error message if any of the inputs in the group now have a value