Your search did not match any results.

Scheduler - SignalR Service

This demo shows how you can use a SignalR service to synchronize appointments across different devices. To emulate such a setup, each Scheduler on this page reads data from its own separate data store. Changes made in one control are repeated in the other and persist until the browser session has expired.

Backend API
<div class="schedulers"> @for(int i = 1; i <= 2; i++) { <div class="column-@i"> @(Html.DevExtreme().Scheduler() .ID(string.Format("{0}{1}", "scheduler", i)) .DataSource(d => d.Mvc() .Controller("SchedulerSignalR") .Key("AppointmentId") .LoadAction("Get") .UpdateAction("Put") .InsertAction("Post") .DeleteAction("Delete") ) .TimeZone("America/Los_Angeles") .RemoteFiltering(true) .Views(new[] { SchedulerViewType.Day, SchedulerViewType.WorkWeek }) .CurrentView(SchedulerViewType.Day) .CurrentDate(new DateTime(2021, 4, 27)) .StartDayHour(9) .EndDayHour(19) .Height(600) .DateSerializationFormat("yyyy-MM-ddTHH:mm:ssZ") .TextExpr("Text") .DescriptionExpr("Description") .StartDateExpr("StartDate") .EndDateExpr("EndDate") .AllDayExpr("AllDay") ) </div> } </div> <script src="~/signalr/signalr-session-id.js"></script> <script src="~/signalr/signalr-client.js"></script> <script> var connection = new signalR.HubConnectionBuilder() .withUrl("@Url.Content("~/schedulerSignalRHub")", { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }) .configureLogging(signalR.LogLevel.Information) .build(); $(function () { var store1 = $("#scheduler1").dxScheduler("getDataSource").store(); var store2 = $("#scheduler2").dxScheduler("getDataSource").store(); connection.start() .then(function () { connection.on("update", function (key, data) { store1.push([{ type: "update", key: key, data: data }]); store2.push([{ type: "update", key: key, data: data }]); }); connection.on("insert", function (data) { store1.push([{ type: "insert", data: data }]); store2.push([{ type: "insert", data: data }]); }); connection.on("remove", function (key) { store1.push([{ type: "remove", key: key }]); store2.push([{ type: "remove", key: key }]); }); }); }); </script>
using DevExtreme.NETCore.Demos.Models.SampleData; using DevExtreme.NETCore.Demos.ViewModels; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; namespace DevExtreme.NETCore.Demos.Controllers { public class SchedulerController : Controller { public ActionResult SignalRService() { return View(); } } }
using System; using System.Linq; using DevExtreme.AspNet.Data; using DevExtreme.AspNet.Mvc; using DevExpress.Utils.Serializing.Helpers; using DevExtreme.NETCore.Demos.Hubs; using DevExtreme.NETCore.Demos.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Caching.Memory; using DevExpress.Data.Utils; namespace DevExtreme.NETCore.Demos.Controllers.ApiControllers { [Route("api/[controller]")] public class SchedulerSignalRController : Controller { InMemoryAppointmentsDataContext _data; private static readonly NonCryptographicRandom random = NonCryptographicRandom.System; private IHubContext<SchedulerSignalRHub> hubContext; public SchedulerSignalRController(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, IHubContext<SchedulerSignalRHub> hubcontext) { _data = new InMemoryAppointmentsDataContext(httpContextAccessor, memoryCache); hubContext = hubcontext; } [HttpGet] public object Get(DataSourceLoadOptions loadOptions) { return DataSourceLoader.Load(_data.Appointments, loadOptions); } [HttpPost] public IActionResult Post(string values) { var newAppointment = new Appointment(); JsonPopulateObjectExtensions.PopulateObject(values, newAppointment); if(!TryValidateModel(newAppointment)) return BadRequest(ModelState.GetFullErrorMessage()); _data.Appointments.Add(newAppointment); _data.SaveChanges(); var groupName = GetGroupName(); if(groupName != null) { hubContext.Clients.Group(GetGroupName()).SendAsync("insert", newAppointment); } return Ok(); } [HttpPut] public IActionResult Put(int key, string values) { var appointment = _data.Appointments.First(a => a.AppointmentId == key); JsonPopulateObjectExtensions.PopulateObject(values, appointment); if(!TryValidateModel(appointment)) return BadRequest(ModelState.GetFullErrorMessage()); _data.SaveChanges(); var groupName = GetGroupName(); if(groupName != null) { hubContext.Clients.Group(GetGroupName()).SendAsync("update", key, appointment); } return Ok(); } [HttpDelete] public void Delete(int key) { var appointment = _data.Appointments.First(a => a.AppointmentId == key); _data.Appointments.Remove(appointment); _data.SaveChanges(); var groupName = GetGroupName(); if(groupName != null) { hubContext.Clients.Group(GetGroupName()).SendAsync("remove", key); } } string GetGroupName() { HttpContext.Request.Cookies.TryGetValue(SchedulerSignalRHub.GroupIdKey, out var cookie); return cookie ?? "0"; } } }
using System; using System.Text.Json.Serialization; namespace DevExtreme.NETCore.Demos.Models { public class Appointment { [JsonPropertyName("AppointmentId")] public int AppointmentId { get; set; } [JsonPropertyName("Text")] public string Text { get; set; } [JsonPropertyName("Description")] public string Description { get; set; } [JsonPropertyName("StartDate")] public string StartDate { get; set; } [JsonPropertyName("EndDate")] public string EndDate { get; set; } [JsonPropertyName("AllDay")] public bool AllDay { get; set; } [JsonPropertyName("RecurrenceRule")] public string RecurrenceRule { get; set; } [JsonPropertyName("RecurrenceException")] public string RecurrenceException { get; set; } } public class DisableDatesAppointment { [JsonPropertyName("AppointmentId")] public int AppointmentId { get; set; } [JsonPropertyName("Text")] public string Text { get; set; } [JsonPropertyName("Description")] public string Description { get; set; } [JsonPropertyName("StartDate")] public DateTime StartDate { get; set; } [JsonPropertyName("EndDate")] public DateTime EndDate { get; set; } [JsonPropertyName("AllDay")] public bool AllDay { get; set; } [JsonPropertyName("RecurrenceRule")] public string RecurrenceRule { get; set; } [JsonPropertyName("RecurrenceException")] public string RecurrenceException { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; namespace DevExtreme.NETCore.Demos.Models.SampleData { public partial class SampleData { public static readonly IEnumerable<Appointment> Appointments = new[] { new Appointment { AppointmentId = 1, Text = "Website Re-Design Plan", StartDate = "2021-04-26T16:30:00.000Z", EndDate = "2021-04-26T18:30:00.000Z" }, new Appointment { AppointmentId = 2, Text = "Book Flights to San Fran for Sales Trip", StartDate = "2021-04-26T19:00:00.000Z", EndDate = "2021-04-26T20:00:00.000Z", AllDay = true }, new Appointment { AppointmentId = 3, Text = "Install New Router in Dev Room", StartDate = "2021-04-26T21:30:00.000Z", EndDate = "2021-04-26T22:30:00.000Z" }, new Appointment { AppointmentId = 4, Text = "Approve Personal Computer Upgrade Plan", StartDate = "2021-04-27T17:00:00.000Z", EndDate = "2021-04-27T18:00:00.000Z" }, new Appointment { AppointmentId = 5, Text = "Final Budget Review", StartDate = "2021-04-27T19:00:00.000Z", EndDate = "2021-04-27T20:35:00.000Z" }, new Appointment { AppointmentId = 6, Text = "New Brochures", StartDate = "2021-04-27T21:30:00.000Z", EndDate = "2021-04-27T22:45:00.000Z" }, new Appointment { AppointmentId = 7, Text = "Install New Database", StartDate = "2021-04-28T16:45:00.000Z", EndDate = "2021-04-28T18:15:00.000Z" }, new Appointment { AppointmentId = 8, Text = "Approve New Online Marketing Strategy", StartDate = "2021-04-28T19:00:00.000Z", EndDate = "2021-04-28T21:00:00.000Z" }, new Appointment { AppointmentId = 9, Text = "Upgrade Personal Computers", StartDate = "2021-04-28T22:15:00.000Z", EndDate = "2021-04-28T23:30:00.000Z" }, new Appointment { AppointmentId = 10, Text = "Customer Workshop", StartDate = "2021-04-29T18:00:00.000Z", EndDate = "2021-04-29T19:00:00.000Z", AllDay = true }, new Appointment { AppointmentId = 11, Text = "Prepare 2021 Marketing Plan", StartDate = "2021-04-29T18:00:00.000Z", EndDate = "2021-04-29T20:30:00.000Z" }, new Appointment { AppointmentId = 12, Text = "Brochure Design Review", StartDate = "2021-04-29T21:00:00.000Z", EndDate = "2021-04-29T22:30:00.000Z" }, new Appointment { AppointmentId = 13, Text = "Create Icons for Website", StartDate = "2021-04-30T17:00:00.000Z", EndDate = "2021-04-30T18:30:00.000Z" }, new Appointment { AppointmentId = 14, Text = "Upgrade Server Hardware", StartDate = "2021-04-30T21:30:00.000Z", EndDate = "2021-04-30T23:00:00.000Z" }, new Appointment { AppointmentId = 15, Text = "Submit New Website Design", StartDate = "2021-04-30T23:30:00.000Z", EndDate = "2021-05-01T01:00:00.000Z" }, new Appointment { AppointmentId = 16, Text = "Launch New Website", StartDate = "2021-04-30T19:20:00.000Z", EndDate = "2021-04-30T21:00:00.000Z" } }; } }
using System; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; namespace DevExtreme.NETCore.Demos.Hubs { public class SchedulerSignalRHub : Hub { public static string GroupIdKey = "dx-SchedulerSignalRHub-groupId"; public override Task OnConnectedAsync() { string groupId; Context.GetHttpContext().Request.Cookies.TryGetValue(GroupIdKey, out groupId); Groups.AddToGroupAsync(Context.ConnectionId, groupId ?? "0"); return base.OnConnectedAsync(); } } }
.schedulers { display: flex; } .column-1 { padding-right: 5px; } .column-2 { padding-left: 5px; } .dx-scheduler-small .dx-scheduler-view-switcher.dx-tabs { display: table; }

Follow the steps below to implement this functionality. Note again that this demo repeats all steps twice for the two Schedulers. Your project will have a single control and a single store.

  1. Configure a CustomStore. In this demo, we use the createStore method (part of the DevExtreme.AspNet.data extension).

  2. Create the Scheduler and use its dataSource property to bind it to the store instance.

  3. When a push notification is received, call the store's push(changes) method to update the store's data (see the connection.on event handlers).

For server-side configuration, refer to the ASP.NET MVC version of this demo.

For more information about integration with push services, refer to the following help topic: Integration with Push Services.