1

I'm trying to post a list of names to a form, allow users to select the relevant names, and post it back. It gets fine but posts back empty. Can you point out my stupid mistake please?

[HttpGet]
public IActionResult Start()
{
    // Get guid and name for all chars and post as list with PC/NPC role and selector.
    // Post this list to the view.
    List<CombatantSelect>cs_list = new List<CombatantSelect>();

    var chars = _context.Characters.AsEnumerable();

    foreach(var c in chars)
    {
        CombatantSelect cs = new CombatantSelect();
        cs.CharGuid = c.Id.ToString();
        cs.Name = c.Name;
        cs.Role = c.PlayerRole.ToString();
        cs_list.Add(cs);
    }

    return View(cs_list);
}

This is the controller action that posts this list see picture. As it arrives populated, I now it's formed correctly.

View of Table

I render the table using this View.

@using Traveller_Web_2.ViewModels
@model List<CombatantSelect>
@{ 
    ViewData["Title"] = "Start";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Start</h1>

<h6>tbc</h6>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post" asp-action="Start" asp-controller="Combat">
            <table class="table">
                <thead class="">
                    <tr>
                        <th>Select for combat</th>
                        <th>Name</th>
                        <th>Role</th>
                    </tr>
                </thead>
                <tbody>

                        @Html.EditorForModel("CombatantSelect")
                </tbody>
            </table>
                <input asp-action="Start" asp-controller="Combat" type="submit" value="Select" class="btn btn-primary" />
        </form>
    </div>
</div>

This is the view model:

public class CombatantSelect
{
        [Key]
        public string CharGuid { get; set; }
        public string Name { get; set; }
        public string Role { get; set; }
        public bool SelectForCombat { get; set; }
}

And this Editor template:

@model IEnumerable<Traveller_Web_2.ViewModels.CombatantSelect>
@for (int i = 0; i < Model.Count(); i++)
{
    <tr>
        <td>
            <input id="SelectForCombat[@i]" asp-for="@Model.ElementAt(i).SelectForCombat" type="checkbox" name="SelectForCombat[@i]" itemid="@i" />
           </td>
        <td>
           <input id="Name[@i]" name="Name[@i]" asp-for="@Model.ElementAt(i).Name" type="text"  />
        </td>
        <td>
            <input id="Name[@i]" name="Name[@i]" asp-for="@Model.ElementAt(i).Role" type="text"  />
            <input id="CharGuid[@i]" name="CharGuid[@i]" type="hidden" asp-for="@Model.ElementAt(i).CharGuid"  />
        </td>
    </tr>
}

This is part of what it renders to demonstrate indexing in the ID and the Name;

<form method="post" action="/Combat/Start">
            <table class="table">
                <thead class="">
                    <tr>
                        <th>Select for combat</th>
                        <th>Name</th>
                        <th>Role</th>
                    </tr>
                </thead>
                <tbody>

                            <tr>
        <td>
            <input id="SelectForCombat[0]" type="checkbox" name="SelectForCombat[0]" itemid="0" data-val="true" data-val-required="The SelectForCombat field is required." value="true" />
           </td>
        <td>
           <input id="Name[0]" name="Name[0]" type="text" value="Chief Sales Officer" />
        </td>
        <td>
            <input id="Name[0]" name="Name[0]" type="text" value="Player" />
            <input id="CharGuid[0]" name="CharGuid[0]" type="hidden" value="bf373774-691a-43f2-5ff8-08d75cab14e2" />
        </td>
    </tr>
    <tr>
        <td>
            <input id="SelectForCombat[1]" type="checkbox" name="SelectForCombat[1]" itemid="1" value="true" />
           </td>
        <td>
           <input id="Name[1]" name="Name[1]" type="text" value="Deputy Shop Assistant" />
        </td>
        <td>
            <input id="Name[1]" name="Name[1]" type="text" value="FriendlyNPC" />
            <input id="CharGuid[1]" name="CharGuid[1]" type="hidden" value="347298c5-984b-460a-f9f8-08d75cac89b9" />
        </td>
    </tr>

It posts back to this method in the controller;

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Start([Bind("CharGuid, Role, SelectForCombat, Name")] 
                           List<CombatantSelect> combatantSelects)
{
    // If it works do something with it, if not give it back
    if (ModelState.IsValid)
    {
        combatantSelects.Count();
    }

    return View(combatantSelects);
}

The ModelState doesn't error - it tells me there is a list of CombatantSelect but it has a count of 0.

Any ideas? Many thanks in advance.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
GManatee
  • 43
  • 2
  • 9
  • You'll need to adjust your form attribute values https://stackoverflow.com/questions/19964553/mvc-form-not-able-to-post-list-of-objects – Jasen Nov 18 '19 at 23:51

2 Answers2

2

Having developed to the next example of Form List of Objects I successfully bound the model using helper tags instead of Html Helpers.

                @for (int i = 0; i < Model.returnCount(); i++)
                {
                <tr>
                    <td class="col-md-2">
                        <input asp-for="Combatants[i].CharacterName" type="text" />
                        <input asp-for="Combatants[i].CharacterGuid" type="hidden" />
                    </td>
                    <td class="col-md-2">
                        <input asp-for="Combatants[i].CharRoll" type="text" />
                    </td>
                    <td class="col-md-1">
                        <input asp-for="Combatants[i].IntModFromStats" type="number" />
                    </td>
                    <td class="col-md-1">
                        <input asp-for="Combatants[i].DiceRoll" type="number" />
                    </td>
                    <td class="col-md-2">
                        <select asp-for="Combatants[i].Ambush" asp-items="Html.GetEnumSelectList<AmbushMod>()">
                        </select>
                    </td>
                    <td class="col-md-1">
                        <input asp-for="Combatants[i].OtherMods" type="number" />
                    </td>
                </tr>
                }

And it allows the specification of input type, drop-down boxes etc.

GManatee
  • 43
  • 2
  • 9
0

MVC recreates the model from posted value. it checks for specific names to recreate the model again. so name in HTML should align with name in model. Try below code to resolve the model binding issue

pass model of type List<Traveller_Web_2.ViewModels.CombatantSelect> to the view

Modify code as below so that produced HTML input control names that align with the model

@model List<Traveller_Web_2.ViewModels.CombatantSelect>
@for (int i = 0; i < Model.Count; i++)
{
    <tr>
        <td>
            @Html.CheckBoxFor(x => x[i].SelectForCombat)
        </td>
        <td>
           @Html.TextBoxFor(x => x[i].Name)
        </td>
        <td>
           @Html.TextBoxFor(x => x[i].Role)
           @Html.HiddenFor(x => x[i].CharGuid)
        </td>
    </tr>
}

above code should create an input with name that aligns with the model.

Arjun Vachhani
  • 1,761
  • 3
  • 21
  • 44