Overview

  • The ASP.NET Core Scheduler is a visual UI component that displays a timeline on the horizontal axis and resources (people, rooms, tools, machines) on the vertical axis (as rows).

  • The Scheduler communicates with the ASP.NET Core backend (API controllers) using HTTP requests that return JSON data.

  • Learn how to add the ASP.NET Core Scheduler component to your page, configure it and load data from an SQL Server (using Entity Framework).

  • Visual Studio 2022 solution with ASP.NET Core .NET 8 project (C# source code) is available for download.

  • See how easily you can change the event appearance using CSS and event customization event handlers.

  • The Scheduler component provides advanced features, such as sortable row headers and frozen rows that can display summary/utilization for individual time slots.

  • 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.

How to Add ASP.NET Core Scheduler to Your Application

asp.net core scheduler component simple

Our Visual Studio project will use JavaScript to create the application UI in the ASP.NET Core page. In this application, we will add the Scheduler to the page using the vanilla JavaScript API. The JavaScript Scheduler component can also be used in popular client-side frameworks (such as Angular, React and Vue).

To add the Scheduler to the ASP.NET Core page (Pages/Index.cshtml), the following steps are necessary:

1. Include the DayPilot Pro library:

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

We have used asp-append-version attribute to include a build-specific string. This will ensure that the current version of daypilot-all.min.js will be loaded by the browser (instead of any previous version that might be cached).

2. Insert a placeholder <div> element in the page:

<div id="scheduler"></div>

This is where the ASP.NET Core Scheduler will be rendered.

3. Configure and initialize the Scheduler component using JavaScript in a <script> section:

<script>
  const scheduler = new DayPilot.Scheduler("scheduler", {
    startDate:DayPilot.Date.today().firstDayOfMonth(),
    days: DayPilot.Date.today().daysInMonth(),
    scale: "Day",
    timeHeaders: [
      { groupBy: "Month" },
      { groupBy: "Day", format: "d" }
    ],
    resources = [
      { name: "Resource A", id: "A"},
      { name: "Resource B", id: "B"},
      { name: "Resource C", id: "C"},
    ]
  });
  scheduler.init();
</script>

We have set the following Scheduler component properties:

  • startDate: the first visible day

  • days: number of days to be displayed

  • scale: determines the cell size (see what other scale values are available in the Scheduler)

  • timeHeaders: sets the Scheduler time headers on the X axis (see how to create your own time headers)

  • resources: specifies the Scheduler rows on the Y axis (read more about loading resource data)

This Scheduler configuration displays a fixed timeline (the current month). To let users change the current month, you can add previous/next buttons, a date picker or enable infinite scrolling.

How to Load ASP.NET Core Scheduler Events

asp.net core scheduler component events appointments

Now we are going to load event data and display the events in the ASP.NET Core Scheduler.

You can define the events using the events property of the configuration object.

<script>
  const scheduler = new DayPilot.Scheduler("scheduler", {
    // ...
    events: [
      {
        id: 1,
        text: "Event 1",
        start: "2024-01-03T00:00:00",
        end: "2024-01-11T00:00:00",
        resource: "B"
      },
      {
        id: 2,
        text: "Event 2",
        start: "2024-01-04T00:00:00",
        end: "2024-01-10T00:00:00",
        resource: "D"
      },
    ]  
  });
  scheduler.init();
</script>

This will add a static set of events that will be displayed immediately when the Scheduler is loaded. However, this would require generating the event data directly in the view source. Usually, you will want to split the presentation from the data and load the events using a special HTTP request.

Let's create a simple API endpoint that returns a JSON string with the event data:

GET /api/events

HTTP response:

[{"id":"1","text":"Event 1","start":"2024-07-13T00:00:00","end":"2024-07-18T00:00:00","resource":"A"}]

The /api/events endpoint is handled by an API controller on the server side. It queries the database and returns events/tasks that fall within the date range specified using start and end query string parameters (Controllers/EventsController.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Project.Models;

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

        public EventsController(SchedulerDbContext context)
        {
            _context = context;
        }

        // GET: api/Events
        [HttpGet]
        public async Task<ActionResult<IEnumerable<SchedulerEvent>>> GetSchedulerEvents([FromQuery] DateTime start, [FromQuery] DateTime end)
        {
            if (_context.Events == null)
            {
                return NotFound();
            }
            return await _context.Events.Where(e => !((e.End <= start) || (e.Start >= end))).ToListAsync();
        }
        
        // ...

    }
}

