Skip to main content

dhtmlxScheduler with Blazor

This tutorial gives you step-by-step instructions on how to host Scheduler inside a Blazor Web App. Blazor renders the host page, JavaScript interop initializes the Scheduler widget, and an ASP.NET Core Web API controller serves CRUD requests.

The application is built with the .NET 10 SDK and Visual Studio Code with the C# Dev Kit. Visual Studio 2022 works as well. To keep the example focused on Scheduler integration, the demo stores events in an in-memory list rather than a database; for a persistent storage example see the ASP.NET Core integration guide.

note

The complete source code is available on GitHub.

Step 1. Creating a project

Create a new Blazor Web App from the command line:

dotnet new blazor -o BlazorApp
cd BlazorApp

Open the resulting folder in VS Code (code .) or in Visual Studio 2022. The default template gives you Components/App.razor, Components/Pages/Home.razor, Program.cs, and a wwwroot/ folder - all the entry points we need.

Step 2. Adding Scheduler to the page

Scheduler ships as a JavaScript library. Place its files together with our own initialization script under wwwroot/lib/scheduler/, so the wwwroot folder structure looks like this:

wwwroot/
lib/
bootstrap/
scheduler/
dhtmlxscheduler.js
dhtmlxscheduler.css
scheduler.js

Note, that scheduler files are added from the project's local copy in this demo. If you prefer the CDN version, replace the <link/> and <script> tags below with CDN URLs.

Create scheduler.js with a minimal initialization function:

wwwroot/lib/scheduler/scheduler.js
function initScheduler() {
scheduler.init("scheduler_here", new Date(), "week");
}

Open Components/App.razor and reference the Scheduler CSS in <head> and the two scripts at the end of <body>:

Components/App.razor
<!DOCTYPE html>
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["BlazorApp.styles.css"]" />
<link rel="stylesheet" href="@Assets["lib/scheduler/dhtmlxscheduler.css"]" /> /*!*/
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<ReconnectModal />
<script src="@Assets["_framework/blazor.web.js"]"></script>
<script src="@Assets["lib/scheduler/dhtmlxscheduler.js"]"></script> /*!*/
<script src="@Assets["lib/scheduler/scheduler.js"]"></script> /*!*/
</body>
</html>

Now host Scheduler on the home page. Replace the contents of Components/Pages/Home.razor with:

Components/Pages/Home.razor
@page "/"
@inject IJSRuntime JSRuntime
@rendermode InteractiveServer

<h3>Scheduler</h3>

<div id="scheduler_here" style={{width: '100%', height: '600px', border: '1px solid #ccc'}}></div>

@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("initScheduler");
}
}
}

The page renders an empty <div> and, after the first render pass, calls into JavaScript via IJSRuntime to attach Scheduler to it. Run the application now (dotnet run) and you should see an empty Scheduler on the home page.

Next steps will show you how to create a backend API and connect Scheduler to it.

Step 3. Creating models

Let's begin with a data model. You'll need a class for scheduler events. dhtmlxScheduler uses non-conventional names for model properties from the .NET world perspective. This uses the same DTO pattern explained in the ASP.NET Core tutorial; the Blazor-adapted version is below.

Create a Models folder in the project root. First, the domain model:

Models/SchedulerEvent.cs
namespace BlazorApp.Models;

