dhtmlxGantt mit ASP.NET Core

Hier ist eine Anleitung, die Ihnen hilft, ein Gantt-Diagramm mit ASP.NET Core auf der Serverseite einzurichten.

Wenn Sie daran interessiert sind, andere serverseitige Technologien zu erkunden, sehen Sie sich diese Tutorials an:

Für die Datenbankkommunikation wird das Entity Framework Core verwendet, und die Anwendung wird mit Visual Studio 2022 erstellt.

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

Schritt 1. Einrichten des Projekts

Starten Sie Visual Studio 2022 und erstellen Sie ein neues Projekt. Wählen Sie Create a new project.

dhtmlxGantt mit ASP.NET Core ein Projekt erstellen

Wählen Sie dann "ASP.NET Core Web App" und benennen Sie das Projekt DHX.Gantt.

dhtmlxGantt mit ASP.NET Core ein Projekt erstellen

dhtmlxGantt mit ASP.NET Core ein Projekt konfigurieren

dhtmlxGantt mit ASP.NET Core ein Projekt konfigurieren

Jetzt ist das Projekt bereit, und Sie können mit dem Hinzufügen des Markups und Skripts für das Gantt-Diagramm fortfahren.

Schritt 2. Hinzufügen von Gantt-Markup und JavaScript

Navigieren Sie zu wwwroot und erstellen Sie eine neue Datei namens index.html.

dhtmlxGantt mit ASP.NET Core 2 ein Projekt erstellen

dhtmlxGantt mit ASP.NET Core 2 ein Projekt erstellen

Richten Sie in dieser Datei eine einfache Seite für das Gantt-Diagramm ein.

Hier werden die Gantt-Dateien vom CDN geladen. Wenn Sie die professionelle Version der Komponente verwenden, müssen Sie die Dateien manuell hinzufügen.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <link href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css"
          rel="stylesheet" type="text/css" />
    <script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
    <script>
        document.addEventListener("DOMContentLoaded", function(event) {
            // Setzen Sie das Datumsformat
            gantt.config.date_format = "%Y-%m-%d %H:%i";
            // Initialisieren Sie das Gantt-Diagramm
            gantt.init("gantt_here");
 
            // Daten laden
            gantt.load("/api/data");
 
            // Datenprozessor einrichten
            var dp = new gantt.dataProcessor("/api/");
            dp.init(gantt);
            dp.setTransactionMode("REST");
        });
    </script>
</head>
<body>
    <div id="gantt_here" style="width: 100%; height: 100vh;"></div>
</body>
</html>

Wenn die Seite geladen wird, wird das Gantt-Diagramm initialisiert und das Laden der Daten ausgelöst. Ein dataProcessor wird ebenfalls konfiguriert, um Änderungen am Diagramm im Backend zu speichern. Die Einrichtung des Backends erfolgt später im Prozess.

Als nächstes aktualisieren Sie Program.cs, um die index.html-Seite zu verwenden. Dies beinhaltet die Konfiguration der App, um statische Dateien aus dem wwwroot-Ordner bereitzustellen. Um dies zu tun, fügen Sie die Methode app.UseDefaultFiles() hinzu. Weitere Details finden Sie hier.

Program.cs

var builder = WebApplication.CreateBuilder(args);
 
// Dienste zum Container hinzufügen.
builder.Services.AddRazorPages();
 
var app = builder.Build();
 
// Konfigurieren Sie die HTTP-Anforderungspipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
 
app.UseHttpsRedirection();
 
app.UseDefaultFiles();  
app.UseStaticFiles();
 
app.UseRouting();
 
app.UseAuthorization();
 
app.MapRazorPages();
 
app.Run();

Die Methode app.UseDefaultFiles() ermöglicht das Bereitstellen von Standarddateien wie:

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

In diesem Tutorial wird "index.html" verwendet. Beachten Sie, dass UseDefaultFiles() nur URLs umschreibt – es dient nicht tatsächlich der Bereitstellung der Dateien. Um Dateien bereitzustellen, benötigen Sie auch UseStaticFiles().

