Search code examples
c#asp.net-core.net-corerazorasp.net-core-mvc

Passing an IEnumerable<Entity> from view to controller in ASP.NET Core 8 MVC


This is my view:

@model IEnumerable<HourlyLimit>
<html>
    <head><title></title></head>
    <body>
        <form method="post" action="~/Admin/HourlyLimits/Set" enctype="multipart/form-data">
            @foreach (var item in Model)
            {
                <input asp-for="@item.ID" hidden />
                <input asp-for="@item.Date" hidden />
                <input asp-for="@item.Hour" hidden />
                <input asp-for="@item.Capacity" type="number">
            }
            <button type="submit"></button>
    </body>
</html>

In the Get method of this action, I'm passing an IEnumerable<HourlyLimit> to the view like this:

IEnumerable<HourlyLimit> hourlyLimits = await _db.HourlyLimits
                                                 .OrderBy(h => h.Hour)
                                                 .ToListAsync();

return View(hourlyLimits);

When the page is loaded, the <input /> tags are populated correctly from the existing records in the database; so it seems that the model binding is working fine.

The issue is, when I submit the form, the object that's being passed to the Post method of that action, it has a count of 0:

[HttpPost]
// hourlyLimits.Count is 0 here when I hit the breakpoint 
public async Task<IActionResult> Set(IEnumerable<HourlyLimit> hourlyLimits) 
{
    if (!ModelState.IsValid)
    {
        //...
    }

    try
    {
        //...
    }
    catch (Exception)
    {
        //...
    }
}

What's wrong? How can I submit an IEnumerable<HouryLimit> from the view?


Solution

  • To prepare context for MVC binding to work correctly use indexing like below:

    @{ int i=0; }
    @foreach (var item in Model)
    {
      <input type="hidden" asp-for="@item.ID" name="@("hourlyLimits["+i+"].ID")" />
      <input type="hidden" asp-for="@item.Date" name="@("hourlyLimits["+i+"].Date")"  />
      <input type="hidden" asp-for="@item.Hour" name="@("hourlyLimits["+i+"].Hour")"  />
      <input type="number" asp-for="@item.Capacity" name="@("hourlyLimits["+i+"].Capacity")" /> 
      @(i++)
    }
    

    The second option is declaring the view data model as IList<HourlyLimit> and using the for statement:

    @model IList<HourlyLimit>
    <html>
        <head><title></title></head>
        <body>
            <form method="post" action="~/Admin/HourlyLimits/Set" >
                 @for (int i = 0; i < Model.Count(); i++)
                {
      <input type="hidden" asp-for="@Model[i].ID" name="@("hourlyLimits["+i+"].ID")" />
      <input type="hidden" asp-for="@Model[i].Date" name="@("hourlyLimits["+i+"].Date")"  />
      <input type="hidden" asp-for="@Model[i].Hour" name="@("hourlyLimits["+i+"].Hour")"  />
      <input type="number" asp-for="@Model[i].Capacity" name="@("hourlyLimits["+i+"].Capacity")" /> 
                }
                <button type="submit"></button>
        </body>
    </html>