As soon as the API endpoint is working, we can load the data using events.load() method:

<script>
  const scheduler = new DayPilot.Scheduler("scheduler", {
    // ...
  });
  scheduler.init();

  scheduler.events.load("/api/events");
</script>

This is a shortcut method for an HTTP call that could be implemented like this:

const start = scheduler.visibleStart();
const end = scheduler.visibleEnd();

const url = `/api/events?start=${start}&end=${end}`;

const {data} = await DayPilot.Http.get(url);

scheduler.update({events: data});

How to Load ASP.NET Core Scheduler Resources (Rows)

asp.net core scheduler component load resources rows

In the first example, we have defined the resources statically during initialization:

<script>
    const dp = new DayPilot.Scheduler("scheduler", {
      // ...
      resources = [
        { name: "Resource A", id: "A"},
        { name: "Resource B", id: "B"},
        { name: "Resource C", id: "C"},
      ]
    });
    scheduler.init();
</script>

Now we will load the resources using the API as well.

Let's create a similar backend endpoint for resource data:

GET /api/resources

HTTP response:

[{"id":"A","name":"Resource A"},{"id":"B","name":"Resource B"},{"id":"C","name":"Resource C"},{"id":"D","name":"Resource D"}]

Controller implementation in C# (Controllers/ResourcesController.cs);

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Project.Models;

namespace Project.Controllers
{
    [Produces("application/json")]
    [Route("api/resources")]
    public class ResourcesController : Controller
    {

        private readonly SchedulerDbContext _context;

        public ResourcesController(SchedulerDbContext context)
        {
            _context = context;
        }

        // GET: api/resources
        [HttpGet]
        public async Task<ActionResult<IEnumerable<SchedulerResource>>> GetResources()
        {
            return await _context.Resources.ToListAsync();
        }

    }
}

Now we can load the resources using rows.load() method:

<script>
    const dp = new DayPilot.Scheduler("scheduler", {
      // ...
    });
    scheduler.init();

    scheduler.rows.load("/api/resources");

</script>

The ASP.NET Core Scheduler can display additional row data in row header columns.

You can define custom columns using rowHeaderColumns property:

  • The text property specifies the column name that will be displayed in the header above the column.

  • The display property specifies the source property of the resource data item (resources array) that holds the cell value.

The additional columns are useful for displaying related values (capacity, id, location), icons/images, action buttons (e.g. a context menu button). You can also enable row sorting feature which lets you reorder the rows based on the cell values.

const scheduler = new DayPilot.Scheduler("scheduler", {
    rowHeaderColumns: [
        {text: "Name", display: "name" },
        {text: "ID", display: "id" }
    ],
    
    // ...
    
});

Styling the ASP.NET Core Scheduler Events

asp.net core scheduler component event styling

First, we make the event corners rounded by overriding the default CSS theme.

<style>
    .scheduler_default_event_inner {
        border-radius: 20px;
        padding-left: 44px;
    }
</style>

Now we can change the appearance of the ASP.NET Core Scheduler events.

This can be either by modifying the CSS theme or by modifying the event properties using onBeforeEventRender. The onBeforeEventRender handler can set the appearance dynamically for each event - this can be useful when you want to style the events depending on its properties (status, custom rules).

The following onBeforeEventRender implementation makes these adjustments:

  • It hides the event duration bar using args.data.barHidden = true.

  • It sets the event background color using args.data.backColor. The border color will be set automatically to a darker shade of the background (args.data.backColor = "darker").

onBeforeEventRender: (args) => {
    args.data.barHidden = true;
    args.data.fontColor = "#ffffff";
    args.data.borderColor = "darker";
    args.data.backColor = "#0089d0";

    const short = app.initials(args.data.text);

    args.data.areas = [];
    
    // dot
    args.data.areas.push({
        left: 0,
        top: 0,
        width: 40,
        height: 40,
        style: "border-radius: 36px; font-size: 20px; display: flex; align-items: center; justify-content: center;",
        backColor: DayPilot.ColorUtil.darker(args.data.backColor, 1),
        fontColor: "#ffffff",
        text: short
    });
    
    // menu    
    args.data.areas.push({
        right: 30,
        top: 10,
        height: 20,
        width: 20,
        symbol: "icons/daypilot.svg#minichevron-down-2",
        style: "background-color: #fff; border: 1px solid #ccc; box-sizing: border-box; border-radius: 10px; padding: 0px;",
        action: "ContextMenu"
    });

    // delete
    args.data.areas.push({
        right: 6,
        top: 10,
        height: 20,
        width: 20,
        symbol: "icons/daypilot.svg#x-2",
        style: "background-color: #fff; border: 1px solid #ccc; box-sizing: border-box; border-radius: 10px; padding: 0px; padding: 2px;",
        action: "None",
        onClick: async (args) => {
            app.deleteEvent(args.source);
        }
    });

},

