Muli-Select in Grid InLine Edit Mode

1 Answer 10 Views
Grid MultiSelect
Mike
Top achievements
Rank 1
Iron
Iron
Mike asked on 06 Sep 2025, 02:24 PM

I have a .net core 8 web application, that has a grid that loads data via Ajax.  When I edit a row, I'd like the `Contract` column to be a multi-select dropdown.  When I edit a row, the control is rendered, but there is no data in it.  I have set static data and the data does show up.

Here's my grid:

@(
Html.Kendo().Grid<ContractBenefitViewModel>()
    .Name("ContractBenefitGrid")
    .Columns(columns =>
    {
        columns.Bound(c => c.Id).Hidden(true);
        columns.Bound(c => c.Contracts)
            .ClientTemplate("#= displayContracts(data) #")
            .EditorTemplateName("Contracts")
            .Width(400)
            .Filterable(false)
            .Sortable(false);
        columns.ForeignKey(c => c.BenefitId, (IEnumerable)ViewBag.Benefits, "Id", "DisplayName");
        columns.Bound(c => c.Gl)
            .Width(150);
        columns.Bound(c => c.Comments);
        columns.Bound(c => c.Active).Hidden(true);
        columns.Bound(c => c.Deleted).Hidden(true);
        columns.Command(command =>
            {
                command.Edit();
                command.Destroy();
            })
            .HtmlAttributes(new { style = "text-align: center;" })
            .Width(200);
    })
    .Editable(editable => editable.Mode(GridEditMode.InLine))
    .ToolBar(toolbar =>
    {
        toolbar.Create();
    })
    .Pageable(pager =>
    {
        pager.Refresh(true);
        pager.PageSizes([10, 20, 50]);
    })
    .Sortable()
    .Filterable()
    .NoRecords()
    .DataSource(dataSource => dataSource
        .Ajax()
        .Batch(true)
        .PageSize(10)
        .ServerOperation(true)
        .Events(events => events.Error("error_handler").RequestEnd("onRequestEnd('staticNotifications')"))
        .Model(model =>
        {
            model.Id(p => p.Id);
            model.Field(p => p.Contracts).DefaultValue(ViewBag.ContractId as List<ContractModel> ?? new List<ContractModel>());
            model.Field(p => p.BenefitValue).DefaultValue(ViewBag.BenefitId);
            model.Field(p => p.Comments);
            model.Field(p => p.LastUpdatedBy).DefaultValue(ViewData[Literals.EmailKey]);
            model.Field(p => p.Active).Editable(false).DefaultValue(Literals.Yes);
            model.Field(p => p.Deleted).Editable(false).DefaultValue(Literals.No);
        })
        .Filter(f =>
        {
            f.Add(x => x.Active).IsEqualTo(Literals.Yes);
            f.Add(x => x.Deleted).IsEqualTo(Literals.No);
        })
        .Read(read => read.Action("GetContractBenefits", "ContractBenefit").Data("forgeryToken"))
        .Create(create => create.Action("CreateContractBenefits", "ContractBenefit").Data("forgeryToken"))
        .Update(update => update.Action("UpdateContractBenefits", "ContractBenefit").Data("forgeryToken"))
        .Destroy(destroy => destroy.Action("DeleteContractBenefits", "ContractBenefit").Data("forgeryToken"))
        .Sort(sort =>
        {
            sort.Add(x => x.BenefitValue);
        })
    )
)

Here's the JavaScript

<script type="text/javascript">
    function displayContracts(data) {
        window.console.log('displayContracts()');
        return $.map(data.Contracts, function (e) { return e.DisplayName; }).join(", ");
    }
</script>

Here's my model

public class ContractBenefitViewModel
{
    public long Id { get; set; }

    public long BenefitId { get; set; }

    [Display(Name = "Benefit")]
    public string BenefitValue
    {
        get
        {
            var sb = new StringBuilder();
            if (Benefit is not null)
            {
                sb.Append(Benefit.Value);
                if (!string.IsNullOrWhiteSpace(Benefit.Gl))
                {
                    sb.Append(" (");
                    sb.Append(Benefit.Gl);
                    sb.Append(")");
                }
            }

            return sb.ToString();
        }
    }

    [Display(Name = "GL Code")]
    public string Gl => Benefit?.Gl ?? string.Empty;

    [Display(Name = "Comments")]
    [StringLength(ModelLiterals.CommentsLength)]
    [MaxLength(ModelLiterals.CommentsLength)]
    public string? Comments { get; set; }

    public string Active { get; set; } = ModelLiterals.Yes;
    public string Deleted { get; set; } = ModelLiterals.No;
    public string LastUpdatedBy { get; set; } = string.Empty;

    public BenefitModel? Benefit { get; set; } = null!;

    [UIHint("Contracts")]
    public IList<ContractModel> Contracts { get; set; } = new List<ContractModel>();
}

Here's my Editor Template (named Contracts.cshtml, in the EditorTemplates folder)

@model Lookups.ContractModel

<div style="width: 99%">
    @(Html.Kendo().MultiSelectFor(m => m)
    .DataValueField("Id")
    .DataTextField("DisplayName")
    .DownArrow()
    .AutoClose(false)
    .BindTo((IEnumerable<Lookups.ContractModel>)ViewData["contracts"])
        )
