Features

  • A reference implementation of JavaScript Scheduler dynamic loading
  • See what Scheduler performance you can expect when displaying 500 resources and 400 events per resource
  • The performance is only affected by the Scheduler viewport size, it's not affected by the total number of events
  • ASP.NET MVC Core, Entity Framework and SQL Sever used on the backend
  • Includes a trial version of DayPilot Pro for JavaScript (see License below)

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

JavaScript Scheduler Configuration

Our JavaScript Scheduler configuration is simple and mostly rely on the default settings. We will use startDate and days properties to display the full year 2019, one cell per day (scale = "Day").

Right after initialization, we load the resources from the server using DayPilot.Scheduler.rows.load() call.

Views/Home/Index.cshtml

@{
    ViewData["Title"] = "Scheduler";
}

<script src="~/js/daypilot/daypilot-all.min.js" asp-append-version="true"></script>

<div id="dp"></div>

<script>
    var dp = new DayPilot.Scheduler("dp");
    dp.startDate = "2019-01-01";
    dp.days = 365;
    dp.timeHeaders = [
        { groupBy: "Month" },
        { groupBy: "Day", format: "d" }
    ];
    dp.scale = "Day";

    dp.init();

    dp.rows.load("/api/Resources");
</script>

Dynamic Event Loading

As the next step, we enable dynamic event loading. This means the Scheduler will fire onScroll event for every scrollbar position change. The events for the current viewport will be loaded using a call to a REST API.

Client-side configuration:

dp.dynamicLoading = true;
dp.onScroll = function (args) {
    args.async = true;
    var url = "/api/Events?start=" + args.viewport.start + "&end=" + args.viewport.end + "&resources=" + args.viewport.resources.join(",");
    DayPilot.Http.ajax({
        url: url,
        success: function (a) {
            args.events = a.data;
            args.loaded();
        }
    });
};

Server-side API:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.UI.Pages.Internal.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Remotion.Linq.Clauses;
using TutorialAspnetCoreSchedulerLarge.Models;

namespace TutorialAspnetCoreSchedulerLarge.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EventsController : ControllerBase
    {
        private readonly CalendarContext _context;

        public EventsController(CalendarContext context)
        {
            _context = context;
        }
        
        // ...

        // GET: api/Events
        [HttpGet]
        public IEnumerable GetEvents([FromQuery] DateTime start, [FromQuery] DateTime end, [FromQuery] string resources)
        {
            if (resources == null)
            {
                return Enumerable.Empty<object>();
            }
            var resArray = resources.Split(',').Select(Int32.Parse).ToList();
            return from e in _context.Events where !((e.End <= start) || (e.Start >= end)) && resArray.Contains(e.ResourceId) select new
            {
                e.Start,
                e.End,
                e.Id,
                e.Text,
                Resource = e.ResourceId
            };
        }

    }
}

Scheduler Performance Tuning

To improve the vertical scrolling performance, we disable the automatic row header width adjustment. This feature is convenient but it may harm smooth scrolling:

dp.rowHeaderWidthAutoFit = false;

We will also clear all existing during dynamic loading (args.clearEvents). This speeds up loading of the viewport events as they don't need to be compared with the existing events.

dp.onScroll = function (args) {
    args.async = true;
    var url = "/api/Events?start=" + args.viewport.start + "&end=" + args.viewport.end + "&resources=" + args.viewport.resources.join(",");
    DayPilot.Http.ajax({
        url: url,
        success: function (a) {
            args.clearEvents = true;
            args.events = a.data;
            args.loaded();
        }
    });
};

Data Layer: Entity Framework + SQL Server Database

The database uses a simple schema defined using model classes:

Models/Data.cs

public class Event
{
    public int Id { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public int ResourceId { get; set; }
    public string Text { get; set; }
}

public class Resource
{
    public int Id { get; set; }
    public string Name { get; set; }
}

The database is automatically created on first load and it is initialized with 500 resources:

Models/Data.cs

using System;
using Microsoft.EntityFrameworkCore;

namespace TutorialAspnetCoreSchedulerLarge.Models
{
    public class CalendarContext : DbContext
    {
        public DbSet<Event> Events { get; set; }
        public DbSet<Resource> Resources { get; set; }

        public CalendarContext(DbContextOptions<CalendarContext> options):base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            for (var y = 1; y <= 500; y++)
            {
                modelBuilder.Entity<Resource>().HasData(new Resource { Id = y, Name = "Resource " + y });
            }            
        }
    }

    // ...

}

The database connection string is defined in appsetings.json. It uses a database called "DayPilot.TutorialSchedulerLarge" and connects using LocalDB.

appsettings.json

{
    "Logging": {
        "LogLevel": {
            "Default": "Warning"
        }
    },
    "AllowedHosts": "*",
    "ConnectionStrings": {
        "CalendarContext": "Server=(localdb)\\mssqllocaldb;Database=DayPilot.TutorialSchedulerLarge;Trusted_Connection=True"
    }
}

Generating Sample Events

asp.net-core-javascript-scheduler-generate-events.png

In order to generate sample data, click the "Generate..." link above the Scheduler. This will call a REST endpoint that generates 200,000 events (400 events per row).