dhtmlxScheduler mit ASP.NET Core

Diese Anleitung führt Sie durch den Prozess der Erstellung eines Schedulers mit ASP.NET Core auf der Serverseite.

Sie können auch Anleitungen für andere serverseitige Plattformen erkunden:

Die Datenbankinteraktion wird mit Entity Framework Core umgesetzt. Das Beispiel wurde mit Visual Studio 2022 entwickelt.

Der vollständige Quellcode ist auf GitHub verfügbar.

Schritt 1. Erstellen eines Projekts

Starten Sie Visual Studio 2022 und erstellen Sie ein neues Projekt, indem Sie "Create a new project" auswählen.

Scheduler App

Wählen Sie dann "ASP.NET Core Web App" und setzen Sie den Projektnamen auf SchedulerApp.

Scheduler App

Scheduler App

API template

An diesem Punkt ist das Projekt bereit und Sie können mit dem Hinzufügen von Markup und Skripten für den Scheduler fortfahren.

Schritt 2. Hinzufügen des Schedulers zur Seite

Navigieren Sie zu wwwroot und erstellen Sie eine neue Datei mit dem Namen index.html.

Explorer

Erstellen Sie in dieser Datei eine einfache Seite zur Anzeige des Schedulers.

Beachten Sie, dass in diesem Beispiel die Scheduler-Dateien von einem CDN geladen werden. Wenn Sie die Professional Edition besitzen, müssen Sie die Scheduler-Dateien manuell zu Ihrem Projekt hinzufügen.

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Getting started with dhtmlxScheduler</title>
    <meta charset="utf-8">
    <script src="https://cdn.dhtmlx.com/scheduler/edge/dhtmlxscheduler.js"></script>
    <link href="https://cdn.dhtmlx.com/scheduler/edge/dhtmlxscheduler.css" 
        rel="stylesheet" type="text/css" charset="utf-8">
    <style>
        html, body {
            margin: 0px;
            padding: 0px;
        }
    </style>
</head>
<body>
  <div id="scheduler_here" class="dhx_cal_container" style='width:100%; height:100vh;'>
        <div class="dhx_cal_navline">
            <div class="dhx_cal_prev_button">&nbsp;</div>
            <div class="dhx_cal_next_button">&nbsp;</div>
            <div class="dhx_cal_today_button"></div>
            <div class="dhx_cal_date"></div>
            <div class="dhx_cal_tab" name="day_tab"></div>
            <div class="dhx_cal_tab" name="week_tab"></div>
            <div class="dhx_cal_tab" name="month_tab"></div>
        </div>
        <div class="dhx_cal_header"></div>
        <div class="dhx_cal_data"></div>
  </div>
    <script>
        scheduler.init('scheduler_here', new Date(2019, 0, 20), "week");
    </script>
</body>
</html>

Öffnen Sie nun Program.cs und konfigurieren Sie die Anwendung so, dass sie die index.html-Seite bereitstellt, indem Sie statische Dateien aus dem wwwroot-Ordner aktivieren. Fügen Sie die Methode app.UseDefaultFiles() hinzu. Weitere Details finden Sie hier.

Program.cs

var builder = WebApplication.CreateBuilder(args);
 
// Add services to the container.
builder.Services.AddRazorPages();
 
var app = builder.Build();
 
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. 
    // You may want to change this for production scenarios, 
    // see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
 
app.UseHttpsRedirection();
 
app.UseDefaultFiles();  
app.UseStaticFiles();
 
app.UseRouting();
 
app.UseAuthorization();
 
app.MapRazorPages();
 
app.Run();

Die Middleware app.UseDefaultFiles() ermöglicht es der Anwendung, Standarddateien bereitzustellen, indem nach diesen Dateien im wwwroot-Ordner gesucht wird:

  • index.html
  • index.htm
  • default.html
  • default.htm

Sie können eine dieser Dateien verwenden, in diesem Tutorial wird "index.html" verwendet. Beachten Sie, dass UseDefaultFiles() nur die URL auf die Standarddatei umschreibt, daher ist UseStaticFiles() ebenfalls notwendig, um die Datei tatsächlich auszuliefern.

Nach diesen Änderungen zeigt die Anwendung beim Ausführen einen leeren Scheduler auf der Seite an.

