Search code examples
.netmodel-view-controllercheckboxlisteditorfor

.Net MVC editor template is not returning data on postback


I have a requirement where I need to display a list of dynamic checkboxes (data coming from database). I have a view model for checkbox.

public class AmenitiesListViewModel
    {
        public int AmenityId { get; set; }
        public string Title { get; set; }
        public bool Checked { get; set; }
        public string ImageSrc { get; set; }
    }

Another viewmodel which has ICollection object for that viewmodel.

public class HouseAmenityViewModel
    {
        public HouseAmenityViewModel()
        {
            Amenities = new HashSet<AmenitiesListViewModel>();
        }

        [HiddenInput]
        public Guid HouseId { get; set; }
        public ICollection<AmenitiesListViewModel> Amenities { get; set; }
    }

Then, I created a service to prepare checkbox list from database.

var amenities = await _amenityRepository.ListAmenitiesAsync();
                if (amenities != null)
                {
                    model.HouseId = houseId;
                    foreach (var amenity in amenities)
                    {
                        model.Amenities.Add(new AmenitiesListViewModel
                        {
                            AmenityId = amenity.AmenityId,
                            Title = amenity.Description,
                            Checked = false,
                            ImageSrc = string.Format(_imageOptions.AmenityImagePath, amenity.ImageFileName)
                        });
                    }
                }

In my view, I am using @Html.EditorFor to render checkbox list.

<form asp-action="UpdateHouseAmenity" asp-controller="Admin" method="post" id="update-amenity-form" class="amenity-form">

        <input asp-for="HouseId" />
        <div class="row"><div class="col-md-12">@Html.EditorFor(m => m.Amenities)</div></div>
        <div class="row">
            <div class="col-md-12 mt-10">
                <button type="submit" class="btn btn-primary" id="update-amenity-button">Submit</button>
                <button type="button" class="btn btn-danger">Reset</button>
            </div>
        </div>

</form>

and editor template looks like below:

@model HouseRentalManagement.Models.AdminViewModels.AmenitiesListViewModel

<div class="container mt-10" style="width: 30%;
  display: inline-block;
  float: left;">

    <label>
        <img src="@Model.ImageSrc" style="height: 30px; width: 30px;" />

        <input type="checkbox" @string.Format("{0}", Model.Checked ? "checked" : "") /> @Model.Title
    </label>
</div>

I got the checkbox list being displayed properly on my view. My form is being submitted to specified action.

Problem is: when I do submit, I get nothing from @Html.EditorFor(m => m.Amenities) collection.

controller:

public async Task<IActionResult> UpdateHouseAmenity(HouseAmenityViewModel model)
        {
            var result = await _houseService.UpdateHouseAmenitiesAsync(model);            
        }

One thing to notice here is this view is rendered using Ajax after the page is loaded. Ajax:

var loadAmenity = function () {
        $.ajax({
            url: "/Admin/GetHouseAmenities"
        }).done(function (data) {
            $('#amenity-info').html(data);
        }).fail(function (a, textError) {
            console.error(textError);
        });
    };

Controller:

var model = await _houseService.GetHouseAmenityViewModelAsync(new Guid());
            return PartialView("~/Views/Admin/_HouseAmenityPartial.cshtml", model);

Solution

  • Your current editor template code will be generating HTML markup like below for each item in the Amenities collection

    <label>
           <img src="SomeImageSourceValue" style="height: 30px; width: 30px;">
           <input type="checkbox"> AminityTitle
    </label>
    

    The checkbox has no name attribute value! For model binding to work, the form element name attribute value should match with the property name of your class(the one you use as the parameter of HttpPost action method). So ideally we need it to be generated like this

    <input type="checkbox" name="Amenities[0].Checked" value="true">
    <input type="checkbox" name="Amenities[1].Checked" value="true">
    <input type="checkbox" name="Amenities[2].Checked" value="true">
    

    Good news is, If you use the input tag helper properly, it will generate the correct markup for you.

    @model HouseRentalManagement.Models.AdminViewModels.AmenitiesListViewModel
    
    <div class="container mt-10" >
    
        <img src="@Model.ImageSrc" style="height: 30px; width: 30px;" />
    
        <input type="checkbox" asp-for="Checked" /> @Model.Title
        <input type="hidden" asp-for="AmenityId" />
    
    </div>