An diesem Punkt sollte das Ausführen der Anwendung ein leeres Gantt-Diagramm anzeigen. Das "Invalid data"-Label in der oberen rechten Ecke erscheint, weil gantt.load() aufgerufen wird, aber es gibt noch kein Backend, das Daten bereitstellt. Sobald das Backend implementiert ist, zeigt das Diagramm Aufgaben und Verknüpfungen.

dhtmlxGantt mit ASP.NET Core 2 Gantt hinzufügen

Mit der grundlegenden Einrichtung abgeschlossen, können Sie nun mit der Implementierung des Backends fortfahren. Beginnen Sie mit dem Erstellen von Modellklassen und gehen Sie dann zum WebAPI-Controller über.

Schritt 3. Erstellen von Modellen und Datenbank

Das Gantt-Datenmodell umfasst Aufgaben und Verknüpfungen. dhtmlxGantt verwendet nicht standardmäßige Eigenschaftsnamen im Vergleich zu .NET-Konventionen. Manchmal werden zusätzliche Eigenschaften für die Client-Seite oder Backend-Logik verwendet, aber diese müssen nicht in der Datenbank gespeichert werden.

Um dies zu handhaben, wird das Data Transfer Object (DTO) Muster angewendet. Es werden zwei Arten von Modellen erstellt:

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

Die Zuordnung zwischen diesen Modellen wird ebenfalls implementiert.

Modelle

Erstellen Sie einen Models-Ordner in Ihrem Projektverzeichnis, um die Modellklassen und den EF-Kontext zu speichern.

Aufgabenmodell

Beginnen Sie mit dem Erstellen einer Klasse für Aufgaben. Fügen Sie eine Datei namens Task.cs im Models-Ordner hinzu.

Hier ist die Struktur des Aufgabenmodells:

DHX.Gantt/Models/Task.cs

namespace DHX.Gantt.Models
{
    public class Task
    {
        public int Id { get; set; }
        public string? Text { get; set; }
        public DateTime StartDate { get; set; }
        public int Duration { get; set; }
        public decimal Progress { get; set; }
        public int? ParentId { get; set; }
        public string? Type { get; set; }
    }
}

Die vollständige Liste der Aufgabeneigenschaften ist zur Referenz verfügbar.

Verknüpfungsmodell

Erstellen Sie als nächstes eine Klasse für Verknüpfungen, indem Sie eine Datei namens Link.cs im Models-Ordner hinzufügen.

Hier ist die Struktur des Verknüpfungsmodells:

DHX.Gantt/Models/Link.cs

namespace DHX.Gantt.Models
{
    public class Link
    {
        public int Id { get; set; }
        public string? Type { get; set; }
        public int SourceTaskId { get; set; }
        public int TargetTaskId { get; set; }
    }
}

Mit den Modellen bereit, ist der nächste Schritt, die Datenbankverbindung einzurichten.

Konfigurieren der Datenbankverbindung

Führen Sie die folgenden Schritte aus, um die Datenbankverbindung zu konfigurieren:

Installieren Sie Entity Framework Core

Das Entity Framework Core wird die Datenbankkommunikation der App verwalten. Um es zu installieren:

  • Finden Sie im Projektbaum Abhängigkeiten unter DHTMLX.Gantt
  • Klicken Sie mit der rechten Maustaste und wählen Sie Manage NuGet Packages
  • Installieren Sie im Browse-Tab Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore und Microsoft.EntityFrameworkCore.Design

dhtmlxGantt mit ASP.NET Core EF Core Installation

Oder verwenden Sie die Befehlszeile des Paketmanagers:

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

Erstellen Sie den Entitätskontext

Um die Interaktion mit der Datenbank zu ermöglichen, definieren Sie einen Kontext:

  • Fügen Sie eine Datei namens GanttContext.cs im Models-Ordner hinzu
  • Definieren Sie die GanttContext-Klasse in der Datei

