Question

I'm seeing an odd (to me) issue where I am attempting to post an array of integers based on checkboxes. When the values are not posted in order, the model binder doesn't seem to function in a way that I would expect.

I can reproduce this against a very simple Action

    public ActionResult Debug(string[] Unassigned)
    {
        return RedirectToAction("Index", new { id = 7 });
    }

An example of this not working would be when the following values are posted (copied in via the immediate window). I would expect Unassigned to have the values 8 and 6.

? Request.Form.AllKeys
{string[4]}
[0]: "__RequestVerificationToken"
[1]: "LoginId"
[2]: "Unassigned[1]"
[3]: "Unassigned[3]"
? Request.Form["Unassigned[1]"]
"8"
? Request.Form["Unassigned[3]"]
"6"
? Unassigned
null

When the values are passed in order, this works (note Unassigned[3] doesn't get bound as Unassigned[2] is not posted.

? Request.Form.AllKeys
{string[5]}
[0]: "__RequestVerificationToken"
[1]: "LoginId"
[2]: "Unassigned[0]"
[3]: "Unassigned[1]"
[4]: "Unassigned[3]"
? Request.Form["Unassigned[0]"]
"2"
? Request.Form["Unassigned[1]"]
"8"
? Request.Form["Unassigned[3]"]
"6"
? Unassigned
{string[2]}
[0]: "2"
[1]: "8"

Minus the formatting, my HTML looks like this

<input type="checkbox" name="Unassigned[0]" value="2">
<input type="checkbox" name="Unassigned[1]" value="8">
<input type="checkbox" name="Unassigned[2]" value="7">
<input type="checkbox" name="Unassigned[3]" value="6">
<input type="checkbox" name="Unassigned[4]" value="5">
<input type="checkbox" name="Unassigned[5]" value="9">
<input type="checkbox" name="Unassigned[6]" value="4">
<input type="checkbox" name="Unassigned[7]" value="3">
<input type="checkbox" name="Unassigned[8]" value="1">

Given that select is not broken, what am doing wrong?

Was it helpful?

Solution

Given that select is not broken, what am doing wrong?

You are not respecting the convention for binding to a list because you have holes in your indexes. You could use non-sequential indexes, like Guids. Look at Phil Haacks blog post I have linked to. He has an entire section dedicated to it.

Other possibility that I would recommend you is to use a view model. So go ahead and write one:

public class ItemViewModel
{
    public string Id { get; set; }
    public bool Selected { get; set; }
}

and then you could have a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new[] 
        {
            new ItemViewModel { Id = "2" },
            new ItemViewModel { Id = "8" },
            new ItemViewModel { Id = "7" },
            new ItemViewModel { Id = "6" },
            new ItemViewModel { Id = "5" },
            new ItemViewModel { Id = "4" },
            new ItemViewModel { Id = "3" },
            new ItemViewModel { Id = "1" },
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(ItemViewModel[] model)
    {
        // everything will be correctly bound here
    }
}

and a corresponding strongly typed view:

@model ItemViewModel[]
@using (Html.BeginForm())
{
    for (var i = 0; i < Model.Length; i++)
    {
        Html.HiddenFor(x => x[i].Id)
        Html.CheckBoxFor(x => x[i].Selected)
    }

    <button type="submit">OK</button>
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top