2

I have created a custom group validator that ensure that the user has filled in at least one field from a given group. This works when I load the page and submit the form without entering anything.

Invalid Form

enter image description here

Valid Form

enter image description here

However, the issue is whenever the form has any error other than the group validation error. When I force the form to display the group required validation error and then display, for example, a string length error on one of the fields in the group the group validation error doesn't disappear.

enter image description here

After a bit of debugging I'm fairly certain that its the client side validation that is not working correctly as its not removing the server side validation error whenever a client side validation is generated. When the form generates the string length error it prevents the form from being posted to the server which would remove the group validation required error.

Can anyone see an issue with the client side validation code? I copied the code from this stackoverflow page MVC3 unobtrusive validation group of inputs and and changed it to meet my needs. This is the first time I have attempted custom validation so forgive me if the issue is something really simple. Another indicator is that whenever I enter and then remove a single character the group validation error doesn't turn on/off.

I have provided the custom validator code below:

Custom Validator C# (Serverside and Clientside)

public class RequireAtLeastOneIfAttribute : ValidationAttribute, IClientValidatable
{
    private string[] Properties { get; set; }
    public RequireAtLeastOneIfAttribute(params string[] properties)
    {
        Properties = properties;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
      if(Properties == null || Properties.Length < 1)
      {
         return null;
      }

      foreach (var property in Properties)
      {
         var propertyInfo = validationContext.ObjectType.GetProperty(property);
         var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);

         if (propertyInfo == null)
         {
            return new ValidationResult(string.Format("Unknown property {0}", property));
         }

         if (propertyValue is string & !string.IsNullOrEmpty(propertyValue as string))
         {
            return null;
         } 

         if (propertyValue != null)
         {
            return null;
         }
      }

      return new ValidationResult("At least one age group field is required");
   }

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

      rule.ValidationParameters["properties"] = string.Join(",", Properties);

      yield return rule;
   }
}

Javascript

The only changes I made to the javascript is the id.

jQuery.validator.unobtrusive.adapters.add(
    'requireatleaseoneif', ['properties'], function (options) {
        options.rules['requireatleaseoneif'] = options.params;
        options.messages['requireatleaseoneif'] = options.message;
    }
);

jQuery.validator.addMethod('requireatleaseoneif', function (value, element, params) {
    var properties = params.properties.split(',');
    var values = $.map(properties, function (property, index) {
        var val = $('#' + property).val();
        return val != '' ? val : null;
    });
    return values.length > 0;
}, '');

Razor

@Html.RadioButtonFor(m => m.HaveDependants, true, new { id = "borrower-haveDependants-true", @class = "block-radio-input" })
@Html.RadioButtonFor(m => m.HaveDependants, false, new { id = "borrower-haveDependants-false", @class = "block-radio-input" })             
@Html.ValidationMessageFor(m => m.HaveDependants)

@Html.LabelFor(m => m.DependantsAgeGroupOne)                            
@Html.TextBoxFor(m => m.DependantsAgeGroupOne, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.DependantsAgeGroupOne)

@Html.LabelFor(m => m.DependantsAgeGroupTwo)
@Html.TextBoxFor(m => m.DependantsAgeGroupTwo, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.DependantsAgeGroupTwo)

@Html.LabelFor(m => m.DependantsAgeGroupThree)
@Html.TextBoxFor(m => m.DependantsAgeGroupThree, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.DependantsAgeGroupThree)

I also that the issue might be with how its triggered. Because the validation attribute isn't on the text input themselves but rather on the radio button I added the following javascript to validate on the whole form on button click.

<script>
    $(document).ready(function () {
        $(form).validate;
    });
</script>

I changed the last bit of Javascript to the following

$('input').blur(function () {
   $('.haveDependants').valid();
})
Community
  • 1
  • 1
CodeMonkey
  • 119
  • 4
  • 12
  • Half of your server-side code is missing. Also, by changing the "id" in the javascript code, you mean you decided that you should change the meaningful name of `atleastonerequired` to the incorrect name of `requireatleaseoneif`? Good job on that! – ataravati Dec 11 '15 at 00:27
  • Did you need to put your two cents in? Your answer is not helpful. It does make sense if you knew the hole story. 'requireatleastoneif - the radio button is set to true. so in the future keep your smart comments to yourself. Thanks! – CodeMonkey Dec 11 '15 at 00:43
  • There a some potential problems with your code. The validation will be fired irrespective of the value of `HaveDependants` and I assume you only want it to fire when its true as indicated by the name RequireAtLeastOne**If** (no where do you actually check the value). Next validation will fail if this is part of a complex object because of your use of `var val = $('#' + property).val();`. But the last script you have shown is just validating the form when the document is first loaded, not _on button click_ Have you posted the correct code? –  Dec 11 '15 at 02:08
  • Yes my intention is to only validate if the radio button is true however I am trying to just get the client side group validation working as is. It has since occurred to me that the last piece of JavaScript is wrong. I've added the new code to the question however this is not working either. – CodeMonkey Dec 11 '15 at 02:17
  • @CodeMonkey (see how this comment starts). Your radio buttons do not have `class='haveDependants'` so `$('.haveDependants').valid();` does nothing because there are no elements with that class name –  Dec 11 '15 at 02:27
  • @CodeMonkey, And if you want to validate an individual element, then it needs to be `$('form').validate().element(yourSelector);` –  Dec 11 '15 at 02:30
  • Whoop! I forgot to add that these inputs now have these classes. – CodeMonkey Dec 11 '15 at 02:30
  • The interesting thing is now I now get a 'Uncaught TypeError: Cannot read property 'name' of undefined' error and when I look at the html it is decorated with all the correct attribute so it should work. – CodeMonkey Dec 11 '15 at 02:32

1 Answers1

-1

I had the same problem with my custom validation attribute. Default attributes were preventing posting a form to the server and my custom attribute was ignored. So this is what I did.

  1. Disable client side form validation:

    @{ Html.EnableClientValidation(false); }

  2. In controller called:

if (!ModelState.IsValid) { return View(model); }

Robert Columbia
  • 6,313
  • 15
  • 32
  • 40
Teodor Głaz
  • 61
  • 1
  • 5