Question

I've got a viewmodel for a page where fields are combined into fieldsets.

The VM looks like this:

public class FieldsetVM
{
    public Int64 ID { get; set; }
    public string Name { get; set; }

    [Display(Name = "Available Fields")]
    public ICollection<SelectListItem> AvailableFields { get; set; }

    [Display(Name = "Current Fields")]
    public ICollection<SelectListItem> UsedFields { get; set; }

    public FieldsetVM(int id, string name, List<Field> availFields, List<Field> usedFields)
    {
        this.ID = id;
        this.Name = name;

        this.AvailableFields = new List<SelectListItem>();

        foreach (Field field in availFields)
            this.AvailableFields.Add(new SelectListItem { Text = string.Format("{0} ({1})", field.Name, field.FieldType.ToString()), Value = field.FieldID.ToString() });

        this.UsedFields = new List<SelectListItem>();

        foreach (Field field in usedFields)
            this.UsedFields.Add(new SelectListItem { Text = string.Format("{0} ({1})", field.Name, field.FieldType.ToString()), Value = field.FieldID.ToString() });
    }

    public FieldsetVM()
    {
    }
}

Get in the controller looks like this:

    [HttpGet]
    public ActionResult Create()
    {
        FieldsetVM vm = new FieldsetVM(0, "", uw.FieldRepo.Get().ToList(), new List<Field>());

        return View(vm);
    }

Relevant piece of the view looks like this:

    <div class="col-md-3 col-xs-6">
    <div class="editor-label">
        @Html.LabelFor(m => m.AvailableFields)
    </div>
    <div class="editor-field">
        @Html.ListBoxFor(m => m.AvailableFields, Model.AvailableFields)
    </div>

    <button type="button" onclick="moveSelected('AvailableFields','UsedFields');">Move Selected</button>
</div>

<div class="col-md-3 col-xs-6">
    <div class="editor-label">
        @Html.LabelFor(m => m.UsedFields)
    </div>
    <div class="editor-field">
        @Html.ListBoxFor(m => m.UsedFields, Model.UsedFields)
    </div>

    <button type="button" onclick="moveSelected('UsedFields','AvailableFields');">Remove Selected</button>
</div>

A tiny bit of JavaScript wires up the two listboxes:

function moveSelected(firstSelectId, secondSelectId) {
$('#' + firstSelectId + ' option:selected').appendTo('#' + secondSelectId);
$('#' + firstSelectId + ' option:selected').remove();

}

And then I have a POST in the controller:

    [HttpPost]
    public ActionResult Create(FieldsetVM postedVm)
    {
        Fieldset fs = new Fieldset();

        fs.Name = postedVm.Name;

        if (fs.Fields == null)
            fs.Fields = new List<Field>();

        fs.Fields.Clear();

        foreach (SelectListItem item in postedVm.UsedFields)
            fs.Fields.Add(uw.FieldRepo.GetByID(item.Value));

        uw.FieldsetRepo.Insert(fs);

        return RedirectToAction("Index");
    }

My expectation is that in the postedVm, we would be able to see the values the user selected into UsedFields. Instead, UsedFields and AvailableFields are ALWAYS blank when the user posts back to the HttpPost Create() action.

I'm trying to figure out why: Surely moving items between list boxes is a fairly common way to configure things? Shouldn't MVC take a look at the values in the generated and use them to populate the postedVm object?

EDIT Based on feedback from best answer, here is my revised Create/Post action.

        [HttpPost]
    public ActionResult Create(FieldsetVM postedVm, int[] UsedFields)
    {
        Fieldset fs = new Fieldset();

        fs.Name = postedVm.Name;

        fs.Fields = new List<Field>();

        foreach (int id in UsedFields)
            fs.Fields.Add(uw.FieldRepo.GetByID(id));

        uw.FieldsetRepo.Insert(fs);
        uw.Save();

        return RedirectToAction("Index");
    }
Was it helpful?

Solution

When you post the form, only the Ids for AvailableFields and UsedFields will be posted. If you have multiple values, then you'll get a comma seperated list of ids, so modelbinding will not be able to bind those posted Ids to FieldsetVM postedVm.

If you do something like public ActionResult Create(int[] availableFields, int[] usedFields) you should be able to get the selected Ids.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top