Scheduler init

Die nächsten Schritte zeigen, wie Sie ein Backend-API erstellen und den Scheduler damit verbinden.

Schritt 3. Modelle und Datenbank erstellen

Beginnen Sie mit dem Datenmodell. Die Scheduler-Ereignisse benötigen eine Klassenrepräsentation. Da dhtmlxScheduler nicht standardisierte Eigenschaftsnamen im Vergleich zu typischen .NET-Konventionen verwendet, wird das Data Transfer Object (DTO)-Muster angewendet. Dies beinhaltet die Definition von:

  • Domänenmodellklassen für EF Core und die interne Nutzung der App
  • DTO-Klassen für die Kommunikation mit der Web-API.

Die Abbildung zwischen diesen Modellen wird ebenfalls implementiert.

Modelle

Fügen Sie dem Projekt einen neuen Ordner namens Models hinzu. Hier werden die Modellklassen und der EF-Kontext abgelegt.

Event-Modell

Erstellen Sie eine Klasse, die Kalendereinträge repräsentiert. Fügen Sie eine Datei namens SchedulerEvent.cs im Ordner Models hinzu.

Hier ein einfaches Beispiel für das Modell:

SchedulerApp/Models/SchedulerEvent.cs

namespace SchedulerApp.Models
{
    public class SchedulerEvent
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
    }
}

Beachten Sie, dass Scheduler-Ereignisse viele weitere Eigenschaften enthalten können, dieses Beispiel konzentriert sich jedoch auf das Wesentliche.

Datenbankkontext

Installieren Sie zunächst Entity Framework Core für ASP.NET Core. Dies kann über den NuGet-Paketmanager erfolgen:

Entity via Nuget

Oder durch Ausführen der folgenden Befehle in der Package Manager Console:

PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore
PM> Install-Package Microsoft.EntityFrameworkCore.Design

Entity Framework Core übernimmt die Datenkommunikation zwischen der App und der Datenbank.

Entity Context erstellen

Definieren Sie als Nächstes eine Datenbanksitzung und ermöglichen Sie das Laden und Speichern von Daten, indem Sie eine Context-Klasse erstellen:

  • Fügen Sie eine Datei mit dem Namen SchedulerContext.cs im Ordner Models hinzu
  • Definieren Sie den Datenbankkontext wie folgt:

SchedulerApp/Models/SchedulerContext.cs

using Microsoft.EntityFrameworkCore;
 
namespace SchedulerApp.Models
{
    public class SchedulerContext : DbContext
    {
        public SchedulerContext(DbContextOptions<SchedulerContext> options)
           : base(options)
        {
        }
        public DbSet<SchedulerEvent> Events { get; set; } = null!;
    }
}

Erste Datensätze zur Datenbank hinzufügen

Jetzt ist es Zeit, einige Anfangsdaten in die Datenbank einzufügen. Erstellen Sie einen Datenbank-Initialisierer, der die Datenbank mit Beispielereignissen füllt.

Fügen Sie im Ordner Models eine Klasse mit dem Namen SchedulerSeeder hinzu. Sie enthält eine Seed()-Methode, die Ereignisse in die Datenbank einfügt.

SchedulerApp/Models/SchedulerSeeder.cs

using System;
using System.Collections.Generic;
using System.Linq;
 
namespace SchedulerApp.Models
{
    public static class SchedulerSeeder
    {
        public static void Seed(SchedulerContext context)
        {
            if (context.Events.Any())
            {
                return;   // DB has been seeded
            }
 
            var events = new List<SchedulerEvent>()
            {
                new SchedulerEvent
                {
                    Name = "Event 1",
                    StartDate = new DateTime(2019, 1, 15, 2, 0, 0),
                    EndDate = new DateTime(2019, 1, 15, 4, 0, 0)
                },
                new SchedulerEvent()
                {
                    Name = "Event 2",
                    StartDate = new DateTime(2019, 1, 17, 3, 0, 0),
                    EndDate = new DateTime(2019, 1, 17, 6, 0, 0)
                },
                new SchedulerEvent()
                {
                    Name = "Multiday event",
                    StartDate = new DateTime(2019, 1, 15, 0, 0, 0),
                    EndDate = new DateTime(2019, 1, 20, 0, 0, 0)
                }
            };
 
            events.ForEach(s => context.Events.Add(s));
            context.SaveChanges();
        }
    }
}

