'ASP.Net Core MVC - Complex ViewModel not binding property with list of objects

I have a ViewModel with some properties and a list of objects. I also have a controller to create the data from the ViewModel.

Whenever the request gets back to the controller, I restrict the binding on Nickname and Items. The ModelState is invalid as the binding on the list is null.

ViewModel

public class ContainerViewModel
{
    public Guid ID { get; set; }
    public string Nickname { get; set; }
    public List<ItemViewModel> Items { get; set; }
    public Guid NewItemID { get; set; }
    public string NewItemNickname { get; set; }
    public uint NewItemQuantity { get; set; }

    public void AddNewItem()
    {
        Items.Add(new ItemViewModel {
            ID = NewItemID,
            Nickname = NewItemNickname,
            Quantity = NewItemQuantity
        });
        NewItemID = Guid.NewGuid();
        NewItemNickname = default;
        NewItemQuantity = default;
    }

    public class ItemViewModel
    {
        public Guid ID { get; set; }
        public string Nickname { get; set; }
        public uint Quantity { get; set; }
    }
}

Controller

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Nickname,Items")] ContainerViewModel containerVM, CancellationToken token)
{
    if (ModelState.IsValid) {
        await _manager.Add(PrepareDataModel(containerVM), token);
        return RedirectToAction(nameof(Index));
     }
     return View(containerVM);
}

--EDIT--

Create.cshtml

@model MyProject.Models.ContainerViewModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Container</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Nickname" class="control-label"></label>
                <input asp-for="Nickname" class="form-control" />
                <span asp-validation-for="Nickname" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Quantity" class="control-label"></label>
                <input asp-for="Quantity" type="number" class="form-control" />
                <span asp-validation-for="Quantity" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Items" class="control-label"></label>
                @await Html.PartialAsync("Items/_Create")
                <span asp-validation-for="Items" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Items/_Create.cshtml

@model MyProject.Models.ContainerViewModel

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.NewItemNickname)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.NewItemQuantity)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Items) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Nickname)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Quantity)
            </td>
            <td></td>
        </tr>
}
        <tr>
            <td>
                <input asp-for="NewItemNickname" class="form-control" />
                <span asp-validation-for="NewItemNickname" class="text-danger"></span>
            </td>
            <td>
                <input asp-for="NewItemQuantity" type="number" class="form-control" />
                <span asp-validation-for="NewItemQuantity" class="text-danger"></span>
            </td>
            <td>
                <input type="submit" asp-action="CreateNewItem" value="Add" class="btn btn-secondary" />
            </td>
        </tr>
    </tbody>
</table>


Solution 1:[1]

In your Partial View, you just use @Html.DisplayFor(xxxxx) to show the data of Items,So these data will not be submit. Change your code like this:

Items/_Create.cshtml

//.......
@for (var i = 0; i < @Model.Items.Count; i++)
{
        <tr>
            <td>
                @Html.DisplayFor(modelItem => @Model.Items[i].Nickname)

                // use hidden to submit the data
                <input type="hidden" asp-for="@Model.Items[i].Nickname">
            </td>
            <td>
                @Html.DisplayFor(modelItem => @Model.Items[i].Quantity)
                <input type="hidden" asp-for="@Model.Items[i].Quantity">
            </td>
            <td></td>
        </tr>
 }
//.......

Result

Because you don't provide the code of CreateNewItem, I think it is may be use d to add new data in Items, I don't write this action, So ss.3 will not be submited to controller.

enter image description here

enter image description here

You can see controller can receive Items successfully.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Xinran Shen