DHX.Gantt/Models/GanttContext.cs

using Microsoft.EntityFrameworkCore;
 
namespace DHX.Gantt.Models
{
    public class GanttContext : DbContext
    {
        public GanttContext(DbContextOptions<GanttContext> options)
           : base(options)
        {
        }
        public DbSet<Task> Tasks { get; set; } = null;
        public DbSet<Link> Links { get; set; } = null;
 
    }
}

Hinzufügen von Anfangsdatensätzen zur Datenbank

Um mit dem Hinzufügen von Datensätzen zur Datenbank zu beginnen, müssen Sie einen Datenbank-Initializer erstellen, um sie mit Aufgaben zu füllen. Erstellen Sie im Models-Ordner eine Klasse namens GanttSeeder. Diese Klasse enthält eine Seed()-Methode, um Aufgaben und Verknüpfungen zur Datenbank hinzuzufügen.

DHX.Gantt/Models/GanttSeeder.cs

using Microsoft.EntityFrameworkCore;
 
namespace DHX.Gantt.Models
{
    public static class GanttSeeder
    {
        public static void Seed(GanttContext context)
        {
            if (context.Tasks.Any())
            {
                return;   // Die Datenbank ist bereits gefüllt
            }
 
            using (var transaction = context.Database.BeginTransaction())
            {
               List<Task> tasks = new List<Task>()
               {
                  new Task()
                    {
                       Id = 1,
                       Text = "Projekt #2",
                       StartDate = DateTime.Today.AddDays(-3),
                       Duration = 18,
                       Progress = 0.4m,
                       ParentId = null
                    },
                    new Task()
                    {
                       Id = 2,
                       Text = "Aufgabe #1",
                       StartDate = DateTime.Today.AddDays(-2),
                       Duration = 8,
                       Progress = 0.6m,
                       ParentId = 1
                    },
                    new Task()
                    {
                       Id = 3,
                       Text = "Aufgabe #2",
                       StartDate = DateTime.Today.AddDays(-1),
                       Duration = 8,
                       Progress = 0.6m,
                       ParentId = 1
                    }
               };
 
               tasks.ForEach(s => context.Tasks.Add(s));
               context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Tasks ON;");
               context.SaveChanges();
 
               context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Tasks OFF;");
               List<Link> links = new List<Link>()
               {
                   new Link() {Id = 1, SourceTaskId = 1, TargetTaskId = 2, Type = "1"},
                   new Link() {Id = 2, SourceTaskId = 2, TargetTaskId = 3, Type = "0"}
               };
 
               links.ForEach(s => context.Links.Add(s));
               context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Links ON;");
               context.SaveChanges();
               context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Links OFF;");
               transaction.Commit();
            }
        }
    }
}

Einrichten der Datenbank

Bevor Sie die Datenbank in Program.cs registrieren, ist ein Verbindungsstring erforderlich. Dieser wird in einer JSON-Datei innerhalb der Anwendungseinstellungen gespeichert. Öffnen oder erstellen Sie die appsettings.json-Datei und fügen Sie den Verbindungsstring ein:

appsettings.json

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

Der Datenbankkontext wird mit Dependency Injection registriert.

Fügen Sie diese Namensräume zu Program.cs hinzu:

Program.cs

using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;

Die Registrierung sieht folgendermaßen aus:

Program.cs

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

Um Controller zu aktivieren, fügen Sie die services.AddControllers()-Methode hinzu:

Program.cs

builder.Services.AddControllers();

Und um Controller-Routen zu registrieren, verwenden Sie app.MapControllers():

Program.cs

app.MapControllers();


Hier ist die vollständige Program.cs-Datei:

Program.cs

using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;
 
var builder = WebApplication.CreateBuilder(args);
 
// Dienste zum Container hinzufügen.
builder.Services.AddRazorPages();
 
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
    options => options.UseSqlServer(connectionString));
 
builder.Services.AddControllers();
 
var app = builder.Build();
 
