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 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.
Any idea what might be happening?