1

I am following this blog Editing a variable length list, ASP.NET MVC 2-style by Steven Anderson and started using Html.BeginCollectionItem() for my ASP.NET MVC project.

The project is about associating multiple permissions to roles. It will have a Role page(view) where user will have option to add permissions by selecting one of the permissions from drop-down list. User will also be able to add these permissions drop-down list by clicking on 'Add' button. 'Add' would dynamically add new drop-down list control on the page. Remove would remove the last drop-down list.

The issue I am facing is with binding nested collection. I have used RolePermissions list below which inturn is a list of RolePermissionViewModel.

See below for screenshots.

Role

Permissions

Role Controller: I have hardcoded sample data for testing.

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web.Mvc;
using MUtilities.Model.Entities;
using MUtilities.Web.Areas.Admin.Models;
using MUtilities.Web.Controllers;
using HtmlHelpers.BeginCollectionItem;
namespace MUtilities.Web.Areas.Admin.Controllers
{
    public class RoleController : BaseController
    {
        public ActionResult Index()
        {


            return View();
        }


        public ViewResult BlankPermission()
        {
            return View("_PermissionPartial", GetPermissions());
        }
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            var role = MRepository.AllRoles.Find(id);
            if (role == null)
            {
                return HttpNotFound();
            }
            return View(role);
        }


        private List<List<RolePermissionViewModel>> GetPermissionList() {           
            var rolePermissions = new List<List<RolePermissionViewModel>>();
            rolePermissions.Add(GetPermissions());
            rolePermissions.Add(GetPermissions());
            return rolePermissions;
        }
        private List<RolePermissionViewModel> GetPermissions()
        {
            var permissions = new List<RolePermissionViewModel>();
          permissions.Add(new RolePermissionViewModel
        {
            PermissionId = -1,
            PermissionName = "--Select a Permission--"
         });
            permissions.Add(new RolePermissionViewModel
            {
                PermissionId = 1,
                PermissionName = "Create of Utility1"
            });
            permissions.Add(new RolePermissionViewModel
            {
                PermissionId = 2,
                PermissionName = "Update of of Utility1"
            });
            return permissions;
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "Id,Name,Description,RolePermissions")] RoleViewModel roleModel)
        {

            if (ModelState.IsValid)
            {
                var role = new Role();
                role.Name = roleModel.Name;

            //some logic here

                return RedirectToAction("Index");
            }

            return View();
        }

        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            //some logic to edit view
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "Id,Name,Permissions")] RoleViewModel roleModel)
        {
            if (ModelState.IsValid)
            {
                var role = MRepository.AllRoles.First(r => r.Id == roleModel.Id);
                role.Name = roleModel.Name;

               //some logic to commit 

                return RedirectToAction("Index");
            }
            return View();
        }

        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Role role = MRepository.AllRoles.Find(id);
            if (role == null)
            {
                return HttpNotFound();
            }
            return View(role);
        }

        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            var role = MRepository.AllRoles.Find(id);
            MRepositoryAllRoles.Remove(role);
            MRepository.Commit();
            return RedirectToAction("Index");
        }
    }
}

Role View:

@model MUtilities.Web.Areas.Admin.Models.RoleViewModel

@{
    ViewBag.Title = "Create";
}
@section scripts{
 <script type="text/javascript">
        $(function () {
            $("#addPermissionBtn").click(function () {
                $.ajax({
                    url: this.href,
                    cache: false,
                    success: function (html) {
                        $("#addPermissionDdl").append(html);
                        //The call to Sys.Mvc.FormContext._Application_Load() refreshes 
                        //the validation on the page to include the new fields.
                        Sys.Mvc.FormContext._Application_Load();
                    }
                });
                return false;
            });

            $("#deletePermissionBtn").on("click", function () {
                var pDdls = $("select[id$='__PermissionList']");
                if (pDdls && pDdls.length > 1) {
                    pDdls.last().remove();
                    //$(this).parents("div.addPermissionDdl:last").remove();
                }
                return false;
            });
        });

</script>   
}
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Role</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.RolePermissions, htmlAttributes: new { @class = "control-label col-md-2" })

            @using (Html.BeginForm())
            {
                <div id="addPermissionDdl" class="col-md-10">
                    @foreach (var rolePermission in Model.RolePermissions)
                    {
                        Html.RenderPartial("_PermissionPartial", rolePermission);
                    }


                </div>
                <div class="col-md-10">
                    @Html.ActionLink("Add", "BlankPermission", null, new { id = "addPermissionBtn", @class = "glyphicon glyphicon-plus-sign" })
                    <a href="#" id="deletePermissionBtn" class="glyphicon glyphicon-remove-circle">Delete</a>
                    </div>
                }
                </div>


        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

RoleViewModel: RolePermissions is a list of RolePermissionViewModel list.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MUtilities.Web.Areas.Admin.Models
{
    public class RoleViewModel
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        public string Description { get; set; }

        [Display(Name="Permissions")]
        public List<List<RolePermissionViewModel>> RolePermissions { get; set; }
    }

    public class RolePermissionViewModel
    {
        public int PermissionId { get; set; }
        public string PermissionName { get; set; }

    }
}

_PermissionPartial.cshtml

 @{
        Layout = null;
    }

    @using HtmlHelpers.BeginCollectionItem;
    @model IEnumerable<MUtilities.Web.Areas.Admin.Models.RolePermissionViewModel>

    @using (Html.BeginCollectionItem("RolePermissions"))
    {
        var permissionDdl = new SelectList(Model.Select(m => m), "PermissionId", "PermissionName", "--Select a Permission--");

        @Html.DropDownList("PermissionList", permissionDdl, new { @class = "form-control" });

    }

But when I click Create button Name, Description would bind ok but not the Permissions. See below.

RolePermission_Not_Binding

Any idea what might be happening?

BusyBees
  • 59
  • 6
  • `BeginCollectionItem` will not work with nested collections. But you can look at [this article](http://www.joe-stevens.com/2011/06/06/editing-and-binding-nested-lists-with-asp-net-mvc-2/) for a solution –  Dec 21 '15 at 07:09
  • Thanks Stephen. I looked into but still couldn't figure out the way to actually make it similar. The author seem to actually create multiple partial views for widget and zones and seem to use the nested way. I am not sure if I can do the same thing. – BusyBees Dec 21 '15 at 18:05

0 Answers0