Registrierung der Datenbank

Der nächste Schritt besteht darin, die Datenbank in Program.cs zu registrieren. Dafür wird zunächst eine Verbindungszeichenfolge benötigt. Diese Zeichenfolge wird in einer JSON-Datei innerhalb der Anwendungseinstellungen abgelegt. Falls das Projekt mit dem API-Template erstellt wurde, ist diese Datei bereits im Projektordner vorhanden. Wer das Empty template verwendet hat, muss die Datei noch anlegen.

Erstellen Sie die Datei appsettings.json (oder öffnen Sie sie, falls sie bereits existiert), und fügen Sie die Verbindungszeichenfolge für die Datenbank hinzu:

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection":"Server=(localdb)\\mssqllocaldb;
        Database=SchedulerDatabase;Trusted_Connection=True;"  }
}

Anschließend registrieren Sie den Datenbankkontext über Dependency Injection.

Fügen Sie diese Namespaces zu Program.cs hinzu:

Program.cs

using Microsoft.EntityFrameworkCore;
using SchedulerApp.Models;
using Microsoft.Extensions.Configuration;

Die Registrierung sieht dann wie folgt aus:

Program.cs

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<SchedulerContext>(
    options => options.UseSqlServer(connectionString));

Um die Controller zu aktivieren, rufen Sie die Methode services.AddControllers() auf.

Program.cs

builder.Services.AddControllers();

Hier ist der vollständige Inhalt von Program.cs:

Program.cs

using Microsoft.EntityFrameworkCore;
using SchedulerApp.Models;
using Microsoft.Extensions.Configuration;
 
var builder = WebApplication.CreateBuilder(args);
 
// Add services to the container.
builder.Services.AddRazorPages();
 
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<SchedulerContext>(
    options => options.UseSqlServer(connectionString));
 
builder.Services.AddControllers();
 
var app = builder.Build();
 
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. 
    // You may want to change this for production scenarios, 
    // see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
 
app.UseHttpsRedirection();
 
app.UseDefaultFiles();
 
app.UseStaticFiles();
 
app.UseRouting();
 
app.UseAuthorization();
 
app.MapRazorPages();
 
app.MapControllers();
 
app.Run();

Abschließend muss die Datenbank beim Starten der App initialisiert und mit Daten befüllt werden. Normalerweise werden dafür Migrationen verwendet, in diesem Beispiel wird jedoch zur Vereinfachung darauf verzichtet.

Erstellen Sie zunächst eine Klasse zur Initialisierung. Fügen Sie die Datei SchedulerInitializerExtension.cs im Ordner Models hinzu:

Models/SchedulerInitializerExtension.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
 
namespace SchedulerApp.Models
{
 public static class SchedulerInitializerExtension
   {
    public static IHost InitializeDatabase(this IHost webHost)
     {
      var serviceScopeFactory =
      (IServiceScopeFactory?)webHost.Services.GetService(typeof(IServiceScopeFactory));
 
        using (var scope = serviceScopeFactory!.CreateScope())
        {
           var services = scope.ServiceProvider;
           var dbContext = services.GetRequiredService<SchedulerContext>();
           dbContext.Database.EnsureDeleted();
           dbContext.Database.EnsureCreated();
           SchedulerSeeder.Seed(dbContext);
        }
 
        return webHost;
     }
   }
}

Anschließend rufen Sie InitializeDatabase() auf:

Program.cs

app.InitializeDatabase();

Damit ist dieser Teil abgeschlossen; als Nächstes folgt der Scheduler.

Definieren von DTOs und Mapping

Nun ist es an der Zeit, die DTO-Klassen für die Web API zu definieren. Beginnen Sie mit der DTO-Klasse für SchedulerEvent. Erstellen Sie im Ordner Models eine Datei und definieren Sie die Klasse WebAPIEvent.cs:

Models/WebApiEvent.cs

using System;
 
