-1

I have multiple checkboxes in my view and when testing different use cases, I noticed that if I do not select any textbox and hit the submit button it will redirect me to the next page. But in fact I want to add like a required attribute if possible for the user to select at least 1 checkbox. I tried adding the required attribute to the first checkbox but that would only mean that the user must select the first checkbox which is not what I want. Is this possible in .cshtml?

This is my view:

@using (Html.BeginForm("Data", "Controller"))
{
    for (int i = 0; i < Model.items.Count; i++)
    {
        int counter = 0;

        @Html.CheckBoxFor(r => Model.items[i].IsSelected)
        <label>  @Model.items[i].te</label><br />

        @Html.HiddenFor(h => @Model.items[i].Id)
        @Html.HiddenFor(h => @Model.items[i].Name)

        if (Model.items[i].Selected)
        {
            counter++;
        }
        else if (counter < 1)
        {
           RequiredCustom custom = new();

           custom.IsValid(Model.items);
        }



    }
        
    <input id="Button" type="submit" value="Next" />

}
MarkCo
  • 810
  • 2
  • 12
  • 29
  • In the post description your are talking about check-boxes. But the view above contains `radio-buttons`. – Jackdaw Aug 18 '21 at 18:23
  • Sorry, I copied the incorrect view, please see the updated post @Jackdaw – MarkCo Aug 18 '21 at 19:15
  • No, it is not possible, you have to validate your model.plans contains at least 1 selected plain, (see custom data validations in c#) and then you could check if this property is valid and show custom error if not e.g. https://stackoverflow.com/questions/21263063/check-model-state-of-a-property – croban Aug 18 '21 at 19:42
  • Here an example for custom attribute https://stackoverflow.com/a/33495692/2302522 try to implement similar that would fit your need. – croban Aug 18 '21 at 19:49
  • @croban Thank you for sharing these with me. I was trying to implement either or but got stuck into this. Please see updated post where I show the controller that handles this – MarkCo Aug 18 '21 at 20:09
  • you have to validate instance property data.plans, means custom attribute must be over Plan.API.Models.ViewPhoneNumberInput.plans property. Required attr. Won't be enough you have to create custom req.attr which fits your need (iterate over items and returns true as soon as an item isselected=true. – croban Aug 18 '21 at 20:57
  • SOrry I am not following you. I updated the post and in the second controller I did `if(temp.Count==0)` and it will enter the condition and write an error log and return the user back to the same page but I cannot get the custom error message from plans to display like I did for my phone number textfield which has a property in another model class named ViewPhoneNumberInput @croban – MarkCo Aug 18 '21 at 21:18
  • public class ViewPhoneNumberInput { ... [RequiredCustom(ErrorMessage = "Please select one chekbox")] public List plans { get; set; } } – croban Aug 19 '21 at 08:36
  • I follow now, so whenever you state I must iterate over items and returns true as soon as an item isselected = true, I would need to do that in the View correct? I.e. inside of that for loop that is already there iterating through the plans, before the loop closes I would do `if (Model.plans[i].IsSelected) return true;`? @croban – MarkCo Aug 19 '21 at 14:31
  • @MarkCo I have updated the answer, fully customized according to your scenario. Hope it would resolve your problem. – Md Farid Uddin Kiron Aug 20 '21 at 07:09

1 Answers1

1

Well, If you don't want to use javascript then you have to use custom validator on asp.net backend side. Here is the complete steps how you could do this.

Custom Validator Method:

public class RequiredCustom : ValidationAttribute
        {
            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                var viewModel = (ViewPhoneNumberInput)validationContext.ObjectInstance;

                var checkBoxCounter = 0;
                foreach (var plan in viewModel.plans)
                {
                    if(plan.IsSelected == true)
                    {
                        checkBoxCounter++;
                    }
                    if (plan.IsSelected == true && checkBoxCounter >0)
                    {
                        return new ValidationResult(ErrorMessage = "You have selected  "+ checkBoxCounter + " checkbox!");

                    }
                    else
                    {
                        return new ValidationResult(ErrorMessage == null ? "Please check one checkbox!" : ErrorMessage);
                    }
                    
                }

                return ValidationResult.Success;
            }
        }

Your Existing Model

public class ViewPhoneNumberInput
    {
        [Required(ErrorMessage = "You did not enter your phone number! Please enter your phone number!")]
        public String PhoneNumber { get; set; }
        [RequiredCustom(ErrorMessage = "Please select at least one checkbox")]
        public List<Plans> plans { get; set; }
    
    }

Views:

@model ViewPhoneNumberInput

@{ ViewBag.Title = " "; }

<h2>Select Your Plan</h2>