</div>

Here's the Index and Get data methods in my controller

[HttpGet]
public async Task<IActionResult> IndexAsync()
{
    Logger.LogDebug("{NameOf}()", nameof(IndexAsync));

    var contracts = await Client.GetContractsAsync();  //Client is my API client library - it fetches data from my API
    var benefits = await Client.GetBenefitsAsync();

    ViewBag.Contracts = contracts;
    ViewData["contracts"] = contracts;
    ViewBag.Benefits = benefitFinancialSupports;
    ViewBag.DefaultContractId = contracts?.FirstOrDefault()?.Id ?? -1;
    ViewBag.DefaultBenefitId = benefits?.FirstOrDefault()?.Id ?? -1;

    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<JsonResult> GetContractBenefitsAsync([DataSourceRequest] DataSourceRequest request)
{
    Logger.LogDebug("{NameOf}()", nameof(GetContractBenefitsAsync));
    var stopwatch = Stopwatch.StartNew();
    try
    {
        if (!ModelState.IsValid) return Json(InvalidModelStateMessage);
        var models = await Client.GetContractBenefitsAsync();
        var viewModels = models
            .Where(x => x is { Benefit: not null, Contract: not null })
            .GroupBy(x => x.BenefitId)
            .Select(g => new ContractBenefitViewModel
            {
                BenefitId = g.Key,
                Benefit = g.First().Benefit,
                Contracts = g.Select(x => x.Contract!).Distinct().ToList(),
                Id = g.First().Id,
                Comments = g.First().Comments,
                Active = g.First().Active,
                Deleted = g.First().Deleted,
                LastUpdatedBy = g.First().LastUpdatedBy
            })
            .ToList();

        return Json(await viewModels.ToDataSourceResultAsync(request));
    }
    catch (Exception ex)
    {
        Logger.LogError(ex, "{NameOf}()", nameof(GetContractBenefitsAsync));
        return Json(new { success = false, message = ex.Message });
    }
    finally
    {
        stopwatch.Stop();
        Logger.LogInformation("**** {NameOf} took [{Elapsed}]", nameof(GetContractBenefitsAsync),
            stopwatch.Elapsed);
    }
}

 

I should mention that I store the data one ContractId with one BenefitId (in a Mapping table), so when I return data via the `Client.GetContractBenefitsAsync()` call, it returns a List of 1-to-1 records, and I group them so I get a single Benefit with all Contracts that are associated with it (hence the Linq GroupBy call to build my ViewModel).  This is how I want to present it to my users, so they can select as many Contracts as they want in the UI, and behind the scenes I'll turn into a 1:1 association.  I'm just having trouble getting my data (the Contracts Multi Select) to display data.  Am I missing something or is there another way to do this?

 

 

 

 

 

 

 

 

 

 

 

1 Answer, 1 is accepted

Sort by
0
Anton Mironov
Telerik team
answered on 10 Sep 2025, 10:39 AM

Hi Mike,

Thank you for the code snippets and the details provided.

The MultiSelect editor in your grid is not displaying data because of a mismatch between the expected model type in your EditorTemplate and the actual property type in your view model. Here’s a breakdown of what’s happening and how you can resolve it:


1. EditorTemplate Model Type

Your EditorTemplate currently uses:

@model Lookups.ContractModel

However, your grid column is bound to:

public IList<ContractModel> Contracts { get; set; } = new List<ContractModel>();

Solution:
Change your EditorTemplate to expect a collection:

@model IList<Lookups.ContractModel>

Or, for more flexibility:

@model IEnumerable<Lookups.ContractModel>

This ensures that the MultiSelect editor receives the correct type and can bind selected values properly.


2. MultiSelect Binding

Update your MultiSelect’s binding in the EditorTemplate:

@(Html.Kendo().MultiSelectFor(m => m)
    .DataValueField("Id")
    .DataTextField("DisplayName")
    .AutoBind(false)
    .BindTo((IEnumerable<Lookups.ContractModel>)ViewData["contracts"])
)

With the correct model type, selected contracts will appear as preselected when editing.


3. Passing Data to the EditorTemplate

You are already populating ViewData["contracts"] in your controller, which is correct. Make sure this is available during both create and edit operations.


4. Alternative Approach: Ajax Loading

If your contract list is large or dynamic, you can load the options via Ajax in the EditorTemplate:

@(Html.Kendo().MultiSelectFor(m => m)
    .DataValueField("Id")
    .DataTextField("DisplayName")
    .AutoBind(false)
    .DataSource(source => source.Read(read => read.Action("GetContracts", "ContractBenefit")))
)

This will fetch contract options dynamically. You can use the grid’s Edit event to pass additional parameters if you need context (like the currently selected benefit).


5. Handling Preselection and Data Flow

  • Ensure that the Contracts property in your view model is populated with the selected contracts for each row.
  • The MultiSelect will automatically preselect these values when the model type and binding are set correctly.

Furthermore, in the following GitHub repository, we have uploaded an example of the same scenario. Feel free to use it on your side:

I hope this information helps.

 

Kind Regards,
Anton Mironov
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

Tags
Grid MultiSelect
Asked by
Mike
Top achievements
Rank 1
Iron
Iron
Answers by
Anton Mironov
Telerik team
Share this question
or