// Konfigurieren Sie die HTTP-Anforderungspipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // Der Standard-HSTS-Wert beträgt 30 Tage.
    // Sie können dies für Produktionsszenarien ändern, 
    // siehe https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
 
app.UseHttpsRedirection();
 
app.UseDefaultFiles();
 
app.UseStaticFiles();
 
app.UseRouting();
 
app.UseAuthorization();
 
app.MapRazorPages();
 
app.MapControllers();
 
app.Run();

Initialisieren und Füllen der Datenbank

Um die Datenbank beim Start der App einzurichten und zu füllen, erstellen Sie eine Klasse zur Initialisierung. Erstellen Sie im Models-Ordner eine Datei namens GanttInitializerExtension.cs:

Models/GanttInitializerExtension.cs

namespace DHX.Gantt.Models
{
  public static class GanttInitializerExtension
  {
    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<GanttContext>();
          dbContext.Database.EnsureDeleted();
          dbContext.Database.EnsureCreated();
          GanttSeeder.Seed(dbContext);
       }
 
       return webHost;
     }
   }
}

Rufen Sie nun InitializeDatabase() auf:

Program.cs

app.InitializeDatabase();

In diesem Beispiel werden Migrationen der Einfachheit halber übersprungen. Stattdessen werden die Methoden EnsureCreated und seed verwendet.

Mit dieser Einrichtung können Sie mit Gantt fortfahren.

Definieren von DTOs und Zuordnung

Als nächstes definieren Sie DTO-Klassen für die Web-API. Beginnen Sie mit der DTO-Klasse für Aufgaben. Erstellen Sie im Models-Ordner eine Datei namens WebApiTask.cs:

Models/WebApiTask.cs

namespace DHX.Gantt.Models
{
    public class WebApiTask
    {
        public int id { get; set; }
        public string? text { get; set; }
        public string? start_date { get; set; }
        public int duration { get; set; }
        public decimal progress { get; set; }
        public int? parent { get; set; }
        public string? type { get; set; }
        public bool open
        {
            get { return true; }
            set { }
        }
 
        public static explicit operator WebApiTask(Task task)
        {
            return new WebApiTask
            {
                id = task.Id,
                text = task.Text,
                start_date = task.StartDate.ToString("yyyy-MM-dd HH:mm"),
                duration = task.Duration,
                parent = task.ParentId,
                type = task.Type,
                progress = task.Progress
            };
        }
 
        public static explicit operator Task(WebApiTask task)
        {
            return new Task
            {
                Id = task.id,
                Text = task.text,
                StartDate = task.start_date != null ? DateTime.Parse(task.start_date,
                  System.Globalization.CultureInfo.InvariantCulture) : new DateTime(),
                Duration = task.duration,
                ParentId = task.parent,
                Type = task.type,
                Progress = task.progress
            };
        }
    }
}

Erstellen Sie nun die DTO-Klasse für Verknüpfungen in einer Datei namens WebApiLink.cs, ebenfalls im Models-Ordner:

Models/WebApiLink.cs

namespace DHX.Gantt.Models
{
    public class WebApiLink
    {
        public int id { get; set; }
        public string? type { get; set; }
        public int source { get; set; }
        public int target { get; set; }
 
        public static explicit operator WebApiLink(Link link)
        {
            return new WebApiLink
            {
                id = link.Id,
                type = link.Type,
                source = link.SourceTaskId,
                target = link.TargetTaskId
            };
        }
 
        public static explicit operator Link(WebApiLink link)
        {
            return new Link
            {
                Id = link.id,
                Type = link.type,
                SourceTaskId = link.source,
                TargetTaskId = link.target
            };
        }
    }
}

Nach Abschluss dieses Schritts sollte Ihre Ordnerstruktur wie folgt aussehen:

Gantt ASP.NET Core 2 Alle Modelle

Sie können nun die App ausführen, um sicherzustellen, dass alles korrekt eingerichtet ist. Wenn keine Laufzeitfehler auftreten, sind Sie bereit.