namespace SchedulerApp.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; }
 
        public static explicit operator WebAPIEvent(SchedulerEvent ev)
        {
            return new WebAPIEvent
            {
                id = ev.Id,
                text = ev.Name,
                start_date = ev.StartDate.ToString("yyyy-MM-dd HH:mm"),
                end_date = ev.EndDate.ToString("yyyy-MM-dd HH:mm")
            };
        }
 
        public static explicit operator SchedulerEvent(WebAPIEvent ev)
        {
            return new SchedulerEvent
            {
                Id = ev.id,
                Name = ev.text,
                StartDate = ev.start_date != null ? DateTime.Parse(ev.start_date,
                  System.Globalization.CultureInfo.InvariantCulture) : new DateTime(),
                EndDate = ev.end_date != null ? DateTime.Parse(ev.end_date,
                  System.Globalization.CultureInfo.InvariantCulture) : new DateTime()
            };
        }
    }
}

Nach Abschluss dieses Schrittes sollte die Ordnerstruktur wie folgt aussehen:

Models

An dieser Stelle kann die App gestartet werden, um zu überprüfen, ob alles korrekt eingerichtet ist. Wenn keine Laufzeitfehler auftreten, war die Einrichtung erfolgreich.

Schritt 4. Implementierung der Web API

Als Nächstes folgt die Implementierung der REST-API.

Hinzufügen eines API-Controllers

Erstellen Sie einen Ordner Controllers und fügen Sie einen leeren API-Controller für Events hinzu:

Controllers/EventsController.cs

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using SchedulerApp.Models;
 
namespace SchedulerApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EventsController : ControllerBase
    {
        private readonly SchedulerContext _context;
        public EventsController(SchedulerContext context)
        {
            _context = context;
        }
 
        // GET api/events
        [HttpGet]
        public IEnumerable<WebAPIEvent> Get()
        {
            return _context.Events
                .ToList()
                .Select(e => (WebAPIEvent)e);
        }
 
        // GET api/events/5
        [HttpGet("{id}")]
        public SchedulerEvent? Get(int id)
        {
            return _context
                .Events
                .Find(id);
        }
 
        // POST api/events
        [HttpPost]
        public ObjectResult Post([FromForm] WebAPIEvent apiEvent)
        {
            var newEvent = (SchedulerEvent)apiEvent;
 
            _context.Events.Add(newEvent);
            _context.SaveChanges();
 
            return Ok(new
            {
                tid = newEvent.Id,
                action = "inserted"
            });
        }
 
        // PUT api/events/5
        [HttpPut("{id}")]
        public ObjectResult? Put(int id, [FromForm] WebAPIEvent apiEvent)
        {
            var updatedEvent = (SchedulerEvent)apiEvent;
            var dbEvent = _context.Events.Find(id);
            if (dbEvent == null)
            {
                return null;
            }
            dbEvent.Name = updatedEvent.Name;
            dbEvent.StartDate = updatedEvent.StartDate;
            dbEvent.EndDate = updatedEvent.EndDate;
            _context.SaveChanges();
 
            return Ok(new
            {
                action = "updated"
            });
        }
 
        // DELETE api/events/5
        [HttpDelete("{id}")]
        public ObjectResult DeleteEvent(int id)
        {
            var e = _context.Events.Find(id);
            if (e != null)
            {
            _context.Events.Remove(e);
            _context.SaveChanges();
            }
 
            return Ok(new
            {
                action = "deleted"
            });
        }
 
    }
}

Konfiguration des Clients

Mit der fertigen Web API können Sie nun zur HTML-Seite zurückkehren und den Scheduler für die Zusammenarbeit mit der API konfigurieren:

wwwroot/index.html

scheduler.config.date_format = "%Y-%m-%d %H:%i";
scheduler.init("scheduler_here", new Date(2019, 0, 20), "week");
 
// load data from backend
scheduler.load("/api/events", "json");
// connect backend to scheduler
var dp = scheduler.createDataProcessor("/api/events");
dp.init(scheduler);
// set data exchange mode
dp.setTransactionMode("REST");

Jetzt ist alles bereit. Beim Ausführen der Anwendung wird ein voll funktionsfähiger Scheduler angezeigt.

Scheduler CRUD

Dynamisches Laden

