@(Html.DevExtreme().LoadPanel()
        .ID("loadPanel")
        .Position(p => p.Of("#gridContainer"))
        .Visible(false)
)
@(Html.DevExtreme().DataGrid<DevExtreme.NETCore.Demos.Models.Northwind.Order>()
    .ID("gridContainer")
    .KeyExpr("OrderID")
    .ShowBorders(true)
    .Pager(p => p.Visible(true))
    .RepaintChangesOnly(true)
    .LoadPanel(loadPanel => loadPanel.Enabled(false))
    .Editing(e => e
        .Mode(GridEditMode.Row)
        .AllowAdding(true)
        .AllowDeleting(true)
        .AllowUpdating(true)
    )
    .Columns(columns => {
        columns.AddFor(m => m.OrderID).AllowEditing(false);
        columns.AddFor(m => m.ShipName);
        columns.AddFor(m => m.ShipCountry);
        columns.AddFor(m => m.ShipCity);
        columns.AddFor(m => m.ShipAddress);
        columns.AddFor(m => m.OrderDate).DataType(GridColumnDataType.Date);
        columns.AddFor(m => m.Freight);
    })
    .OnOptionChanged("onOptionChanged")
    .OnSaving("onSaving")
)
<div class="options">
    <div class="caption">Options</div>
    <div class="option">
        <span>Edit Row Key:</span>
        <div id="editRowKey">null</div>
    </div>
    <div class="option">
        <span>Changes:</span>
        <div id="changes">[]</div>
    </div>
</div>
<script>
    var dataGrid, loadPanel;
    $(function () {
        dataGrid = $("#gridContainer").dxDataGrid("instance");
        loadPanel = $("#loadPanel").dxLoadPanel("instance");
        loadPanel.show();
        sendRequest('@Url.RouteUrl(new { controller = "DataGridWebApi", action = "Orders", skip = 700 })')
            .always(() => { loadPanel.hide(); })
            .done((data) => {
                dataGrid.option("dataSource", data);
            });
    });
    function onOptionChanged(e) {
        if (e.name === "editing") {
            var editRowKey = e.component.option("editing.editRowKey"),
                changes = e.component.option("editing.changes");
            $("#editRowKey").text(editRowKey === null ? "null" : editRowKey);
            changes = changes.map((change) => {
                return {
                    type: change.type,
                    key: change.type !== "insert" ? change.key : undefined,
                    data: change.data
                };
            });
            $("#changes").text(JSON.stringify(changes, null, " "));
        }
    };
    function onSaving(e) {
        var change = e.changes[0];
        if (change) {
            e.cancel = true;
            loadPanel.show();
            e.promise = saveChange(change)
                .always(() => { loadPanel.hide(); })
                .done((data) => {
                    var orders = e.component.option("dataSource");
                    if (change.type === "insert") {
                        change.data = data;
                    }
                    orders = DevExpress.data.applyChanges(orders, [change], { keyExpr: "OrderID" });
                    e.component.option({
                        dataSource: orders,
                        editing: {
                            editRowKey: null,
                            changes: []
                        }
                    });
                });
        }
    };
    function saveChange(change) {
        switch (change.type) {
            case "insert":
                return sendRequest('@Url.RouteUrl(new { controller = "DataGridWebApi", action = "InsertOrder" })', "POST", { values: JSON.stringify(change.data) });
            case "update":
                return sendRequest('@Url.RouteUrl(new { controller = "DataGridWebApi", action = "UpdateOrder" })', "PUT", { key: change.key, values: JSON.stringify(change.data) });
            case "remove":
                return sendRequest('@Url.RouteUrl(new { controller = "DataGridWebApi", action = "DeleteOrder" })', "DELETE", { key: change.key });
        }
    };
    function sendRequest(url, method, data) {
        var d = $.Deferred();
        method = method || "GET";
        $.ajax(url, {
            method: method,
            data: data,
            cache: false,
            xhrFields: { withCredentials: true }
        }).done(function (result) {
            d.resolve(method === "GET" ? result.data : result);
        }).fail(function (xhr) {
            d.reject(xhr.responseJSON ? xhr.responseJSON.Message : xhr.statusText);
        });
        return d.promise();
    };
</script>
        
        using DevExtreme.NETCore.Demos.Models;
using DevExtreme.NETCore.Demos.Models.DataGrid;
using DevExtreme.NETCore.Demos.Models.SampleData;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Controllers {
    public class DataGridController : Controller {
        public ActionResult EditStateManagement() {
            return View();
        }
    }
}
        
        using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DevExpress.Utils.Serializing.Helpers;
