16

I am trying to bind a dynamic array of elements to a view model where there might be missing indexes in the html

e.g. with the view model

class FooViewModel
{
   public List<BarViewModel> Bars { get; set; }
}

class BarViewModel
{
   public string Something { get; set; }
}

and the html

<input type="text" name="Bars[1].Something" value="a" />
<input type="text" name="Bars[3].Something" value="b" />
<input type="text" name="Bars[6].Something" value="c" />

at the moment, bars will just be null. how could I get the model binder to ignore any missing elements? i.e. the above would bind to:

FooViewModel
{
     Bars
     {
            BarViewModel { Something = "a" },
            BarViewModel { Something = "b" },
            BarViewModel { Something = "c" }
     }
}
James Hollingworth
  • 14,040
  • 12
  • 39
  • 57

4 Answers4

12

Add the .Index as your first hidden input to deal with out of sequence elements as explained in this Phil Haacked blog post:

<input type="text" name="Bars.Index" value="" />
<input type="text" name="Bars[1].Something" value="a" />
<input type="text" name="Bars[3].Something" value="b" />
<input type="text" name="Bars[6].Something" value="c" />
amurra
  • 15,221
  • 4
  • 70
  • 87
  • 5
    Very close, but the accepted answer in this url has a more complete solution: http://stackoverflow.com/questions/8598214/mvc3-non-sequential-indices-and-defaultmodelbinder – Levitikon Mar 28 '12 at 03:32
  • @Levitikon - The accepted solution in your link is overkill. You don't need to specify a `.Index` for every item. I've used the above approach numerous times without needing the extra hidden inputs described in your link. Plus, Phil Haack was on the ASP.NET MVC development team, so I'm pretty sure what he wrote in his blog is the way to go. – amurra Mar 28 '12 at 14:16
  • 2
    @amurra in the Haack article he says to use a separate hidden input for each field so it would seem that it's necessary. – Mykroft May 01 '15 at 15:00
  • 3
    I can confirm that in MVC 5.2.3 the hidden input is necessary for each field and value needs to be the correct index. – Christopher Nov 16 '18 at 21:14
0

MVC is able to populate list itself.

public ActionResult Index(FooViewModel model)
{
   ...

So no matter if anything is missing mvc will create new List<BarViewModel> and for each found index - [1],[3],[6] it will create new BarViewModel and add it to List. So you will get FooViewModel with populated Bars.

Echilon
  • 10,064
  • 33
  • 131
  • 217
Bakhodir
  • 21
  • 3
0

A possible workaround could be to instantiate the ViewModel and the collection to the correct size (assuming it's known), then update it with TryUpdateModel... something like:

    [HttpPost]
    public ActionResult SomePostBack(FormCollection form)
    {
        // you could either look in the formcollection to get this, or retrieve it from the users' settings etc.
        int collectionSize = 6; 

        FooViewModel bars = new FooViewModel();
        bars.Bars = new List<BarViewModel>(collectionSize);
        TryUpdateModel(bars, form.ToValueProvider());

        return View(bars);
    }H
Stelloy
  • 2,316
  • 1
  • 19
  • 26
-1

i didnt know even that worked!

bearing that in mind, id have done something like:

<input type="text" name="Bars.Something" value="a" />
<input type="hidden" name="Bars.Something" value="" />
<input type="text" name="Bars.Something" value="b" />
<input type="hidden" name="Bars.Something" value="" />
<input type="hidden" name="Bars.Something" value="" />
<input type="text" name="Bars.Something" value="c" />

which would hopefully post

a,,b,,,c

but I suspect that will bind in the same way as you describe

Youre probably going to have write a custom model binder that looks for the max index, makes a list of that size then puts the elements in the correct place.

Saying all that, wait for someone else to post a really simple attribute you can put on your property that makes it just work ;D

Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231