Derzeit ruft der Scheduler bei jedem Aufruf der GET-Aktion die gesamte Events-Tabelle ab. Das ist anfangs in Ordnung, aber mit zunehmender Nutzung und wachsender Datenmenge steigt auch das übertragene Datenvolumen deutlich an. Mit dynamischem Laden kann der Scheduler nur den jeweils benötigten Bereich von Events abrufen.

Auf der Clientseite wird dies mit der Methode scheduler.setLoadMode aktiviert:

wwwroot/index.html

scheduler.setLoadMode("day");
// load data from backend
scheduler.load("/api/events", "json");

Danach fügt der Scheduler den GET-Anfragen die Parameter from und to hinzu, sodass das Backend nur Events im angegebenen Zeitraum zurückgeben kann.

Das Backend muss diese Parameter lediglich im GET-Handler akzeptieren und die Events entsprechend filtern:

Controllers/EventsController.cs

// GET api/events
[HttpGet]
public IEnumerable<WebAPIEvent> Get([FromQuery] DateTime from, [FromQuery] DateTime to)
{
    return _context.Events
        .Where(e => e.StartDate < to && e.EndDate >= from)
        .ToList()
        .Select(e => (WebAPIEvent)e);
}

Wiederkehrende Events

Um wiederkehrende Events (wie z. B. „Event täglich wiederholen“) zu unterstützen, muss die entsprechende Erweiterung auf der Scheduler-Seite aktiviert werden:

scheduler.plugins({
    recurring: true
});

Aktualisierung des Modells

Das Modell muss aktualisiert werden, um Wiederholungsinformationen korrekt zu verarbeiten:

Models/SchedulerEvent.cs

using System;
 
namespace SchedulerApp.Models
{
    public class SchedulerEvent
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
 
        public int EventPID { get; set; }         public string? RecType { get; set; }         public long EventLength { get; set; }  
    }
}

Ebenso sollte das Data Transfer Object wie folgt aktualisiert werden:

Models/WebAPIEvent.cs

using System;
using System.Text.Encodings.Web;
 
namespace SchedulerApp.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; }
 
        public int? event_pid { get; set; }         public string? rec_type { get; set; }         public long? event_length { get; set; }  
        public static explicit operator WebAPIEvent(SchedulerEvent ev)
        {
            return new WebAPIEvent
            {
                id = ev.Id,
                text = HtmlEncoder.Default.Encode(ev.Name != null ? ev.Name : ""), 
                start_date = ev.StartDate.ToString("yyyy-MM-dd HH:mm"),
                end_date = ev.EndDate.ToString("yyyy-MM-dd HH:mm"),
                event_pid  = ev.EventPID,
                rec_type = ev.RecType,
                event_length = ev.EventLength
            };
        }
 
        public static explicit operator SchedulerEvent(WebAPIEvent ev)
        {
            return new SchedulerEvent
            {
                Id = ev.id,
                Name = ev.text,
                StartDate = ev.start_date != null ? DateTime.Parse(ev.start_date,
                  System.Globalization.CultureInfo.InvariantCulture) : new DateTime(),
                EndDate = ev.end_date != null ? DateTime.Parse(ev.end_date,
                  System.Globalization.CultureInfo.InvariantCulture) : new DateTime(),
    ///
                EventPID = ev.event_pid != null ? ev.event_pid.Value : 0, 
                EventLength = ev.event_length != null ? ev.event_length.Value : 0,
                RecType = ev.rec_type
            };
        }
    }
}

Aktualisierung des API-Controllers

Die Aktionen PUT, POST und DELETE müssen angepasst werden, damit sie die speziellen Fälle im Zusammenhang mit wiederkehrenden Ereignissen korrekt behandeln, wie im Abschnitt handling recurring events beschrieben.

Beginnen wir mit der POST-Aktion. Sie behandelt einen Sonderfall, bei dem das Löschen eines einzelnen Vorkommens aus einer wiederkehrenden Serie das Hinzufügen eines neuen Datenbankeintrags erfordert. In diesem Fall ruft der Client die insert-Aktion auf:

Controllers/EventsController.cs

// POST api/events
[HttpPost]
public ObjectResult Post([FromForm] WebAPIEvent apiEvent)
{
   var newEvent = (SchedulerEvent)apiEvent;
 
   _context.Events.Add(newEvent);
   _context.SaveChanges();
 
  // delete a single occurrence from a recurring series
  var resultAction = "inserted";   if(newEvent.RecType == "none")   {
     resultAction = "deleted";   }
 
  return Ok(new
  {
     tid = newEvent.Id,
     action = resultAction   });
}

