locked
Getting back empty viewmodel RRS feed

  • Question

  • User25974107 posted

    Hi!

    What I need is to get back a list of checkboxes that are not selected. I have a viewmodel:

    public class ProductInShopsCheckboxViewModel
        {
            public string ShopName { get; set; } = default!;
            
            public List<CheckboxViewModel>? Checkboxes { get; set; } = new List<CheckboxViewModel>();
        }
    
        public class CheckboxViewModel
        {
            public bool IsSelected { get; set; } = true;
    
            public string ProductName { get; set; } = default!;
    
            public long Barcode { get; set; }
    
            public Guid ProductId { get; set; }
    
        }

    Then in controller, I fill the checkboxes list:

    public async Task<IActionResult> Assortment(Guid id)
            {
                if (id == null)
                {
                    return NotFound();
                }
                
                IEnumerable<ProductInShop> products = _uow.ProductInShops.GetAllProducts(id);
    
                ProductInShopsCheckboxViewModel vm = new ProductInShopsCheckboxViewModel
                {
                    ShopName = _uow.ProductInShops.GetShopName(id)
                };
    
                foreach (var p in products)
                {
                    vm.Checkboxes!.Add(new CheckboxViewModel
                    {
                        ProductName = p.Product!.ProductName,
                        Barcode = p.Product.Barcode,
                        ProductId = p.ProductId
                    });
                }
                
                return View(vm);
            }

    and finally I generate the checkboxes in my view:

    <form asp-action="Assortment" method="post">
            @for (var i = 0; i < Model.Checkboxes!.Count; i++) 
            {
                <div class="[ form-group ]">
                    <input 
                        name="products"  
                        type="checkbox" 
                        id="@Model.Checkboxes[i].ProductId" 
                        value="@Model.Checkboxes[i].ProductName"/>
                    <div class="[ btn-group ]">
                        <label asp-for="@Model">@Model.Checkboxes[i].ProductName</label>
                    </div>
                </div>
            }
            <div class="form-group">
                <input type="submit" value="Send email" class="btn btn-primary"/>
            </div>
        </form> 

    This is working very well. However when I ask the viewmodel back in my controller, it's empty. 

    public async Task<IActionResult> Assortment(Guid id, ProductInShopsCheckboxViewModel vm)
    {
        Console.WriteLine(vm.Checkboxes!.Count);  //size is 0
    }

    If I ask from controller string [ ] products, I get all the checked products, but it's useless for me without barcodes.

    Any advice is appreciated.

    Thursday, July 2, 2020 5:00 PM

Answers

  • User348806598 posted

    There is a better way of doing this without naming the names of controls yourself. To use helper methods like below-

    @model MVCSamples.Models.ProductInShopsCheckboxViewModel
    @{
        ViewData["Title"] = "Assortment";
    }
    
    <h1>Assortment</h1>
    
    <form action="_2168657_Getting_back_empty_viewmodel/AssortmentSave" method="post">
        @for (var i = 0; i < Model.Checkboxes!.Count; i++)
        {
            <div class="[ form-group ]">
                @Html.HiddenFor(model => model.Checkboxes[i].ProductId)
                <label>@Html.CheckBoxFor(model => model.Checkboxes[i].IsSelected)</label> @Html.DisplayFor(model => model.Checkboxes[i].ProductName)
                <label>Bar code : @Model.Checkboxes[i].Barcode</label>
            </div>
        }
        <div class="form-group">
            <input type="submit" value="Send email" class="btn btn-primary" />
        </div>
    </form> 
    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, July 2, 2020 7:16 PM
  • User-474980206 posted

    the browser only post backs selected checkboxes and only the name & value of the selected checkbox. to send the barcode, you need to include in a form field, say a hidden field.

    you could use the mvc trick of having a hidden field for the checkboxes or just use the helper:

    <form asp-action="Assortment" method="post">
            @for (var i = 0; i < Model.Checkboxes!.Count; i++) 
            {
                <div class="[ form-group ]">
                    <input 
                        asp-for="@Model.Checkboxes[i].IsSelected"  
                        type="checkbox" />
                    <div class="[ btn-group ]">
                        <label asp-for="@Model.Checkboxes[i].IsSelected">@Model.Checkboxes[i].ProductName</label>
                    </div>
                    <input type="hidden" asp-for="Model.Checkboxes[i].ProductName" />
                    <input type="hidden" asp-for="Model.Checkboxes[i].Barcode" />
                    <input type="hidden" asp-for="Model.Checkboxes[i].ProductId" />
                </div>
            }
            <div class="form-group">
                <input type="submit" value="Send email" class="btn btn-primary"/>
            </div>
        </form> 

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, July 2, 2020 7:20 PM

