$(() => {
const BASE_PATH = 'https://js.devexpress.com/Demos/NetCore/';
const url = `${BASE_PATH}api/DataGridCollaborativeEditing/`;
const groupId = new DevExpress.data.Guid().toString();
const createStore = function () {
return DevExpress.data.AspNet.createStore({
key: 'ID',
loadUrl: url,
insertUrl: url,
updateUrl: url,
deleteUrl: url,
onBeforeSend(method, ajaxOptions) {
ajaxOptions.data.groupId = groupId;
},
});
};
const createDataGrid = function (gridId, store) {
$(`#${gridId}`).dxDataGrid({
dataSource: store,
height: 600,
showBorders: true,
repaintChangesOnly: true,
highlightChanges: true,
paging: {
enabled: false,
},
editing: {
mode: 'cell',
refreshMode: 'reshape',
allowUpdating: true,
allowDeleting: true,
allowAdding: true,
useIcons: true,
},
columns: [
{
dataField: 'Prefix',
caption: 'Title',
width: 50,
validationRules: [{ type: 'required' }],
},
{
dataField: 'FirstName',
validationRules: [{ type: 'required' }],
},
{
dataField: 'StateID',
caption: 'State',
lookup: {
dataSource: DevExpress.data.AspNet.createStore({
key: 'ID',
loadUrl: `${BASE_PATH}api/DataGridStatesLookup`,
}),
displayExpr: 'Name',
valueExpr: 'ID',
},
validationRules: [{ type: 'required' }],
},
{
dataField: 'BirthDate',
dataType: 'date',
validationRules: [{
type: 'range',
max: new Date(3000, 0),
message: 'Date can not be greater than 01/01/3000',
}],
},
],
});
};
const store1 = createStore();
const store2 = createStore();
const updateStores = function (events) {
store1.push(events);
store2.push(events);
};
createDataGrid('grid1', store1);
createDataGrid('grid2', store2);
const hubUrl = `${BASE_PATH}dataGridCollaborativeEditingHub?GroupId=${groupId}`;
const connection = new signalR.HubConnectionBuilder()
.withUrl(hubUrl, {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
})
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start()
.then(() => {
connection.on('update', (key, data) => {
updateStores([{ type: 'update', key, data }]);
});
connection.on('insert', (data) => {
updateStores([{ type: 'insert', data }]);
});
connection.on('remove', (key) => {
updateStores([{ type: 'remove', key }]);
});
});
});
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>window.jQuery || document.write(decodeURIComponent('%3Cscript src="js/jquery.min.js"%3E%3C/script%3E'))</script>
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.5/css/dx.light.css" />
<script src="js/dx.all.js"></script>
<script src="js/signalr.js"></script>
<script src="https://unpkg.com/devextreme-aspnet-data@4.0.0/js/dx.aspnet.data.js"></script>
<script src="index.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body class="dx-viewport">
<div class="demo-container">
<div class="tables">
<div class="column">
<div id="grid1"></div>
</div>
<div class="column">
<div id="grid2"></div>
</div>
</div>
</div>
</body>
</html>
.tables {
display: flex;
}
.column:first-child {
width: 50%;
padding-right: 15px;
}
.column:last-child {
width: 50%;
padding-left: 15px;
}
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DevExtreme.MVC.Demos.Hubs;
using DevExtreme.MVC.Demos.Models.DataGrid;
using DevExtreme.MVC.Demos.Models.SampleData;
using Microsoft.AspNet.SignalR;
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Web.Http;
using DevExpress.Data.Utils;
namespace DevExtreme.MVC.Demos.Controllers.ApiControllers {
public class DataGridCollaborativeEditingController : ApiController {
static readonly NonCryptographicRandom random = NonCryptographicRandom.System;
IHubContext hubContext;
public DataGridCollaborativeEditingController() {
hubContext = GlobalHost.ConnectionManager.GetHubContext<DataGridCollaborativeEditingHub>();
}
[HttpGet]
public object Get(DataSourceLoadOptions loadOptions) {
return DataSourceLoader.Load(SampleData.DataGridEmployees, loadOptions);
}
[HttpPost]
public HttpResponseMessage Post(FormDataCollection form) {
var values = form.Get("values");
var groupId = form.Get("groupId");
var newEmployee = new Employee();
JsonConvert.PopulateObject(values, newEmployee);
newEmployee.ID = random.Next(int.MaxValue);
Validate(newEmployee);
if(!ModelState.IsValid) {
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage());
}
// db.Employees.Add(newEmployee);
// db.SaveChanges();
hubContext.Clients.Group(groupId).insert(newEmployee);
return Request.CreateResponse(newEmployee);
}
[HttpPut]
public HttpResponseMessage Put(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
var values = form.Get("values");
var groupId = form.Get("groupId");
var employee = new Employee();
JsonConvert.PopulateObject(values, employee);
employee.ID = key;
Validate(employee);
if(!ModelState.IsValid) {
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState.GetFullErrorMessage());
}
// db.SaveChanges();
hubContext.Clients.Group(groupId).update(key, employee);
return Request.CreateResponse(employee);
}
[HttpDelete]
public void Delete(FormDataCollection form) {
var key = Convert.ToInt32(form.Get("key"));
var groupId = form.Get("groupId");
// var employee = db.Employees.First(a => a.ID == key);
// db.Employees.Remove(employee);
// db.SaveChanges();
hubContext.Clients.Group(groupId).remove(key);
}
}
}
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
namespace DevExtreme.MVC.Demos.Hubs {
public class DataGridCollaborativeEditingHub : Hub {
public override Task OnConnected() {
var groupId = Context.QueryString["GroupId"];
Groups.Add(Context.ConnectionId, groupId);
return base.OnConnected();
}
}
}