1

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")
}
Mist
  • 684
  • 9
  • 30
  • What problem are you facing? Is there an error? – Jasen Jul 21 '18 at 20:06
  • i have to use checkbox as per spec but one checkbox must be selected before form submitted. how to do client side validation by annotation? – Mist Jul 21 '18 at 20:07
  • You can use the [`RangeAttribute`](https://stackoverflow.com/a/32012576/2030565) but you will need some custom JavaScript to populate a hidden item. – Jasen Jul 21 '18 at 20:21
  • share some hint where i can use range attribute for bool property. share some url. thanks – Mist Jul 21 '18 at 20:31
  • There's a link in the comment. You are not validating the checkbox directly but computing the number of checkboxes selected. – Jasen Jul 21 '18 at 20:35
  • If you want both server and client side validation, refer [this project](https://github.com/stephenmuecke/mvc-collectionvalidation) for a `ValidationAttribute` you can apply to a collection of `bool` properties –  Jul 21 '18 at 22:21
  • @StephenMuecke Sir how to use your library for my scenario ? if possible please add some sample code. – Mist Jul 22 '18 at 03:54
  • @Mist, Since that project validates a collection, you would have a `CustomerViewModel` containing a `[RequireInCollection("IsSelected", true, Maximum = 1)]public List X { get; set; }` property where `XViewModel` contains properties `public string Name { get; set; }` and `public bool IsSelected { get; set; }` The collection would then be populated with 3 instances to represent each of the options (the `Name` would contain "Mail to me" etc) –  Jul 22 '18 at 09:28
  • However, in this case I would just use radio buttons and bind to a view model property with a `[Required]` attribute since you only want one of them to be selected (refer my answer) –  Jul 22 '18 at 09:31

2 Answers2

2

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; }
}
D-Shih
  • 44,943
  • 6
  • 31
  • 51
  • this validation fire at server side but i am looking for a attribute which will fire client side first too. – Mist Jul 21 '18 at 20:17
  • You can use jquery validation library with asp.net MVC validation `Attribute` here is a link wish can help you https://www.c-sharpcorner.com/article/asp-net-mvc5-jquery-form-validator/ – D-Shih Jul 21 '18 at 20:22
2

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 = ""})
  • Thank you sir. radio buttons are mutually exclusive but in my case i have to use checkbox because user may select 3 option or user one option. so guide me how to handle this situation if user select no option then i have to show a error message at client side by not normal data annotation. – Mist Jul 22 '18 at 15:50
  • Then use the [mvc-collectionvalidation](https://github.com/stephenmuecke/mvc-collectionvalidation) project I noted in the comments to your question –  Jul 22 '18 at 21:49