Schritt 4. Implementierung der Web-API

Nun ist es an der Zeit, die REST-API zu implementieren.

Hinzufügen von Controllern

Erstellen Sie einen Controllers-Ordner und fügen Sie drei leere API-Controller hinzu: einen für Aufgaben, einen weiteren für Verknüpfungen und einen dritten für den gesamten Datensatz:

Gantt ASP.NET Core 2 Controller hinzufügen

Aufgaben-Controller

Ein Controller zur Verwaltung von Aufgaben ist erforderlich. Er wird grundlegende CRUD-Operationen für Gantt-Aufgaben abwickeln.

So funktioniert es:

  • Bei GET-Anfragen werden Aufgaben aus der Datenbank abgerufen und als Datenübertragungsobjekte zurückgegeben.
  • Bei PUT/POST-Anfragen werden Aufgaben vom Client als WebAPITask-Objekte gesendet, was das von dhtmlxGantt verwendete Format ist. Diese müssen in das Task-Klassenformat konvertiert werden, das von EntityFramework verwendet wird. Einmal konvertiert, können die Änderungen im DatabaseContext gespeichert werden.

Controllers/TaskController.cs

using Microsoft.AspNetCore.Mvc;
using DHX.Gantt.Models;
 
namespace DHX.Gantt.Controllers
{
    [Produces("application/json")]
    [Route("api/task")]
    public class TaskController : Controller
    {
        private readonly GanttContext _context;
        public TaskController(GanttContext context)
        {
            _context = context;
        }
 
        // GET api/task
        [HttpGet]
        public IEnumerable<WebApiTask> Get()
        {
            return _context.Tasks
                .ToList()
                .Select(t => (WebApiTask)t);
        }
 
        // GET api/task/5
        [HttpGet("{id}")]
        public Models.Task? Get(int id)
        {
            return _context
                .Tasks
                .Find(id);
        }
 
        // POST api/task
        [HttpPost]
        public ObjectResult Post(WebApiTask apiTask)
        {
            var newTask = (Models.Task)apiTask;
 
            _context.Tasks.Add(newTask);
            _context.SaveChanges();
 
            return Ok(new
            {
                tid = newTask.Id,
                action = "inserted"
            });
        }
 
        // PUT api/task/5
        [HttpPut("{id}")]
        public ObjectResult? Put(int id, WebApiTask apiTask)
        {
            var updatedTask = (Models.Task)apiTask;
            var dbTask = _context.Tasks.Find(id);
            if (dbTask == null)
            {
                return null;
            }
            dbTask.Text = updatedTask.Text;
            dbTask.StartDate = updatedTask.StartDate;
            dbTask.Duration = updatedTask.Duration;
            dbTask.ParentId = updatedTask.ParentId;
            dbTask.Progress = updatedTask.Progress;
            dbTask.Type = updatedTask.Type;
 
            _context.SaveChanges();
 
            return Ok(new
            {
                action = "updated"
            });
        }
 
        // DELETE api/task/5
        [HttpDelete("{id}")]
        public ObjectResult DeleteTask(int id)
        {
            var task = _context.Tasks.Find(id);
            if (task != null)
            {
                _context.Tasks.Remove(task);
                _context.SaveChanges();
            }
 
            return Ok(new
            {
                action = "deleted"
            });
        }
    }
}

Verknüpfungs-Controller

Nun ist ein Controller zur Verwaltung von Verknüpfungen erforderlich:

Controllers/LinkController.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using DHX.Gantt.Models;
 
namespace DHX.Gantt.Controllers
{
    [Produces("application/json")]
    [Route("api/link")]
    public class LinkController : Controller
    {
        private readonly GanttContext _context;
        public LinkController(GanttContext context)
        {
            _context = context;
        }
 
        // GET api/Link
        [HttpGet]
        public IEnumerable<WebApiLink> Get()
        {
            return _context.Links
                .ToList()
                .Select(t => (WebApiLink)t);
        }
 
