Using Kendo MultiSelect with Kendo UI Grid in ASP.NET MVC

loxdog picture loxdog · Dec 18, 2013 · Viewed 10.5k times · Source

This is related to another question I asked about recently. I'm trying to bind user role information to a grid, and I'm assigning roles to a user. Each user can be in multiple roles within the database, and these should be edited using a Kendo UI MultiSelect.

When I select the roles required and post back to the controller, the array of "RoleBasicModel" objects contains the required number of roles, but all their properties are empty.

The models are defined as:

public class UserInfo
{
    public string UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }
    public string Roles { get; set; }
    public IEnumerable<RoleBasicModel> RoleList { get; set; }
}
public class RoleBasicModel
{
    public string Id { get; set; }
    public string Text { get; set; }
}

The grid is setup as:

    @(Html.Kendo().Grid<Models.UserInfo>()
    .Name("userGrid")
    .Columns(columns =>
    {
        columns.Bound(p => p.UserName);
        columns.Bound(p => p.FirstName);
        columns.Bound(p => p.LastName);
        columns.Bound(p => p.Roles).EditorTemplateName("RoleListEditor").Template(p => p.RoleList);
        columns.Command(command => { command.Edit(); command.Destroy(); });
    })
    .Filterable()
    .Sortable()
    .Resizable(r => r.Columns(true))
    .Editable(editable => { editable.Mode(GridEditMode.InLine); editable.DisplayDeleteConfirmation("Are you sure you want to remove this user?"); })
    .HtmlAttributes(new { style = "min-height:90px;max-height:450px;" })
    .DataSource(dataSource => dataSource
        .Ajax()
        .Events(events => events.Error("error_handler"))
        .Model(model =>
        {
            model.Id(p => p.UserId);
            model.Field(p => p.UserId).Editable(false);
            model.Field(p => p.FirstName).Editable(true);
            model.Field(p => p.LastName).Editable(true);
            model.Field(p => p.UserName).Editable(false);
            model.Field(p => p.RoleList).Editable(true);
        }
        ).Read(read => read.Action("GetAllUsers", "Admin").Type(HttpVerbs.Get))
        .Update(update => update.Action("UpdateUser", "Admin").Type(HttpVerbs.Post))
        .Destroy(update => update.Action("DeleteUser", "Admin").Type(HttpVerbs.Post))
    )
)

And my editor template, which uses the Kendo MultiSelect, is defined as:

@Html.Kendo().MultiSelect().Name("RoleList").DataTextField("Text").DataValueField("Id").BindTo((IEnumerable<Models.RoleBasicModel>)ViewData["uroles"]).Placeholder("No role selected")

Is there any obvious reason why the data sent back to the server is empty? I'm suspecting I'm missing something from the MultiSelect control that'll define the correct model to use. I have referred to the test project that is often cited as an answer to similar questions, but I've had no joy with that either.

As requested, (an abridged version of) the controller I'm using:

 public ActionResult ManageUsers()
    {            
        PopulateRoles();
        return View();
    }

    private void PopulateRoles()
    {
        ViewData["uroles"] = new ApplicationDbContext().Roles.Select(r => new RoleBasicModel { Text = r.Name, Id = r.Id }).ToList();
    }

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult GetAllUsers([DataSourceRequest]DataSourceRequest request)
    {
        using (var context = new ApplicationDbContext())
        {
            var allUsers = context.Users.ToList().Select(x =>
                new UserInfo
                {
                    UserName = x.UserName,
                    UserId = x.Id,
                    FirstName = x.FirstName,
                    LastName = x.LastName,
                    RoleList = x.Roles.Select(p => new RoleBasicModel { Text = p.Role.Name, Id = p.RoleId }),
                    Roles = string.Join(", ", x.Roles.Select(p => p.Role.Name).ToList())
                }).ToList();
            return Json(allUsers.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
        }
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult UpdateUser([DataSourceRequest] DataSourceRequest request, UserInfo user)
    {
        if (user != null && ModelState.IsValid)
        {
            using (var context = new ApplicationDbContext())
            {
// Do something with the user details
            }
        }

        return Json(new[] { user }.ToDataSourceResult(request, ModelState));
    }

EDIT: Upon viewing the data posted back to the server, it appears as if the array of selected objects isn't parsed correctly. The format should be RoleList[0].Id:123456 but is instead RoleList[0][Id]:123456. I'm thinking this could be a problem with the MultiSelect control, rather than any code I've written?

Answer

loxdog picture loxdog · Jan 3, 2014

So I've eventually worked out what the problem was. As per my edit, I noticed that the data wasn't serialised properly from the MultiSelect control.

I spent some time getting the example available on the Kendo website working and I noticed that they posted the data serialised correctly and incorrectly to the server. The trick they used (which seems ridiculous to me) is that on the Update function on the grid, they serialise any data inside an array for themselves i.e.

.Update(update => update.Action("UpdateUser", "Admin").Type(HttpVerbs.Post).Data("serialize"))

Where the "serialize" function is defined as:

function serialize(data) {
    for (var property in data) {
        if ($.isArray(data[property])) {
            serializeArray(property, data[property], data);
        }
    }
}

function serializeArray(prefix, array, result) {
    for (var i = 0; i < array.length; i++) {
        if ($.isPlainObject(array[i])) {
            for (var property in array[i]) {
                result[prefix + "[" + i + "]." + property] = array[i][property];
            }
        }
        else {
            result[prefix + "[" + i + "]"] = array[i];
        }
    }
}

Having searched around extensively for a resolution to my problem, and other people have been pointed to a working solution without explanation, I thought it might be useful to someone else to understand what the problem is when you try to use a Kendo MultiSelect in a Kendo Grid, rather than just saying "look at this example".

tl;dr Always serialise the data inside a Kendo MultiSelect yourself before posting to the server (if you're using a Kendo Grid)