0

Not used to ask questions here but there is a first time for everything.

I'm currently working on a web project in ASP.NET MVC and I am stuck at the very first form... What I'm trying to do is to display a group of checkboxes and require at least one of them to be checked. Fortunately, I already found several answers on the matter and it works... only on the server side.

But I can't figure out why it is not working on the client side! Although all my other fields custom rules apply well on client side! I'm still rather new to this tech so maybe I am missing something here...

I've been on it for days now. I hope someone here will be kind enough to help me.

Below is my code around the checkboxes :

View

<div class="col-sm-3">
    <label>Documents:<br/>(select at least one)*</label><br/>
    @Html.ValidationMessageFor(m => m.AllDocumentsChecked, "", new @class = "text-danger" })
</div>
<div class="col-sm-3">
    <div class="form-check">
        @Html.CheckBoxFor(m => m.AllDocumentsChecked, new { @class = "form-check-input", @id = "AllDocumentsChecked", @name = "outputFiles" })
        <label for="select_all" class="form-check-label">All Documents</label>
    </div>
    <div class="form-check">
        @Html.CheckBoxFor(m => m.Doc1Checked, new { @class = "form-check-input check", @id = "Doc1Checked", @name = "outputFiles" })
        <label for="file_doc1" class="form-check-label">Doc1</label>
    </div>
    <div class="form-check">
        @Html.CheckBoxFor(m => m.Doc2Checked, new { @class = "form-check-input check", @id = "Doc2Checked", @name = "outputFiles" })
        <label for="file_doc2" class="form-check-label">Doc2</label>
    </div>
</div>
<div class="col-sm-3">
    <div class="form-check">
        @Html.CheckBoxFor(m => m.Doc3Checked, new { @class = "form-check-input check", @id = "Doc3Checked", @name = "outputFiles" })
        <label for="file_doc3" class="form-check-label">Doc3</label>
    </div>
    <div class="form-check">
        @Html.CheckBoxFor(m => m.Doc4Checked, new { @class = "form-check-input check", @id = "Doc4Checked", @name = "outputFiles" })
        <label for="file_doc4" class="form-check-label">Doc4</label>
    </div>
    <div class="form-check">
        @Html.CheckBoxFor(m => m.Doc5Checked, new { @class = "form-check-input check", @id = "Doc5Checked", @name = "outputFiles" })
        <label for="file_doc5" class="form-check-label">Doc5</label>
    </div>
</div>
<div class="col-sm-3">
    <div class="form-check">
        @Html.CheckBoxFor(m => m.Doc6Checked, new { @class = "form-check-input check", @id = "Doc6Checked", @name = "outputFiles" })
        <label for="file_doc6" class="form-check-label">Doc6</label>
    </div>                                
</div>

Model

[Display(Name = "All Documents")]
[RequireAtLeastOneOfGroup("Documents")]
public bool AllDocumentsChecked { get; set; }

[Display(Name = "Doc1")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc1Checked { get; set; }

[Display(Name = "Doc2")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc2Checked { get; set; }

[Display(Name = "Doc3")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc3Checked { get; set; }

[Display(Name = "Doc4")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc4Checked { get; set; }

[Display(Name = "Doc5")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc5Checked { get; set; }

[Display(Name = "Doc6")]
[RequireAtLeastOneOfGroup("Documents")]
public bool Doc6Checked { get; set; }

RequireAtLeastOneFromGroupAttribute class

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class RequireAtLeastOneOfGroupAttribute : ValidationAttribute, IClientValidatable
{
    public string GroupName { get; private set; }

    public RequireAtLeastOneOfGroupAttribute(string groupName)
    {
        ErrorMessage = string.Format("You must select at least one value from this group", groupName);
        GroupName = groupName;
    }        

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        foreach (var property in GetGroupProperties(validationContext.ObjectType))
        {
            var propertyValue = (bool)property.GetValue(validationContext.ObjectInstance, null);
            if (propertyValue)
            {
                // at least one property is true in this group => the model is valid
                return ValidationResult.Success;
            }
        }
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }

    private IEnumerable<PropertyInfo> GetGroupProperties(Type type)
    {
        return
            from property in type.GetProperties()
            where property.PropertyType == typeof(bool)
            let attributes = property.GetCustomAttributes(typeof(RequireAtLeastOneOfGroupAttribute), false).OfType<RequireAtLeastOneOfGroupAttribute>()
            where attributes.Count() > 0
            from attribute in attributes
            where attribute.GroupName == GroupName
            select property;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var groupProperties = GetGroupProperties(metadata.ContainerType).Select(p => p.Name);
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage
        };
        rule.ValidationType = string.Format("group", GroupName.ToLower());
        rule.ValidationParameters.Add("group", string.Join(",", groupProperties));
        //rule.ValidationParameters["propertynames"] = string.Join(",", groupProperties);
        yield return rule;
    }
}

require_at_least_one_from_group javascript

$.validator.unobtrusive.adapters.add('atleastone', ['propertynames'],
function (options) {
    options.rules['group'] = { propertynames: options.params.propertynames.split(',') };
    options.messages['group'] = options.message;
});

$.validator.addMethod('group', 
function (value, element, params) {
    var properties = params.properties.split(',');
    var isValid = false;
    for (var i = 0; i < properties.length; i++) {
        var property = properties[i];
        if ($('#' + property).is(':checked')) {
            isValid = true;
            break;
        }
    }
    return isValid;
}, '');

EDIT

Example of html tagging for a checkbox

Below the html resulting from the helpers above. Still can't see what is wrong.

<input class="form-check-input check" data-val="true" data-val-atleastone="You must select at least one value from this group" 
data-val-atleastone-group="AllDocumentsChecked,Doc1Checked,Doc2Checked,
Doc3Checked,Doc4Checked,Doc5Checked,Doc6Checked" data-val-required="The Doc1 field is required." 
id="Doc1Checked" name="Doc1Checked" value="true" type="checkbox">
<input name="Doc1Checked" value="false" type="hidden">

Thanks by advance!

OeufMayo
  • 1
  • 3
  • It seem all check boxes have same name. remove the name attribute and check . As you are using strongly typed model, you can remove id and name – user9405863 Apr 10 '18 at 14:56
  • what error you are getting – user9405863 Apr 10 '18 at 15:58
  • Not directly related to your case, but you might be interested in [mvc-collectionvalidation](https://github.com/stephenmuecke/mvc-collectionvalidation). As a side note, your `new { @name = "outputFiles" }` does absolutely nothing which is fortunate because if i t did change the `name` attribute, model binding would fail –  Apr 10 '18 at 21:40
  • @user9405863 Tried to remove the name attribute and it didn't change the result. Actually there is no error (at least displayed), but the issue here is that when I submit the form, the client side validation doesn't trigger for my checkbox group! Nevertheless, (once the other fields are provided correctly), server side validation works, as I see the validation error message appearing like for the other fields. – OeufMayo Apr 11 '18 at 10:34
  • @StephenMuecke That plugin seems interesting, I will give it a try and let you know, thanks! Plus, I didn't notice for the name attribute, thanks for the info. Seems fortunate indeed! – OeufMayo Apr 11 '18 at 10:35
  • I just retrieved the topic I tried to implement here, if it can help : [https://stackoverflow.com/questions/7247748/mvc3-validation-require-one-from-group](https://stackoverflow.com/questions/7247748/mvc3-validation-require-one-from-group) – OeufMayo Apr 11 '18 at 14:41

0 Answers0