        // GET api/Link/5
        [HttpGet("{id}")]
        public Link? Get(int id)
        {
            return _context
                .Links
                .Find(id);
        }
 
        // POST api/Link
        [HttpPost]
        public ObjectResult Post(WebApiLink apiLink)
        {
            var newLink = (Link)apiLink;
 
            _context.Links.Add(newLink);
            _context.SaveChanges();
 
            return Ok(new
            {
                tid = newLink.Id,
                action = "inserted"
            });
        }
 
        // PUT api/Link/5
        [HttpPut("{id}")]
        public ObjectResult Put(int id, WebApiLink apiLink)
        {
            var updatedLink = (Link)apiLink;
            updatedLink.Id = id;
            _context.Entry(updatedLink).State = EntityState.Modified;
 
            _context.SaveChanges();
 
            return Ok(new
            {
                action = "updated"
            });
        }
 
        // DELETE api/Link/5
        [HttpDelete("{id}")]
        public ObjectResult DeleteLink(int id)
        {
            var Link = _context.Links.Find(id);
            if (Link != null)
            {
                _context.Links.Remove(Link);
                _context.SaveChanges();
            }
 
            return Ok(new
            {
                action = "deleted"
            });
        }
    }
}

Daten-Controller

Schließlich ist ein Controller zur Handhabung von Datenaktionen erforderlich:

Controllers/DataController.cs

using Microsoft.AspNetCore.Mvc;
using DHX.Gantt.Models;
 
namespace DHX.Gantt.Controllers
{
    [Produces("application/json")]
    [Route("api/data")]
    public class DataController : Controller
    {
        private readonly GanttContext _context;
        public DataController(GanttContext context)
        {
            _context = context;
        }
 
        // GET api/data
        [HttpGet]
        public object Get()
        {
            return new
            {
                data = _context.Tasks.ToList().Select(t => (WebApiTask)t),
                links = _context.Links.ToList().Select(l => (WebApiLink)l)
 
            };
        }
 
    }
}

Das ist alles eingerichtet. Sie können nun die Anwendung ausführen und ein voll funktionsfähiges Gantt-Diagramm sehen.

Gantt ASP.NET Core Gantt ist bereit

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

Fehlerbehandlung

Um Fehler zu handhaben, kann eine Middleware-Klasse erstellt werden, um Laufzeitausnahmen abzufangen und Antworten zu schreiben. Diese Klasse wird zur Anforderungspipeline der App hinzugefügt. So richten Sie sie ein:

1. Beginnen Sie mit dem Erstellen einer Middleware-Klasse mithilfe einer Vorlage im Projektordner.

Gantt ASP.NET Core Middleware-Klasse

2. Installieren Sie das JSON-Framework für ASP.NET Core. Dies können Sie über den NuGet-Paketmanager tun:

Gantt ASP.NET Core Install NewtonSoft Json

Oder verwenden Sie die Befehlszeile des Paketmanagers:

PM> Install-Package NewtonSoft.JSON

3. Umwickeln Sie im Invoke-Methode der Middleware den _next-Aufruf mit einem try-catch-Block, um Ausnahmen abzufangen und den Handler auszuführen, wenn ein Fehler auftritt.

GanttErrorMiddleware.cs

public async Task Invoke(HttpContext httpContext)
{
    try
    {
        await _next(httpContext);
    }catch(Exception e)
    {
        await HandleExceptionAsync(httpContext, e);
    }           
}
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);
}

4. Fügen Sie den erforderlichen Namensraum zu GanttErrorMiddleware.cs hinzu:

using Newtonsoft.Json;

5. Schließlich verbinden Sie die Middleware in Program.cs. Fügen Sie den erforderlichen Namensraum hinzu:

Program.cs

using DHX.Gantt;

Dann fügen Sie die Middleware in die Pipeline ein, indem Sie aufrufen:

Program.cs

app.UseGanttErrorMiddleware();

Speichern der Aufgabenreihenfolge

