0

Consider the following:

Form

<form method="post">
    <input name="As[].Id1" />
    <input name="As[].Id2" />
    <input name="Ids[]" />
</form>

Form Data

As[].Id1=0&As[].Id2=1&As[].Id1=2&As[].Id2=3&Ids[]=4&Ids[]=5

Models

public class A {
    public int Id1 { get; set; }
    public int Id2 { get; set; }
}

public class B {
    public IEnumerable<A> As { get; set; }
    public IEnumerable<int> Ids { get; set; }
}

Action Method

[HttpPost]
public ActionResult C(
    B b) {...}

In the example above, I expect to have an instance of B where it's As property contains two objects: { Id1=0, Id2=1}, and { Id1=2, Id2=3 }; and it's Ids property contains two ints: 4,5. In reallity, I get an instance of B where it's As property is null and it's Ids property contains two ints: 4,5.

So, the binder seems to have a hard time mapping collection properties of complex objects when the form data does not have an index for the collection. If it had an index, that starts at 0, it binds as expected.

My problem is that my form can't have an index because the amount of postable fields can change at any given point, which is why I want to use an index-less collection which works fine for simple properties.

Is there some way I can make the binder work without going down the road of either 1) normalizing the form before submission with JavaScript to edit all field names and give them an index or 2) writing a custom binder for class B? I want to avoid the JavaScript route because I don't trust it, and from briefly trying out the custom binder route, it looks like it will be a pain to get to work.

Gup3rSuR4c
  • 9,145
  • 10
  • 68
  • 126
  • 2
    Short answer is no (the indexers are required to match up which belongs with which - how would it know that you did not want `{ Id1=0, Id2=3}`, and `{ Id1=2, Id2=1 }` for example. Writing a custom model binder would require that you trust the name/value pairs are always posted in the correct order. Why do you not trust javascript? –  Apr 20 '17 at 23:58
  • 1
    You can also use a hidden input for the `.Index` property to match up the collections which means you do not need to have zero-based, consecutive indexers –  Apr 21 '17 at 00:00
  • @StephenMuecke, Yeah, I was thinking about the order of pairs when I was testing out the custom binder. As far as the JS, I just want to make the forms in my app not dependent on JS in order to be submitted because JS can be disabled. Do you have an example of what you mean by the hidden `.Index` property? – Gup3rSuR4c Apr 21 '17 at 00:27
  • Refer [this answer](http://stackoverflow.com/questions/24026374/adding-another-pet-to-a-model-form/24027152#24027152) for an example of dynamically adding and deleting collection items (its using javascript, but the key is the `Index` property. Essentially you can post `As[xx].Id1=0&As[xx].Id2=1&As.Index=xx` where `xx` can be anything your want. –  Apr 21 '17 at 00:39
  • But how are you generating your form inputs if your nor using javascript (if the collection was generated on the server, they would be correctly indexed anyway so not clear what you doing) –  Apr 21 '17 at 00:40
  • Ok, I see how the hidden index works, it basically tells the binder how to group the properties in order to instance the full class to add to the collection. I was looking at the example here: http://stackoverflow.com/questions/8598214/mvc3-non-sequential-indices-and-defaultmodelbinder. As far as the forms, they're generated server-side, but I don't use the `HtmlHelper`s except for drop down fields. – Gup3rSuR4c Apr 21 '17 at 00:53
  • Why in the world not? - a simple `for` loop or `EditorTemplate` and using the `HtmlHelper` methods will not only simplify your code and ensure strong binding, and it has other advantages such as client side validation etc - and you would not be able to correctly return the view if `ModelState` was invalid with your current implementation –  Apr 21 '17 at 00:56
  • I used to use them when I started out with MVC, but felt they get in the way and are just too verbose to accomplish the same thing I could by manually typing out the form field I want. Don't worry, I've taken the time to make sure that if a `ModelState` error occurs I can re-display the form for corrections. Never really got into the client-side validation so I can't speak to it. I'd love to jump ship to ASP.NET Core and the tag helpers, but I'm still stuck with VS2013 and MVC 5. Anyway, thanks for the help, that hidden index is amazing! Post an answer so I can give you credit. – Gup3rSuR4c Apr 21 '17 at 01:03
  • @Gup3rSuR4c So, helpers that will enable you to get around the problem you're having "get in the way" and "are too verbose"? How verbose are you willing to be in order to avoid code that is too verbose? – ErikE Apr 21 '17 at 02:04
  • @ErikE, I don't need to be verbose to accomplish the equivalent of the html helpers. Ultimately, sure, I might be in the wrong, but after several years, I've settled on this style of development and I like it because it makes sense to me. – Gup3rSuR4c Apr 21 '17 at 03:09

1 Answers1

1

Other than using javascript to set the collection indexer or creating a custom model binder, a third option is to include a hidden input for the collections .Index property which allows you to match up non-zero, non-consecutive indexers. The value of the indexer can be anything you want (e.g. a number, a Guid or a string), so that your name/value pairs would look like

As[xx].Id1=0
As[xx].Id2=1
As.Index=xx

As[45].Id1=2
As[45].Id2=3
As.Index=45

For more information on model binding to collections, refer Model Binding To A List