Example E4425
Visible to All Users

Grid View for ASP.NET MVC - How to implement cascading combo boxes in the grid's edit form

This example demonstrates how to create cascading combo box editors and use them to edit grid data.

Overview

Follow the steps below to implement cascading combo boxes in the grid's edit form:

  1. Call a column's MVCxGridViewColumn.EditorProperties method to add a combo box editor to the column.
    Razor
    settings.Columns.Add(c => c.CountryId, country =>{ country.Caption = "Country"; country.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties)); }); settings.Columns.Add(c => c.CityId, city =>{ city.Caption = "City"; city.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CityComboBoxProperties)); });
  2. Add a MVCxColumnComboBoxProperties object to specify an editor's settings and call the MVCxColumnComboBoxProperties.BindList method to bind the column to a data source.
    C#
    MVCxColumnComboBoxProperties countryComboBoxProperties; public MVCxColumnComboBoxProperties CountryComboBoxProperties { get { if(countryComboBoxProperties == null) countryComboBoxProperties = CreateCountryComboBox(); return countryComboBoxProperties; } } protected MVCxColumnComboBoxProperties CreateCountryComboBox() { MVCxColumnComboBoxProperties cs = new MVCxColumnComboBoxProperties(); cs.CallbackRouteValues = new { Controller = "Home", Action = "ComboBoxCountryPartial" }; // ... cs.ClientSideEvents.SelectedIndexChanged = "CountriesCombo_SelectedIndexChanged"; cs.BindList(WorldCities.Countries.ToList()); return cs; }
  3. Specify the secondary editor's CallbackRouteValue parameters.
  4. Handle the primary editor's SelectedIndexChanged event. In the handler, call the secondary editor's PerformCallback method to update the editor's data.
    JavaScript
    function CountriesCombo_SelectedIndexChanged(s, e) { customCallback = true; grid.GetEditor('CityId').PerformCallback(); }
  5. Handle the secondary editor's client-side BeginCallback event and pass the selected value of the secondary editor as a parameter.
    JavaScript
    function CitiesCombo_BeginCallback(s, e) { e.customArgs['CountryId'] = grid.GetEditor('CountryId').GetValue(); }
  6. Use the grid's GetComboBoxCallbackResult method to get the result of callback processing.
    C#
    public ActionResult ComboBoxCountryPartial(){ return GridViewExtension.GetComboBoxCallbackResult(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties); }
  7. Call the secondary editor's CallbackRouteValues.Action method to populate the editor with values based on the passed parameter.

Files to Review

Documentation

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

Controllers/HomeController.cs(vb)
C#
using System; using System.Web.Mvc; using E4425.Models; using DevExpress.Web.Mvc; using System.Linq; namespace E4425.Controllers { public class HomeController : Controller { WorldCitiesEntities entity = new WorldCitiesEntities(); public ActionResult Index() { return View(entity.Customers.ToList()); } public ActionResult GridViewPartial() { return PartialView(entity.Customers.ToList()); } public ActionResult GridViewEditPartial(Customer customerInfo) { if (ModelState.IsValid) { try { entity.Customers.Attach(customerInfo); var entry = entity.Entry(customerInfo); entry.Property(e => e.CityId).IsModified = true; entry.Property(e => e.CountryId).IsModified = true; entry.Property(e => e.CustomerName).IsModified = true; // uncomment the next line to enable database updates // entity.SaveChanges(); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", entity.Customers.ToList()); } public ActionResult GridViewInsertPartial(Customer customerInfo) { if (ModelState.IsValid) { try { entity.Customers.Add(customerInfo); // uncomment the next line to enable database updates // entity.SaveChanges(); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", entity.Customers.ToList()); } public ActionResult GridViewDeletePartial(int CustomerId) { if (ModelState.IsValid) { try { entity.Customers.Remove(entity.Customers.Find(CustomerId)); // uncomment the next line to enable database updates //entity.SaveChanges(); } catch (Exception e) { ViewData["EditError"] = e.Message; } } else ViewData["EditError"] = "Please, correct all errors."; return PartialView("GridViewPartial", entity.Customers.ToList()); } public ActionResult ComboBoxCountryPartial() { return GridViewExtension.GetComboBoxCallbackResult(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties); } public ActionResult ComboBoxCityPartial() { return GridViewExtension.GetComboBoxCallbackResult(ComboBoxPropertiesProvider.Current.CityComboBoxProperties); } } }
Global.asax
Code
<%@ Application Codebehind="Global.asax.cs" Inherits="E4425.MvcApplication" Language="C#" %>
Global.asax.cs(vb)
C#
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace E4425 { // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.IgnoreRoute("{resource}.ashx/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); ModelBinders.Binders.DefaultBinder = new DevExpress.Web.Mvc.DevExpressEditorsBinder(); } } }
Models/City.cs(vb)
C#
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated from a template. // // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace E4425.Models { using System; using System.Collections.Generic; public partial class City { public City() { this.Countries = new HashSet<Country>(); this.Customers = new HashSet<Customer>(); } public int CityId { get; set; } public string CityName { get; set; } public Nullable<int> CountryId { get; set; } public virtual Country Country { get; set; } public virtual ICollection<Country> Countries { get; set; } public virtual ICollection<Customer> Customers { get; set; } } }
Models/ComboBoxPropertiesProvider.cs(vb)
C#
using DevExpress.Web; using DevExpress.Web.Mvc; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI.WebControls; namespace E4425.Models { public class ComboBoxPropertiesProvider : IDisposable { static ComboBoxPropertiesProvider current; public static ComboBoxPropertiesProvider Current { get { if (current == null) current = new ComboBoxPropertiesProvider(); return current; } } public int EditableCountryID { get { string rawCountryId = HttpContext.Current.Request["CountryId"]; return string.IsNullOrEmpty(rawCountryId) ? -1 : int.Parse(rawCountryId); } } MVCxColumnComboBoxProperties cityComboBoxProperties; public MVCxColumnComboBoxProperties CityComboBoxProperties { get { if (cityComboBoxProperties == null) cityComboBoxProperties = CreateCityComboBox(); return cityComboBoxProperties; } } MVCxColumnComboBoxProperties countryComboBoxProperties; public MVCxColumnComboBoxProperties CountryComboBoxProperties { get { if (countryComboBoxProperties == null) countryComboBoxProperties = CreateCountryComboBox(); return countryComboBoxProperties; } } WorldCitiesEntities worldCities; protected WorldCitiesEntities WorldCities { get { if (worldCities == null) worldCities = new WorldCitiesEntities(); return worldCities; } } protected MVCxColumnComboBoxProperties CreateCountryComboBox() { MVCxColumnComboBoxProperties cs = new MVCxColumnComboBoxProperties(); cs.CallbackRouteValues = new { Controller = "Home", Action = "ComboBoxCountryPartial" }; cs.Width = Unit.Percentage(100); cs.TextField = "CountryName"; cs.ValueField = "CountryId"; cs.ValueType = typeof(int); cs.IncrementalFilteringDelay = 1000; cs.IncrementalFilteringMode = IncrementalFilteringMode.Contains; cs.FilterMinLength = 2; cs.CallbackPageSize = 20; cs.ClientSideEvents.SelectedIndexChanged = "CountriesCombo_SelectedIndexChanged"; cs.BindList(WorldCities.Countries.ToList()); return cs; } protected MVCxColumnComboBoxProperties CreateCityComboBox() { MVCxColumnComboBoxProperties cs = new MVCxColumnComboBoxProperties(); cs.CallbackRouteValues = new { Controller = "Home", Action = "ComboBoxCityPartial" }; cs.Width = Unit.Percentage(100); cs.CallbackPageSize = 20; cs.TextField = "CityName"; cs.ValueField = "CityId"; cs.ValueType = typeof(int); cs.IncrementalFilteringDelay = 1000; cs.IncrementalFilteringMode = IncrementalFilteringMode.Contains; cs.FilterMinLength = 2; cs.BindList(OnItemsRequestedByFilterCondition, OnItemRequestedByValue); cs.ClientSideEvents.BeginCallback = "CitiesCombo_BeginCallback"; cs.ClientSideEvents.EndCallback = "CitiesCombo_EndCallback"; return cs; } protected object OnItemRequestedByValue(ListEditItemRequestedByValueEventArgs e) { int id; if (e.Value == null || !int.TryParse(e.Value.ToString(), out id)) return null; var query = WorldCities.Cities.Where(city => city.CityId == id); return query.ToList(); } protected object OnItemsRequestedByFilterCondition(ListEditItemsRequestedByFilterConditionEventArgs e) { IQueryable<City> query; var skip = e.BeginIndex; var take = e.EndIndex - e.BeginIndex + 1; if (EditableCountryID > -1) query = WorldCities.Cities.Where(city => city.CityName.Contains(e.Filter) && city.Country.CountryId == EditableCountryID).OrderBy(city => city.CityId); else query = WorldCities.Cities.Where(city => city.CityName.Contains(e.Filter)).OrderBy(city => city.CityId); query = query.Skip(skip).Take(take); return query.ToList(); } #region IDisposable Members public void Dispose() { if (this.worldCities != null) worldCities.Dispose(); } #endregion } }
Models/Country.cs(vb)
C#
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated from a template. // // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace E4425.Models { using System; using System.Collections.Generic; public partial class Country { public Country() { this.Cities = new HashSet<City>(); this.Customers = new HashSet<Customer>(); } public int CountryId { get; set; } public string CountryName { get; set; } public Nullable<int> CapitalId { get; set; } public virtual ICollection<City> Cities { get; set; } public virtual City City { get; set; } public virtual ICollection<Customer> Customers { get; set; } } }
Models/Customer.cs(vb)
C#
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated from a template. // // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace E4425.Models { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; public partial class Customer { public int CustomerId { get; set; } [Required(ErrorMessage = "Customer cannot be empty")] public string CustomerName { get; set; } [Required(ErrorMessage = "Country should be chosen")] public Nullable<int> CountryId { get; set; } [Required(ErrorMessage = "City should be chosen")] public Nullable<int> CityId { get; set; } public virtual City City { get; set; } public virtual Country Country { get; set; } } }
Models/WoldCitiesModel.Context.cs(vb)
C#
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated from a template. // // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace E4425.Models { using System; using System.Data.Entity; using System.Data.Entity.Infrastructure; public partial class WorldCitiesEntities : DbContext { public WorldCitiesEntities() : base("name=WorldCitiesEntities") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public DbSet<City> Cities { get; set; } public DbSet<Country> Countries { get; set; } public DbSet<Customer> Customers { get; set; } } }
Models/WoldCitiesModel.cs(vb)
C#
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated from a template. // // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------
Views/Home/GridViewPartial.cshtml
Razor
@using E4425.Models; @(Html.DevExpress().GridView<Customer>(settings => { settings.Name = "grid"; settings.KeyFields(c => c.CustomerId); settings.Width = System.Web.UI.WebControls.Unit.Pixel(500); settings.CallbackRouteValues = new { Controller = "Home", Action = "GridViewPartial" }; settings.SettingsEditing.Mode = GridViewEditingMode.EditForm; settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Home", Action = "GridViewEditPartial" }; settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Home", Action = "GridViewInsertPartial" }; settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Home", Action = "GridViewDeletePartial" }; settings.CommandColumn.Visible = true; settings.CommandColumn.ShowNewButtonInHeader = true; settings.CommandColumn.ShowDeleteButton = true; settings.CommandColumn.ShowEditButton = true; settings.Columns.Add(c => c.CustomerId).Visible = false; settings.Columns.Add(c => c.CustomerName); settings.SettingsEditing.ShowModelErrorsForEditors = true; settings.Columns.Add(c => c.CountryId, country => { country.Caption = "Country"; country.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CountryComboBoxProperties)); }); settings.Columns.Add(c => c.CityId, city => { city.Caption = "City"; city.EditorProperties().ComboBox(cs => cs.Assign(ComboBoxPropertiesProvider.Current.CityComboBoxProperties)); }); settings.CellEditorInitialize = (s, e) => { ASPxEdit editor = (ASPxEdit)e.Editor; editor.ValidationSettings.Display = Display.Dynamic; }; settings.ClientSideEvents.BeginCallback = "onBeginCallback"; settings.ClientSideEvents.EndCallback = "onEndCallback"; }).SetEditErrorText(ViewData["EditError"] as string).Bind(Model).GetHtml())
Views/Home/Index.cshtml
Razor
@{ ViewBag.Title = "GridView - How to implement cascading comboboxes in the EditForm"; } <script type="text/javascript"> var customCallback = false; var editingStart = false; var previousValue = null; function onBeginCallback(s, e) { if (e.command = "STARTEDIT") editingStart = true; } function onEndCallback(s, e) { if (editingStart) { customCallback = true; var editor = grid.GetEditor('CityId'); if (editor) { previousValue = editor.GetValue(); editor.PerformCallback(); } } } function CountriesCombo_SelectedIndexChanged(s, e) { customCallback = true; grid.GetEditor('CityId').PerformCallback(); } function CitiesCombo_BeginCallback(s, e) { e.customArgs['CountryId'] = grid.GetEditor('CountryId').GetValue(); } function CitiesCombo_EndCallback(s, e) { var editor = MVCxClientComboBox.Cast(s); if (customCallback) { customCallback = false; if (previousValue) { editor.SetValue(previousValue); previousValue = null; } else { editor.SetSelectedIndex(0); } } } </script> @using (Html.BeginForm()) { @Html.Partial("GridViewPartial") }

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.