Wenn Benutzer Aufgaben per Drag-and-Drop umordnen, kann die neue Reihenfolge in der Datenbank gespeichert werden. Weitere Details sind in diesem Abschnitt verfügbar.

Umordnung auf der Client-Seite

Um die Umordnung auf der Client-Seite zu aktivieren, fügen Sie die folgenden Zeilen zu index.html hinzu:

wwwroot/index.html

gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
 
// Das Datumsformat angeben
gantt.config.date_format = "%Y-%m-%d %H:%i";
// Gantt initialisieren
gantt.init("gantt_here");

Hinzufügen der Aufgabenreihenfolge zum Modell

Um die Änderungen der Aufgabenreihenfolge im Backend widerzuspiegeln, aktualisieren Sie das Task-Modell, indem Sie eine neue Eigenschaft hinzufügen:

Models/Task.cs

namespace DHX.Gantt.Models
{
    public class Task
    {
        public int Id { get; set; }
        public string? Text { get; set; }
        public DateTime StartDate { get; set; }
        public int Duration { get; set; }
        public decimal Progress { get; set; }
        public int? ParentId { get; set; }
        public string? Type { get; set; }
        public int SortOrder { get; set; }     }
}

Aktualisieren der Controller

Es ist Zeit, einige Aktualisierungen an den Controllern vorzunehmen.

  1. Um sicherzustellen, dass Aufgaben in der richtigen Reihenfolge gesendet werden, sortiert nach dem SortOrder-Wert, fügen Sie die hervorgehobene Zeile zum DataController hinzu:

Controllers/DataController.cs

[HttpGet]
public object Get()
{
    return new
        {
            data = _context.Tasks
                .OrderBy(t => t.SortOrder)                 .ToList()
                .Select(t => (WebApiTask)t),
            links = _context.Links
                .ToList()
                .Select(l => (WebApiLink)l)
        };
}
  1. Wenn neue Aufgaben hinzugefügt werden, sollten sie automatisch einen Standard-SortOrder-Wert erhalten:

controllers/TaskController.cs

// POST api/task
[HttpPost]
public IActionResult Post(WebApiTask apiTask)
{
    var newTask = (Models.Task)apiTask;
 
    newTask.SortOrder = _context.Tasks.Max(t => t.SortOrder) + 1;     _context.Tasks.Add(newTask);
    _context.SaveChanges();
 
    return Ok(new
    {
        tid = newTask.Id,
        action = "inserted"
    });
}
  1. Der sortOrder-Wert sollte aktualisiert werden, wenn Aufgaben auf der Client-Seite umgeordnet werden. Wenn ein Benutzer die Reihenfolge der Aufgaben ändert, sendet Gantt eine PUT-Anfrage mit den neuen Aufgabenpositionen in der target-Eigenschaft zusammen mit anderen Aufgabendetails.

Um dies zu handhaben, fügen Sie die target-Eigenschaft zur WebApiTask-Klasse hinzu:

Models/WebApiTask.cs

public class WebApiTask
{
    public int id { get; set; }
    public string? text { get; set; }
    public string? start_date { get; set; }
    public int duration { get; set; }
    public decimal progress { get; set; }
    public int? parent { get; set; }
    public string? type { get; set; }
    public string? target { get; set; }     public bool open
    {
        get { return true; }
        set { }
    }
}

Ändern Sie als nächstes die Put-Aktion im TaskController, um die Umordnung zu handhaben:

Controllers/TaskController.cs

