ASP.Net MVC: User must select one check box validate at client side

2.7k Views Asked by At

I have UI where i am showing 3 checkboxes and each refer to different property of model class. i am using jquery unobtrusive validation just by mvc data annotation. i want when user submit form then user has to select one checkbox otherwise client side error message will display and form will not be submitted.

i can do it by jquery but i want to do it by mvc data annotation.

see my model class

public class Customer
{
    [Required]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Display(Name = "Mail to me")]
    public bool SelfSend { get; set; }

    [Display(Name = "3rd party")]
    public bool thirdParty { get; set; }

    [Display(Name = "Others")]
    public bool Others { get; set; }
}

Controller

[ValidateAntiForgeryToken()]
[HttpPost]
public ActionResult Index(Customer customer)
{
    if (customer.Others == false || customer.SelfSend == false || customer.thirdParty == false)
        ModelState.AddModelError("Error", "Must select one option");

    return View();
}

with the below code i can validate any checkboxes is selected or not from server side code and add model error which show error at client side.

but i want to do validation by client side using normal data annotation.

see my razor code

<div class="row">
    <div class="col-md-8">
        <section id="testform">
            @using (Html.BeginForm("Index", "Customers", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()
                <h4>Enter customer info.</h4>
                <hr />
                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                <div class="form-group">
                    @Html.LabelFor(m => m.FirstName, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.FirstName, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(m => m.LastName, new { @class = "col-md-2 control-label" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.LastName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.LastName, "", new { @class = "text-danger" })
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.SelfSend)
                            @Html.LabelFor(m => m.SelfSend)
                        </div>
                    </div>

                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.thirdParty)
                            @Html.LabelFor(m => m.thirdParty)
                        </div>
                    </div>

                    <div class="col-md-offset-2 col-md-10">
                        <div class="checkbox">
                            @Html.CheckBoxFor(m => m.Others)
                            @Html.LabelFor(m => m.Others)
                        </div>
                    </div>
                    <div class="col-md-offset-2 col-md-10">
                        @Html.ValidationMessage("Error", "", new { @class = "text-danger" })
                    </div>
                    </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Save" class="btn btn-default" />
                    </div>
                </div>

            }
        </section>
    </div>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
2

There are 2 best solutions below

2
D-Shih On BEST ANSWER

You can try to write a customer model validation attribute.

add CheckBoxAuthAttribute in your one of three validation property.

There is a method protected virtual ValidationResult IsValid(object value, ValidationContext validationContext) in you can override inValidationAttribute.

public class CheckBoxAuthAttribute : ValidationAttribute
{
    public CheckBoxAuthAttribute(params string[] propertyNames)
    {
        this.PropertyNames = propertyNames;
    }

    public string[] PropertyNames { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
        var values = properties
                .Select(p => p.GetValue(validationContext.ObjectInstance, null))
                .OfType<bool>();

        if (values.Contains(true) || (bool)value == true)
        {
            return null;
        }
        return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
    }
}
public class Customer
{
    [Required]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Display(Name = "Mail to me")]
    [CheckBoxAuth("thirdParty", "Others", ErrorMessage = "Must select one option"))]
    public bool SelfSend { get; set; }

    [Display(Name = "3rd party")]
    public bool thirdParty { get; set; }

    [Display(Name = "Others")]
    public bool Others { get; set; }
}
2
AudioBubble On

Since you want one of 3 possible options to be selected, then use radio buttons and bind to a property with a required attribute.

Start by defining a view model

public class CustomerVM
{
    [Required]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }
    [Required]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }
    [Required]
    public int? Mailing { get; set; } // see notes below
}

And in the view

@model CustomerVM
....
@using (Html.BeginForm())
{
    ....
    <label>
        @Html.RadioButtonFor(m => m.Mailing, 1, new { id = ""})
        <span>Mail to me</span>
    </label>
    <label>
        @Html.RadioButtonFor(m => m.Mailing, 2, new { id = ""})
        <span>3rd party</span>
    </label>
    .... // ditto for "Others"
    @Html.ValidationMessageFor(m => m.Mailing)
    ....
}

and the POST method will be

[HttpPost]
public ActionResult Index(CustomerVM model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }
    .... // map to instance of data model, save and redirect
}

Note that if these options are unlikely to change, it would be more appropriate to make the property an enum rather than an int, for example

public enum Mailing
{
    [Display(Name = "Mail to me")]
    SelfSend = 1,
    [Display(Name = "3rd party")]
    ThirdParty = 2,
    [Display(Name = "Others")]
    Others = 3
}

public class CustomerVM
{
    ....
    [Required]
    public Mailing? Mailing { get; set; }
}

and the view code would be

@Html.RadioButtonFor(m => m.Mailing, Mailing.SelfSend, new { id = ""})