Für die PUT-Aktion ist es wichtig, alle Modelleigenschaften zu aktualisieren. Zusätzlich müssen beim Ändern einer wiederkehrenden Serie alle modifizierten Vorkommen dieser Serie gelöscht werden:

Controllers/EventsController.cs

// PUT api/events/5
[HttpPut("{id}")]
public ObjectResult? Put(int id, [FromForm] WebAPIEvent apiEvent)
{
    var updatedEvent = (SchedulerEvent)apiEvent;
    var dbEvent = _context.Events.Find(id);
    if (dbEvent == null)
    {
        return null;
    }
    dbEvent.Name = updatedEvent.Name;
    dbEvent.StartDate = updatedEvent.StartDate;
    dbEvent.EndDate = updatedEvent.EndDate;
    dbEvent.EventPID = updatedEvent.EventPID;
    dbEvent.RecType = updatedEvent.RecType;
    dbEvent.EventLength = updatedEvent.EventLength;
 
    if (!string.IsNullOrEmpty(updatedEvent.RecType) && updatedEvent.RecType != "none")
    {
     //all modified occurrences must be deleted when we update a recurring series
     //https://docs.dhtmlx.com/scheduler/server_integration.html#recurringevents
 
        _context.Events.RemoveRange(
            _context.Events.Where(e => e.EventPID == id)
        );
    }
 
    _context.SaveChanges();
 
    return Ok(new
    {
        action = "updated"
    });
}

Abschließend muss die DELETE-Aktion zwei Szenarien behandeln:

  • Wenn das zu löschende Ereignis einen nicht-leeren event_pid-Wert hat, bedeutet dies, dass eine modifizierte Instanz einer wiederkehrenden Serie gelöscht wird. Statt den Eintrag zu entfernen, sollte er mit rec_type='none' aktualisiert werden, sodass der Scheduler dieses Vorkommen überspringt.
  • Beim Löschen einer ganzen wiederkehrenden Serie sollten alle modifizierten Instanzen dieser Serie ebenfalls entfernt werden.

Controllers/EventsController.cs

[HttpDelete("{id}")]
public ObjectResult DeleteEvent(int id)
{
  var e = _context.Events.Find(id);
  if (e != null)
  {
   //some logic specific to recurring events support
   //https://docs.dhtmlx.com/scheduler/server_integration.html#recurringevents
 
     if (e.EventPID != default(int))
     {
        // deleting a modified occurrence from a recurring series
        // If an event with the event_pid value was deleted, it should be updated 
        // with rec_type==none instead of deleting.
 
        e.RecType = "none";
     }
     else
     {
      //if a recurring series deleted, delete all modified occurrences of the series
      if (!string.IsNullOrEmpty(e.RecType) && e.RecType != "none")
       {
    //all modified occurrences must be deleted when we update recurring series
    //https://docs.dhtmlx.com/scheduler/server_integration.html#recurringevents
         _context.Events.RemoveRange(
            _context.Events.Where(ev => ev.EventPID == id)
        );
       }
 
        _context.Events.Remove(e);
     }
 
     _context.SaveChanges();
  }
 
  return Ok(new
  {
     action = "deleted"
  });
}

Parsen von Wiederholungsserien

Ein wiederkehrendes Ereignis wird in der Datenbank als einzelner Eintrag gespeichert, den der Scheduler auf der Client-Seite in einzelne Vorkommen aufteilt. Um die Daten der einzelnen Ereignisse serverseitig abzurufen, steht eine Hilfsbibliothek zum Parsen von Wiederholungsereignissen in dhtmlxScheduler mit ASP.NET Core zur Verfügung.

Diese Bibliothek ist auf GitHub verfügbar.

Fehlerbehandlung

Um Fehler effektiv zu behandeln, sollte eine Middleware-Klasse erstellt werden. Diese Middleware fängt Laufzeitausnahmen ab und formatiert die Antworten entsprechend. Nach dem Erstellen der Middleware muss sie in die Request-Pipeline der App eingefügt werden. Die Schritte sind wie folgt:

