4

I'm currently working on an ASP.NET MVC 4.5 application. I want to add objects to a List<T> dynamically on a button click, using a @Html.EditorFor and send the data to the Controller with a Form submit.

However, my problem, only the 1st element of the populated list gets sent to the controller when I submit my Form.

The area in my view where I add Elements looks like this:

<div id="addTarget" data-target="@Url.Action("AddTarget", "Offer")">

    @for(var i = 0; i < Model.Targets.Count(); i++)
    {
        @Html.EditorFor(m => m.Targets[i])
    }
</div>

My Controller looks like that:

...

public OfferVm NewOffer { get; set; } = new OfferVm();

[HttpGet]
public ActionResult Create()
{
    // ...here I add my first initial target to OfferVm
    NewOffer.Targets.Add(new TargetVm());

    return View(NewOffer);
}

public ActionResult AddTarget()
{
    // ...here I add a target to my NewOffer
    NewOffer.Targets.Add(new TargetVm());
    return PartialView("~/Views/Shared/EditorTemplates/TargetVm.cshtml");
}

[HttpPost]
public ActionResult Create(OfferVm offerVM)
{
   //... here only 1 target is in my offerVM
}

My OfferVm class looks like this:

public class OfferVm
{
    public OfferVm()
    {
        this.Targets = new List<TargetVm>();
    }

    public List<TargetVm> Targets { get; set; }
}

Do you have an idea how I can add a new target to my NewOffer.Targets List, and get the data in my controller, when I post the form?

9
  • 1
    You cannot use EditorFor() - and your generating identical name attributes for your 'new' TargetVm (the DefaultModelBinder only reads the first value and ignores the rest). Refer this answer for some options Commented Mar 9, 2017 at 9:22
  • Hi Stephen, Thanks for your answer! It gave me a good idea on how to solve this issue. As I'm limited on three "Targets" I just create them in the constructor, and hide two of them with javascript. Have a nice day! Commented Mar 9, 2017 at 9:55
  • A terrible way to handle it Commented Mar 9, 2017 at 9:58
  • 1
    And your not understanding the EditorFor() method anyway - its just @Html.EditorFor(m => m.Targets) - no loop - the method generates the correct html for each item in the collection. Commented Mar 9, 2017 at 10:08
  • 1
    You obviously did did not read either of my answers I linked to :) - you need to re-parse the validator Commented Mar 9, 2017 at 11:03

1 Answer 1

1

The reason only the first TargetVm in your collection is being bound in the POST method, is that your AddTarget() method your calling to add a new item is generating form controls with name that are identical to those your have previously generated. Assuming TargetVm has a property named ID, then your have multiple elements with

<input ... name="Targets[0].ID" ... />

and the DefaultModelBinder only binds the first name/value pair from the request, and ignores any subsequent duplicate names. In order to bind to a collection, your name attributes need to have indexers that start at zero and be consecutive.

<input ... name="Targets[0].ID" ... /> // first TargetVM
<input ... name="Targets[1].ID" ... /> // second TargetVM
<input ... name="Targets[2].ID" ... /> // third TargetVM

Note you can also include an extra input for each item in the collection - <input type="hidden" name="Targets.Index value="xx" /> (where xx is the value of the indexer) to bind non-consecutive indexers as discussed in Model Binding To A List.

Using the EditorFor() method will not work in the case where your dynamically adding new collection items, unless you were to use javascript to update the value of the indexer in the name attribute when its added.

Solutions include using a javascript MVVM framework such as knockout, using the BeginCollectionItem() helper, or using a client side template and javascript to dynamically add new form controls. The latter two options are discussed in this answer, and a more complete example using BeginCollectionItem() is shown in this answer.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi, thanks for your response! Yes, the BeginCollectionItem() did the trick!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.