public class SchedulerEvent
{
public int Id { get; set; }
public string? Text { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}

Note, that scheduler events can have all kinds of additional properties, which can be utilized in the calendar. We're showing you the basic stuff here.

The DTO uses the snake_case names that Scheduler expects on the wire and converts dates to and from ISO yyyy-MM-ddTHH:mm:ss strings:

Models/WebAPIEvent.cs
namespace BlazorApp.Models;

public class WebAPIEvent
{
public int id { get; set; }
public string? text { get; set; }
public string? start_date { get; set; }
public string? end_date { get; set; }

// SchedulerEvent -> WebAPIEvent
public static explicit operator WebAPIEvent(SchedulerEvent ev)
{
return new WebAPIEvent
{
id = ev.Id,
text = ev.Text,
start_date = ev.StartDate.ToString("yyyy-MM-ddTHH:mm:ss"),
end_date = ev.EndDate.ToString("yyyy-MM-ddTHH:mm:ss")
};
}

// WebAPIEvent -> SchedulerEvent
public static explicit operator SchedulerEvent(WebAPIEvent ev)
{
return new SchedulerEvent
{
Id = ev.id,
Text = ev.text,
StartDate = ev.start_date != null ? DateTime.Parse(ev.start_date) : DateTime.UtcNow,
EndDate = ev.end_date != null ? DateTime.Parse(ev.end_date) : DateTime.UtcNow
};
}
}

Step 4. Implementing Web API

Now it's time for the actual REST API implementation. Create the Controllers folder and add a controller for our events:

Controllers/SchedulerController.cs
using BlazorApp.Models;
using Microsoft.AspNetCore.Mvc;

namespace BlazorApp.Controllers;

[ApiController]
[Route("api/scheduler")]
public class SchedulerController : ControllerBase
{
private static List<SchedulerEvent> _events = new()
{
new SchedulerEvent
{
Id = 1,
Text = "Test event 1",
StartDate = DateTime.Now.Date.AddHours(6),
EndDate = DateTime.Now.Date.AddHours(8)
},
new SchedulerEvent
{
Id = 2,
Text = "Test event 2",
StartDate = DateTime.Now.Date.AddHours(9),
EndDate = DateTime.Now.Date.AddHours(11)
}
};

private static int _nextId = 3;

[HttpGet]
public IActionResult Get() => Ok(_events.Select(e => (WebAPIEvent)e));

[HttpPost]
public IActionResult Post([FromBody] WebAPIEvent dto)
{
if (!ModelState.IsValid) return BadRequest();
var evt = (SchedulerEvent)dto;
evt.Id = _nextId++;
_events.Add(evt);
return Ok(new { tid = evt.Id, action = "inserted" });
}

[HttpPut("{id:int}")]
public IActionResult Put(int id, [FromBody] WebAPIEvent dto)
{
if (!ModelState.IsValid) return BadRequest();
var index = _events.FindIndex(e => e.Id == id);
if (index == -1) return NotFound();
var evt = (SchedulerEvent)dto;
evt.Id = id;
_events[index] = evt;
return Ok(new { action = "updated" });
}

[HttpDelete("{id:int}")]
public IActionResult Delete(int id)
{
var removed = _events.RemoveAll(e => e.Id == id) > 0;
return removed ? Ok(new { action = "deleted" }) : NotFound();
}
}
note

This implementation uses a static List<SchedulerEvent> for simplicity - data does not persist across app restarts or multiple users. Perfect for trying CRUD; for production swap the list for an Entity Framework Core context.

Next, update Program.cs to register the controller:

Program.cs
using BlazorApp.Components;

var builder = WebApplication.CreateBuilder(args);

// Blazor services
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddServerSideBlazor()
.AddCircuitOptions(options =>
{
options.DetailedErrors = true;
});

// Web API controllers
builder.Services.AddControllers(); /*!*/

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
app.UseAntiforgery();

app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();

// Map API endpoints
app.MapControllers(); /*!*/

app.Run();

The last part is to load events from the API and connect a data processor so changes flow back to the controller. Update scheduler.js:

wwwroot/lib/scheduler/scheduler.js
function initScheduler() {
// The C# DTO emits ISO yyyy-MM-ddTHH:mm:ss; the default date_format config can't
// express the "T" delimiter, so override the format_date / parse_date templates.
scheduler.templates.format_date = function (date) { /*!*/
return date.toISOString(); /*!*/
}; /*!*/
scheduler.templates.parse_date = function (datestr) { /*!*/
return new Date(datestr); /*!*/
}; /*!*/

scheduler.init("scheduler_here", new Date(), "week");

// load data from backend
scheduler.load("/api/scheduler"); /*!*/
// connect backend to scheduler
const dp = scheduler.createDataProcessor({ /*!*/
url: "/api/scheduler", /*!*/
mode: "REST-JSON" /*!*/
}); /*!*/
}

The default date_format config string can't express the ISO T delimiter, so the demo overrides the format_date and parse_date templates instead. toISOString() always emits UTC with a trailing Z, and new Date(datestr) parses both the UTC strings the client produces and the local-time strings the server emits.

Everything is ready. You can run the application and see the fully-fledged Scheduler.

See also

The Web API controller in this tutorial is a basic CRUD skeleton on top of in-memory storage. The topics below apply equally to a Blazor host but are not duplicated here - follow the ASP.NET Core integration guide:

For a persistent backend (Entity Framework Core + SQL Server), the ASP.NET Core tutorial covers the full setup.

What's next

Now you have a fully functioning Scheduler. You can view the full code on GitHub, clone or download it and use it for your projects.

You can also check guides on the numerous features of Scheduler or tutorials on integration of Scheduler with other backend frameworks.

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.