1. Erstellen Sie eine Middleware-Klasse anhand einer Vorlage im Projektordner.

Create middleware class

Es ist notwendig, das JSON-Framework für ASP.NET Core sowie die HTTP-Abstraktionen für die Verarbeitung von HTTP-Anfragen und -Antworten zu installieren. Dies kann über den NuGet-Paketmanager erfolgen:

Nuget PM

Oder durch Ausführen der folgenden Befehle in der Paket-Manager-Konsole:

PM> Install-Package Microsoft.AspNetCore.Http.Abstractions
PM> Install-Package Microsoft.Newtonsoft.Json

2. Suchen Sie die Invoke-Methode in der Middleware-Klasse und beachten Sie den Aufruf von _next. Da einige Handler Ausnahmen auslösen könnten, umschließen Sie den _next-Aufruf mit einem try-catch-Block, um Fehler abzufangen und mit einem dedizierten Handler zu verarbeiten.

SchedulerErrorMiddleware.cs

public async Task Invoke(HttpContext httpContext)
{
  try
    {
       await _next(httpContext);
    }catch (Exception ex)
    {
        await HandleExceptionAsync(httpContext, ex);
    }
}
 
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
   var result = JsonConvert.SerializeObject(new{
       action = "error"
   });
   context.Response.ContentType = "application/json";
   context.Response.StatusCode = StatusCodes.Status500InternalServerError;
   return context.Response.WriteAsync(result);
}

Fügen Sie folgenden Namespace zu SchedulerErrorMiddleware.cs hinzu:

SchedulerErrorMiddleware.cs

using Newtonsoft.Json;

3. Nachdem die Middleware bereit ist, öffnen Sie Program.cs und registrieren Sie sie, indem Sie diesen Namespace hinzufügen:

Program.cs

using SchedulerApp;

Rufen Sie anschließend app.UseSchedulerErrorMiddleware() auf, um sie in die Pipeline einzubinden:

Program.cs

app.UseSchedulerErrorMiddleware();

Anwendungssicherheit

Der Scheduler selbst bietet keinen eingebauten Schutz gegen gängige Sicherheitsrisiken wie SQL-Injection, XSS oder CSRF-Angriffe. Die Sicherheit der Anwendung liegt in der Verantwortung der Entwickler, die das Backend implementieren. Weitere Informationen finden Sie im dedicated article.

XSS-Schutz

Eine einfache Möglichkeit, sich vor XSS zu schützen, besteht darin, Text-Eigenschaften vor der Übertragung an den Client zu kodieren. Im folgenden Beispiel wird der integrierte HtmlEncoder verwendet, um HTML-Zeichen im Ereignistext zu maskieren. Diese Methode hält die Datenbankdaten unverändert und liefert dem Client sichere Inhalte:

Models/WebAPIEvent.cs

using System.Text.Encodings.Web;
 
public static explicit operator WebAPIEvent(SchedulerEvent ev)
{
  return new WebAPIEvent
    {
      id = ev.Id,
      text = HtmlEncoder.Default.Encode(ev.Name != null ? ev.Name : ""), 
      start_date = ev.StartDate.ToString("yyyy-MM-dd HH:mm"),
      end_date = ev.EndDate.ToString("yyyy-MM-dd HH:mm")
    };
}

Alternativ kann eine spezialisierte Bibliothek wie HtmlAgilityPack verwendet werden, um HTML-Tags beim Speichern oder Laden der Daten vollständig zu entfernen.

Fehlerbehebung

Wenn alle Schritte zur Integration des Schedulers mit ASP.NET Core durchgeführt wurden, aber die Ereignisse nicht auf der Seite angezeigt werden, lesen Sie den Artikel Fehlerbehebung bei Backend-Integrationsproblemen. Dieser bietet Hilfestellung zur Diagnose und Lösung häufiger Probleme.

Wie geht es weiter?

An diesem Punkt sollte der Scheduler vollständig funktionsfähig sein. Der vollständige Quellcode steht auf GitHub zum Klonen oder Herunterladen für Ihre Projekte bereit.

Weitere Ressourcen sind Anleitungen zu den vielen Funktionen des Schedulers und Tutorials zur Integration des Schedulers mit anderen Backend-Frameworks.

Nach oben