Add a Utilization Summary Row at the Top of the ASP.NET Core Scheduler

asp.net core scheduler component utilization summary

In our ASP.NET Core Scheduler page, we will display a special row with utilization histogram. This utilization/availability chart will be displayed in a frozen row that will stay visible even if you scroll down the Scheduler.

First, we need to add the frozen row to the data set with resources. In a previous chapter, you have learned how to load resources from the database.

The updated loadResources() method modifies the array with resources that it loads from /api/resources endpoint. It adds the frozen row (“Utilization”) to the top of the list.

  • This special row is made frozen using frozen: "top" property.

  • The row must be read-only (it will not be possible to create or move events there). To disable drag and drop operations for this row, we add cellsDisabled: true to the row properties.

  • Because the row contains calculated data that depend on the content of the ASP.NET Core Scheduler grid, we need to force an automatic update of its cells after every change by adding cellsAutoUpdated: true.

const app = {
    async loadResources() {
        const {data} = await DayPilot.Http.get("/api/resources");
        const resources = [
            { name: "Utilization", id: "UTILIZATION", frozen: "top", cellsAutoUpdated: true, cellsDisabled: true },
            ...data
        ];
        scheduler.update({resources});
    },
    
    // ...
    
};

Now you can use an onBeforeCellRender event handler to generate the content of the summary row.

  • It looks up all events for the selected date/time range and calculates the utilization as a percentage of the total available slots.

  • For each time column, it displays a color bar with a height corresponding to the utilization percentage.

onBeforeCellRender: args => {
    if (args.cell.resource === "UTILIZATION") {
        
        const color = "#fcb711";
        const borderColor = DayPilot.ColorUtil.darker(color);
        
        const max = scheduler.rows.all().filter(r => r.data.frozen !== "top" && r.children().length === 0).length;

        const start = args.cell.start;
        const end = args.cell.end;
        const inUse = scheduler.events.all().filter(e => {
            return DayPilot.Util.overlaps(start, end, e.start(), e.end());
        }).length;

        const percentage = inUse / max;
        
        args.cell.properties.backColor = "#ffffff";
        if (inUse > 0) {

            const cellHeight = scheduler.eventHeight - 1;
            const barHeight = Math.min(percentage, 1) * cellHeight;

            args.cell.properties.areas = [
                {
                    bottom: 1,
                    height: barHeight,
                    left: 3,
                    right: 3,
                    backColor: color,
                    style: `box-sizing: border-box; border: 1px solid ${borderColor}`,
                }
            ];
        }
    }
},

For a detailed explanation of how you can create a summary row with utilization data, please see the JavaScript Scheduler: Column Summary and Availability Chart tutorial.

Entity Framework Model Classes

The SchedulerEvent class represents the ASP.NET Core Scheduler events/tasks/reservations (SchedulerEvent.cs):

public class SchedulerEvent
{
    public int Id { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public string? Text { get; set; }
    public string? Color { get; set; }

    [JsonPropertyName("resource")]
    public int ResourceId { get; set; }
}

The SchedulerResource class represents resources that will be displayed as rows (SchedulerResource.cs):

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

The Entity Framework migrations that create and initialize the SQL Server database schema are already created. Before running the project for the first time, it’s necessary to create the database using the following command (use the Package Manager console):

Update-Database

SQL Server Database Schema

The migrations will create the following database schema.

[Events] table:

CREATE TABLE [dbo].[Events] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [Start]      DATETIME2 (7)  NOT NULL,
    [End]        DATETIME2 (7)  NOT NULL,
    [Text]       NVARCHAR (MAX) NULL,
    [Color]      NVARCHAR (MAX) NULL,
    [ResourceId] INT            NOT NULL,
    CONSTRAINT [PK_Events] PRIMARY KEY CLUSTERED ([Id] ASC)
);

[Resources] table:

CREATE TABLE [dbo].[Resources] (
    [Id]   INT            IDENTITY (1, 1) NOT NULL,
    [Name] NVARCHAR (MAX) NOT NULL,
    CONSTRAINT [PK_Resources] PRIMARY KEY CLUSTERED ([Id] ASC)
);