// PUT api/task/5
[HttpPut("{id}")]
public IActionResult? Put(int id, WebApiTask apiTask)
{
    var updatedTask = (Models.Task)apiTask;
    updatedTask.Id = id;
 
    var dbTask = _context.Tasks.Find(id);
    if (dbTask == null)
    {
        return null;
    }
    dbTask.Text = updatedTask.Text;
    dbTask.StartDate = updatedTask.StartDate;
    dbTask.Duration = updatedTask.Duration;
    dbTask.ParentId = updatedTask.ParentId;
    dbTask.Progress = updatedTask.Progress;
    dbTask.Type = updatedTask.Type;
 
    if (!string.IsNullOrEmpty(apiTask.target))                         {                                                            // Umordnung erfolgte                                   this._UpdateOrders(dbTask, apiTask.target);        }                                                    
    _context.SaveChanges();
 
    return Ok(new
    {
         action = "updated"
    });
}

Fügen Sie schließlich eine Methode hinzu, um die Aktualisierungen der Aufgabenreihenfolge zu handhaben:

Controllers/TaskController.cs

private void _UpdateOrders(Models.Task updatedTask, string orderTarget)
{
    int adjacentTaskId;
    var nextSibling = false;
 
    var targetId = orderTarget;
 
    // Die ID der benachbarten Aufgabe wird entweder als '{id}' oder als 'next:{id}' gesendet, je nachdem, ob es sich um das nächste oder das vorherige Geschwister handelt
    if (targetId.StartsWith("next:"))
    {
        targetId = targetId.Replace("next:", "");
        nextSibling = true;
    }
 
    if (!int.TryParse(targetId, out adjacentTaskId))
    {
        return;
    }
 
    var adjacentTask = _context.Tasks.Find(adjacentTaskId);
    var startOrder = adjacentTask!.SortOrder;
 
    if (nextSibling)
         startOrder++;
 
    updatedTask.SortOrder = startOrder;
 
    var updateOrders = _context.Tasks
        .Where(t => t.Id != updatedTask.Id)
        .Where(t => t.SortOrder >= startOrder)
        .OrderBy(t => t.SortOrder);
 
    var taskList = updateOrders.ToList();
 
    taskList.ForEach(t => t.SortOrder++);
}

Anwendungssicherheit

Gantt selbst enthält keinen Schutz gegen Sicherheitsrisiken wie SQL-Injection, XSS oder CSRF-Angriffe. Es liegt an den Entwicklern, ihre Anwendungen zu sichern. Weitere Details finden Sie im relevanten Artikel.

XSS-Schutz

Eine einfache Möglichkeit, sich gegen XSS zu schützen, besteht darin, die Texteigenschaften Ihrer Daten zu codieren, bevor Sie sie an den Client senden. Hier ist ein Beispiel, das den eingebauten HtmlEncoder verwendet, um HTML im Aufgabentext zu escapen. Dies stellt sicher, dass die Datenbank unveränderte Daten enthält, der Client jedoch bereinigte Werte erhält.

Models/WebApiTask.cs

using System.Text.Encodings.Web;
 
public static explicit operator WebApiTask(Task task)
{
    return new WebApiTask
    {
        id = task.Id,
        text = HtmlEncoder.Default.Encode(task.Text != null ? task.Text : ""),         start_date = task.StartDate.ToString("yyyy-MM-dd HH:mm"),
        duration = task.Duration,
        parent = task.ParentId,
        type = task.Type,
        progress = task.Progress
    };
}

Alternativ könnten Sie eine Bibliothek wie HtmlAgilityPack verwenden, um HTML-Tags beim Speichern oder Laden von Daten vollständig zu entfernen.

Fehlerbehebung

Wenn Sie alle Schritte befolgt haben, aber Gantt immer noch keine Aufgaben und Verknüpfungen anzeigt, lesen Sie den Leitfaden zur Fehlerbehebung in diesem Artikel. Er bietet Tipps zur Diagnose und Lösung von Problemen.

Was kommt als nächstes

Mit diesen Schritten haben Sie jetzt ein voll funktionsfähiges Gantt. Der vollständige Code ist auf GitHub verfügbar, damit Sie ihn klonen oder herunterladen und in Ihren Projekten verwenden können.

Für weitere Funktionen erkunden Sie die Gantt-Leitfäden oder sehen Sie sich Tutorials zur Integration von Gantt mit anderen Backend-Frameworks an.

Zurück nach oben