0

So I have a list of viewmodels that i iterate through and one of the properties in the viewmodel object is a dictionary. In my customercontroller, in the details action I get all the viewmodels that correspond to the id from the asp-route and in the view i have a form where I present all the dictionary values so that you can modify them if you like. Afterwards you can submit the form. This is where I se that the list of viewmodels are "0". Why is this?

this is my model:

   public class CustomerViewModel
{
    public CustomerViewModel()
    {
        EmployeeAndHours = new Dictionary<string, int>();
    }

    public string projectName { get; set; }
    public Dictionary<string, int> EmployeeAndHours { get; set; }
}

this is the get action:

        // GET: Customers/Details/5
    public IActionResult Details(int? id) 
    {
        if (id == null)
        {
            return NotFound(); 
        }

        var customers = _customerHelper.GetCustomerDetails(id);

        if (customers == null)
        {
            return NotFound();
        }

        return View(customers);
    }

this is the post action:

        [HttpPost]
    public IActionResult EditCustomerDetailViewModel(List<customerViewModel> customers)
    {
        //TODO
        return View("Details");
    }

this is my view:

@model List<myNamespace.Models.ViewModels.CustomerViewModel>

<div class="container-fluid">
    <form asp-controller="Customers" asp-action="EditCustomerDetailViewModel" method="post">
        @foreach (var customer in Model)
        {
            <div class="media">
                <div class="media-body">
                    <div class="text-wrapper">
                        <h5 class="mt-0">@customer.projectName</h5>
                    </div>
                    <div class="slider-wrapper">
                        @foreach (var employee in customer.EmployeeAndHours) // This is the dictionary
                        {
                            <input name="@("EmployeeAndHours[" + employee.Key + "]")" type="range" min="0" max="1000" step="1" value="@employee.Value" data-orientation="horizontal">
                            <hr />
                        }
                    </div>
                </div>
            </div>
        }
        <div class="form-group" style="text-align:center">
            <input id="customer-detail-form-button" type="submit" value="Save changes" class="btn btn-success" />
        </div>
    </form>
</div>
  • Is your view model data showing at controller action level before pushing to razor view? – user9405863 Mar 16 '18 at 22:22
  • Given what you _have_ shown, I would assume the names of your inputs don't match the properties of your CustomerViewModel class. The model binder can only work with whatever is posted from your page, and you're not giving it enough to do so successfully. – Tieson T. Mar 16 '18 at 22:26
  • @Hazarath Yes, everything works fine at the get action, and I have no erors in console. – swedish_junior_dev Mar 17 '18 at 00:13
  • @Tieson, maybe so, Ill check it out in the morning! – swedish_junior_dev Mar 17 '18 at 00:17
  • You need to show your model (as noted above, your generating `name` attributes which have not relationship to your model (and certainly have no relationship to a `Dictionary`, which you should not be using anyway) –  Mar 17 '18 at 02:24
  • @StephenMuecke, I have edited the post with my model. – swedish_junior_dev Mar 17 '18 at 11:52

2 Answers2

1

You cannot use a foreach loop to generate form controls for collection items and get correct 2-way model binding. You need to use a for loop or and EditorTemplate for typeof CustomerViewModel as explained in Post an HTML Table to ADO.NET DataTable.

In addition, you should avoid binding to a Dictionary because you cannot use the strong typed HtmlHelper method or TagHelpers to give 2-way model binding.

In order oo bind to your current model, your name attribute would need to be in the format name="[#].EmployeeAndHours[Key]" where # is the zero-based collection indexer.

Instead, modify your view models to

public class CustomerViewModel
{
    public string ProjectName { get; set; }
    public List<EmployeeHoursViewMode> EmployeeHours { get; set; }
}
public class EmployeeHoursViewModel
{
    public string Employee { get; set; }
    public int Hours{ get; set; }
}

And the view then becomes

@model List<CustomerViewModel>
<form asp-controller="Customers" .... >
    @for(int i = 0; i < Model.Count i++)
    {
        ....
        <h5>@Model[i].ProjectName</h5>
        @Html.HiddenFor(m => m[i].ProjectName)
        // or <input type="hidden" asp-for-"Model[i].ProjectName />
        <div>
            @for(int j = 0; j < Model[i].EmployeeHours.Count; j++)
            {
                @Html.HiddenFor(m => m[i].EmployeeHours[j].Employee)
                @Html.LabelFor(m => m[i].EmployeeHours[j].Hours, Model[i].EmployeeHours[j].Employee)
                // or <label asp-for="Model[i].EmployeeHours[j].Hours">@Model[i].EmployeeHours[j].Employee</label>
                @Html.TextBoxFor(m => m[i].EmployeeHours[j].Hours, new { type = "range", ... })
                // or <input asp-for-"Model[i].EmployeeHours[j].ProjectName type="range" ... />
            }
        </div>
    }
    <input type="submit" value="Save changes" class="btn btn-success" />
}

Note the above code assumes you want to post back the value of ProjectName (hence the hidden input), and that you want to display the employee name adjacent each 'Hours' input.

-1

You are not referencing the model name from your POST action, try this:

@{int index = 0;}
@foreach (var customer in Model)
{
    ...
    @foreach (var employee in customer.EmployeeAndHours)
    {
        <input name="customer[@(index)].EmployeeAndHours[@(employee.Key)]" type="range" min="0" max="1000" step="1" value="@employee.Value" data-orientation="horizontal">
        <hr />
    }
    ...
    index++;
}
John Caprez
  • 393
  • 2
  • 10