using DevExtreme.NETCore.Demos.Models.DataGrid;
using DevExtreme.NETCore.Demos.Models.Northwind;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Controllers.ApiControllers {
    [Route("api/[controller]/[action]")]
    public class DataGridWebApiController : Controller {
        InMemoryNorthwindContext _nwind;
        public DataGridWebApiController(NorthwindContext nwind, IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache) {
            _nwind = new InMemoryNorthwindContext(nwind, httpContextAccessor, memoryCache);
        }
        [HttpGet]
        public object Orders(DataSourceLoadOptions loadOptions) {
            return DataSourceLoader.Load(_nwind.Orders, loadOptions);
        }
        [HttpPost]
        public IActionResult InsertOrder(string values) {
            var newOrder = new Order();
            JsonPopulateObjectExtensions.PopulateObject(values, newOrder);
            if(!TryValidateModel(newOrder))
                return BadRequest(ModelState.GetFullErrorMessage());
            _nwind.Orders.Add(newOrder);
            _nwind.SaveChanges();
            return Ok(newOrder);
        }
        [HttpPut]
        public IActionResult UpdateOrder(int key, string values) {
            var order = _nwind.Orders.First(o => o.OrderID == key);
            JsonPopulateObjectExtensions.PopulateObject(values, order);
            if(!TryValidateModel(order))
                return BadRequest(ModelState.GetFullErrorMessage());
            _nwind.SaveChanges();
            return Ok(order);
        }
        [HttpDelete]
        public void DeleteOrder(int key) {
            var order = _nwind.Orders.First(o => o.OrderID == key);
            _nwind.Orders.Remove(order);
            _nwind.SaveChanges();
        }
        // additional actions
        [HttpGet]
        public object OrderDetails(int orderID, DataSourceLoadOptions loadOptions) {
            return DataSourceLoader.Load(
                from i in _nwind.Order_Details
                where i.OrderID == orderID
                select new {
                    Product = i.Product.ProductName,
                    Price = i.UnitPrice,
                    i.Quantity,
                    Sum = i.UnitPrice * i.Quantity
                },
                loadOptions
            );
        }
        [HttpGet]
        public object ShippersLookup(DataSourceLoadOptions loadOptions) {
            var lookup = from i in _nwind.Shippers
                         orderby i.CompanyName
                         select new {
                             Value = i.ShipperID,
                             Text = i.CompanyName
                         };
            return DataSourceLoader.Load(lookup, loadOptions);
        }
        [HttpGet]
        public object CustomersLookup(DataSourceLoadOptions loadOptions) {
            var lookup = from i in _nwind.Customers
                         let text = i.CompanyName + " (" + i.Country + ")"
                         orderby i.CompanyName
                         select new {
                             Value = i.CustomerID,
                             Text = text
                         };
            return DataSourceLoader.Load(lookup, loadOptions);
        }
        [HttpPost]
        public object Batch([FromBody] List<DataChange> changes) {
            foreach(var change in changes) {
                Order order;
                if(change.Type == "update" || change.Type == "remove") {
                    var key = Convert.ToInt32(change.Key);
                    order = _nwind.Orders.First(o => o.OrderID == key);
                } else {
                    order = new Order();
                }
                if(change.Type == "insert" || change.Type == "update") {
                    JsonPopulateObjectExtensions.PopulateObject(change.Data.ToString(), order);
                    if(!TryValidateModel(order))
                        return BadRequest(ModelState.GetFullErrorMessage());
                    if(change.Type == "insert") {
                        _nwind.Orders.Add(order);
                    }
                    change.Data = order;
                } else if(change.Type == "remove") {
                    _nwind.Orders.Remove(order);
                }
            }
            _nwind.SaveChanges();
            return Ok(changes);
        }
    }
}
        
        using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
namespace DevExtreme.NETCore.Demos.Models.Northwind {
    public class InMemoryNorthwindContext : InMemoryDataContext<Order> {
        NorthwindContext _nwind;
        public InMemoryNorthwindContext(NorthwindContext nwind, IHttpContextAccessor contextAccessor, IMemoryCache memoryCache)
            : base(contextAccessor, memoryCache) {
            _nwind = nwind;
        }
        public ICollection<Order> Orders => ItemsInternal;
        public DbSet<Customer> Customers => _nwind.Customers;
        public DbSet<Order_Detail> Order_Details => _nwind.Order_Details;
        public DbSet<Shipper> Shippers => _nwind.Shippers;
        protected override IEnumerable<Order> Source => _nwind.Orders;
        protected override int GetKey(Order item) => item.OrderID;
        protected override void SetKey(Order item, int key) => item.OrderID = key;
    }
}
        
        #gridContainer {
    height: 440px;
}
.options {
    padding: 20px;
    margin-top: 20px;
    background-color: rgba(191, 191, 191, 0.15);
}
.caption {
    margin-bottom: 10px;
    font-weight: 500;
    font-size: 18px;
}
.option {
    margin-bottom: 10px;
}
.option > span {
    position: relative;
    margin-right: 10px;
}
.option > div {
    display: inline-block;
    font-weight: bold;
}