@using (Html.BeginForm("NewCustomerView", "StackOverFlow"))
{
    for (int i = 0; i < Model.plans.Count; i++)
    {
        int counter = 0;

        @Html.CheckBoxFor(r => Model.plans[i].IsSelected)
        <label>  @Model.plans[i].PlanName</label>


        @Html.HiddenFor(h => @Model.plans[i].PlanId)
        @Html.HiddenFor(h => @Model.plans[i].PlanName)

        <br />@Html.ValidationMessageFor(r => Model.plans)<br />
    }
    <p><strong>Phone Number</strong></p>
    @Html.TextBoxFor(r => Model.PhoneNumber)
    <p>  @Html.ValidationMessageFor(r => Model.PhoneNumber) </p>


    <input id="Button" type="submit" value="Next" />

}

Output:

enter image description here

Update:

I would preferably handle this following way:

 public class RequiredCustom : ValidationAttribute
        {
            protected override ValidationResult IsValid(object value, ValidationContext validationContext)
            {
                var viewModel = (ViewPhoneNumberInput)validationContext.ObjectInstance;

                var checkBoxCounter = 0;
                foreach (var plan in viewModel.plans)
                {
                    if(plan.IsSelected == true)
                    {
                        checkBoxCounter++;
                    }
                    if (plan.IsSelected == true && checkBoxCounter == 1)
                    {
                        return new ValidationResult(ErrorMessage = "You have selected checkbox!");

                    }
                    else
                    {
                        return new ValidationResult(ErrorMessage == null ? "Please check one checkbox!" : ErrorMessage);
                    }
                    
                }

                return ValidationResult.Success;
            }
        }

Note: This is the solution for the exception you were getting System.InvalidCastException: 'Unable to cast object of type 'PlanService.API.Models.ViewPhoneNumberInput' to type 'PlanService.API.Models.Plans. Because you were passing a list to the validator but previously it was expecting single list. Now its alright.

Now the validator model expecting a list of plans and can act accordingly.

Update Output

enter image description here

Hope it would help you to achieve your goal.

Md Farid Uddin Kiron
  • 16,817
  • 3
  • 17
  • 43
  • He is asking for [... if possible for the user to select at least 1 checkbox...] your custom validator checks if every item inside List<> is selected – croban Aug 19 '21 at 08:43
  • Ha ha, if you can set a condition for one item will that be a big deal for `List`? He needs to iterate of the iteam and finally check if at least one iteam selected. Just need to loop over, as he already knows. – Md Farid Uddin Kiron Aug 19 '21 at 09:07
  • :) i'm not sure if he knows that – croban Aug 19 '21 at 09:22
  • If the OP requires, that also be provided. No worries – Md Farid Uddin Kiron Aug 19 '21 at 09:30
  • Thank you! Yes, as @croban mentioned I am looking to set a required attribute that will display a custom error message to the user if they have not selected at least one checkbox. WOuld I need to perform the iteration inside of the view? – MarkCo Aug 19 '21 at 14:33
  • I implemented IsValid method in the same file that contains the class `ViewPhoneNumberInput` and when I ran the program it said System.InvalidCastException: 'Unable to cast object of type 'PlanService.API.Models.ViewPhoneNumberInput' to type 'PlanService.API.Models.Plans'.' Please see the updated post (***Updated View section and ViewPhoneNumberInput section***) – MarkCo Aug 19 '21 at 15:02
  • Yes, because you are passing the list to the validator though it takes a single instance object, but you have to loop through of your checkBox list and have to pass each item to the validator once, then you have set an counter, if the validator count at least one then you got the point that atleast one checkbox selected. And type casting error meaning you are passing list instead of single object which the validaor expects. Hope you got my points – Md Farid Uddin Kiron Aug 19 '21 at 15:28
  • I see, so what I did was made a foreach loop inside of the `RequiredCustom` class (see updated post) in order to achieve what you suggested. Now when it comes to passing each item to the validator once and setting a counter and making an if condition (if counter <= 1) call the `Isvalid()` method I should do this in the view correct? @MdFaridUddinKiron – MarkCo Aug 19 '21 at 16:19
  • Correct! this way I mean, if you still encounter any further issue, no worries I will assist on that. – Md Farid Uddin Kiron Aug 19 '21 at 16:46
  • Its was late night here, so I was asleep, so what problem you are having now? – Md Farid Uddin Kiron Aug 20 '21 at 01:09
  • 2
    @croban Solution provided that you were concerned about. – Md Farid Uddin Kiron Aug 20 '21 at 12:22
  • @MarkCo Its my pleasure to assist you on this. – Md Farid Uddin Kiron Aug 20 '21 at 14:55