All replies

  • User348806598 posted

    Hi Kalev,

    Welcome to the community.

    Your view is not correct. You need to name your controls in specific ways-

    @model MVCSamples.Models.ProductInShopsCheckboxViewModel
    @{
        ViewData["Title"] = "Assortment";
    }
    
    <h1>Assortment</h1>
    
    <form action="_2168657_Getting_back_empty_viewmodel/AssortmentSave" method="post">
        @for (var i = 0; i < Model.Checkboxes!.Count; i++)
        {
        <script>
            function changeChecked(checkbox, hiddenId) {
                document.getElementById(hiddenId).value = checkbox.checked;
            }
        </script>
        <div class="[ form-group ]">
            <input name="Checkboxes[@i].ProductId" type="hidden" value="@Model.Checkboxes[@i].ProductId" />
            <label>
                <input checked="@(Model.Checkboxes[@i].IsSelected? "checked" : "")" onchange="changeChecked(this, 'IsSelected_@i')" type="checkbox" />
                <input name="Checkboxes[@i].IsSelected" type="hidden" id="IsSelected_@i"  value="@Model.Checkboxes[@i].IsSelected" /> @Model.Checkboxes[@i].ProductName
            </label>
            <label>Bar code : @Model.Checkboxes[@i].Barcode</label>
        </div>
        }
        <div class="form-group">
            <input type="submit" value="Send email" class="btn btn-primary" />
        </div>
    </form> 

    A working sample is there in this repository-

    https://github.com/anupdg/mvcsamples/blob/master/Views/_2168657_Getting_back_empty_viewmodel/Index.cshtml

    Thursday, July 2, 2020 7:13 PM
  • User348806598 posted

    There is a better way of doing this without naming the names of controls yourself. To use helper methods like below-

    @model MVCSamples.Models.ProductInShopsCheckboxViewModel
    @{
        ViewData["Title"] = "Assortment";
    }
    
    <h1>Assortment</h1>
    
    <form action="_2168657_Getting_back_empty_viewmodel/AssortmentSave" method="post">
        @for (var i = 0; i < Model.Checkboxes!.Count; i++)
        {
            <div class="[ form-group ]">
                @Html.HiddenFor(model => model.Checkboxes[i].ProductId)
                <label>@Html.CheckBoxFor(model => model.Checkboxes[i].IsSelected)</label> @Html.DisplayFor(model => model.Checkboxes[i].ProductName)
                <label>Bar code : @Model.Checkboxes[i].Barcode</label>
            </div>
        }
        <div class="form-group">
            <input type="submit" value="Send email" class="btn btn-primary" />
        </div>
    </form> 
    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, July 2, 2020 7:16 PM
  • User-474980206 posted

    the browser only post backs selected checkboxes and only the name & value of the selected checkbox. to send the barcode, you need to include in a form field, say a hidden field.

    you could use the mvc trick of having a hidden field for the checkboxes or just use the helper:

    <form asp-action="Assortment" method="post">
            @for (var i = 0; i < Model.Checkboxes!.Count; i++) 
            {
                <div class="[ form-group ]">
                    <input 
                        asp-for="@Model.Checkboxes[i].IsSelected"  
                        type="checkbox" />
                    <div class="[ btn-group ]">
                        <label asp-for="@Model.Checkboxes[i].IsSelected">@Model.Checkboxes[i].ProductName</label>
                    </div>
                    <input type="hidden" asp-for="Model.Checkboxes[i].ProductName" />
                    <input type="hidden" asp-for="Model.Checkboxes[i].Barcode" />
                    <input type="hidden" asp-for="Model.Checkboxes[i].ProductId" />
                </div>
            }
            <div class="form-group">
                <input type="submit" value="Send email" class="btn btn-primary"/>
            </div>
        </form> 

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, July 2, 2020 7:20 PM