How do I use a SelectListItem in ASP.NET MVC to display choices via a List Box or Check List box

1.9k Views Asked by At

I've been reading several articles about how to present choices to users. Some use ListBoxFor, some use CheckBoxFor, and then there is this thing called MultiSelectList.

What I am (was) confused about was that each example seemed to have done it a totally different way, and none of them actually used the built in "SelectListItem" class but instead always created their own.

So I originally was going to post a question asking for general clarification, but I thought it would just be representative of all the other various post and repetitive.

So let me re-phrase: How do you use a "List" or a "MultiSelectList" to present a user a list of choices, including the option for them to be displayed as a list of Check boxes?

In other words, if I have the following 2 items in my Model, how would I display each of them as a typical choice list box, or a typical Check List box?

public List<SelectListItem> Widgets1 { get; set; }
public MultiSelectList Widgets2 { get; set; }
2

There are 2 best solutions below

0
da_jokker On

So let's start with the Model. It is really basic and you can see that I'm just creating 3 Lists that will store the same data.

The primary difference here is that all of the examples I read, people where creating their own Item classes, where I wanted to simply use the built in "SelectListItem" class

public class FooModel
{
  [Display(Name = "WidgetCheckList")]
  public List<SelectListItem> WidgetsAsCheckList { get; set; }

  [Display(Name = "WidgetListBox")]
  public List<SelectListItem> WidgetsAsListBox { get; set; }

  [Display(Name = "WidgetMultiSelectList")]
  public MultiSelectList WidgetMultiSelectList { get; set; }

  //We have to create a bucket that not only some how 
  //auto-magically knowns what has been pre-selected in the 
  //original list, but provides the view something to store
  //the new selections in when returning to the controller.
  //I have to admit, I have no idea how this knows what was
  //pre-selected, but being new at MVC, there are things I 
  //just have to leave it as a mystery becuase it just works.

  [HiddenInput(DisplayValue = false)]
  public List<string> userSelectionsAsListBox { get; set; }

  [HiddenInput(DisplayValue = false)]
  public List<string> userSelectionsAsMultiSelectList { get; set; }

  public FooModel()
  {
    this.WidgetsAsCheckList = new List<SelectListItem>();
    this.WidgetsAsListBox = new List<SelectListItem>();
    this.WidgetMultiSelectList = new MultiSelectList(new List<SelectListItem>());
  }
}   

For the Controller, because this was a learning test, I just made up the data. The key here is that I build a List of SelectListItems and then used that same list to populate all 3 demo fields of the Model to show 3 different ways to work with the same data.

// ------------------------------------------------------------------------
[HttpGet]       //Display the Edit view
public ActionResult Edit()
{
    FooModel myModel = new FooModel;

    //For testing, here I'm going to Inject some Choices
    //So first we build a list of them
    List<SelectListItem> myChoices = new List<SelectListItem>();
    for (Int32 myIndex = 1; myIndex < 15; myIndex++)
    {
        SelectListItem myChoice = new SelectListItem();
        myChoice.Value = myIndex.ToString();
        myChoice.Text = "Choice " + myIndex.ToString();
        if ((myIndex % 2) == 0)
        {
            myChoice.Selected = true;
        }
        else
        {
            myChoice.Selected = false;
        }
        myChoices.Add(myChoice);
    }

    String[] mySelections = myChoices.Where(x => x.Selected == true).ToArray().Select(x => x.Value).ToArray();

    //Now we use that same list to populate all 3 variations in our model
    myModel.WidgetsAsCheckList.AddRange(myChoices);
    myModel.WidgetsAsListBox.AddRange(myChoices);
    myModel.WidgetMultiSelectList = new MultiSelectList(myChoices, "Value", "Text", mySelections); 

    return View(myModel);
}

Now for the view, I display each list. The first of course is a check box and the second and third are list boxes but use different underlying objects...

@* This displays the "list" of SelectListItems as Checkboxes but we have to do alot more work *@
<div class="form-group">
    @Html.LabelFor(model => model.WidgetsAsCheckList, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        <div class="form-control" style="overflow-y: scroll; height: 25em; width:280px;">
            @for (var myIndex = 0; myIndex < Model.WidgetsAsCheckList.Count; myIndex++)
            {
                @Html.CheckBoxFor(cc => cc.WidgetsAsCheckList[myIndex].Selected, new { htmlAttributes = new { @class = "form-control" } })
                @Html.HiddenFor(cc => cc.WidgetsAsCheckList[myIndex].Value, new { htmlAttributes = new { @class = "form-control" } })
                @Html.DisplayFor(cc => cc.WidgetsAsCheckList[myIndex].Text, new { htmlAttributes = new { @class = "form-control" } })
                <br />
            }
        </div>
    </div>

</div>

 @* This displays the "list" of SelectListItems as list box that does all the work for us *@
<div class="form-group">
    @Html.LabelFor(model => model.WidgetsAsListBox, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">                
        @Html.ListBoxFor(model => model.userSelectionsAsListBox, Model.WidgetsAsListBox, new { @class = "form-control", size = 25 })
    </div>
</div>

@* This displays the "MultiSelectList" as list box that does all the work for us *@
<div class="form-group">
    @Html.LabelFor(model => model.WidgetMultiSelectList, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.ListBoxFor(model => model.userSelectionsAsMultiSelectList, Model.WidgetMultiSelectList, new { @class = "form-control", size = 25 })
    </div>
</div>

And finally, when the user makes their own selections (or takes the pre-selected ones) and hits submit, we can get the results in the the controller simply by...

// ---------------------------------------------------------------------
[HttpPost]       
[ValidateAntiForgeryToken]
public ActionResult Edit(FooModel myFooModel)
{

    List<string> SelectedItemsFromListBox = myFooModel.userSelectionsAsListBox;

    List<string> SelectedItemsFromMultiSelectList = myFooModel.userSelectionsAsMultiSelectList;

    List<string> SelectedItemsFromCheckList = myFooModel.WidgetsAsCheckList.Where(x => x.Selected == true).ToList().Select(x => x.Value).ToList();

}
0
da_jokker On

Warning... just wanted to point out that the "CheckBox" option basically hangs once you get too many choices. (e.g. changed my loop to 500) and it basically won't submit.

the problem is traced back to the validation of the CheckBoxFor line. This can be fixed by changing the one line to...

                        @Html.CheckBoxFor(cc => cc.WidgetsAsCheckList[myIndex].Selected, new { data_val = "false",  htmlAttributes = new { @class = "form-control" } })

If I do this, I can have 1,500 items in the check list and the submit occurs in under 3 seconds