Diese Anleitung erklärt, wie man eine Gantt-Diagramm-Anwendung mit ASP.NET und REST API auf der Serverseite erstellt. Die Schritte beinhalten das Einrichten der ASP.NET MVC 5-Webplattform und die Verwendung des Web API 2-Controllers für die REST API. Entity Framework wird die Datenbankkommunikation übernehmen, und Visual Studio wird zum Erstellen der Anwendung verwendet.
Wenn Sie an anderen serverseitigen Integrationen interessiert sind, sehen Sie sich diese Tutorials an:
Der vollständige Quellcode für dieses Tutorial ist auf GitHub verfügbar.
Starten Sie Visual Studio 2022 und wählen Sie die Option, ein neues Projekt zu erstellen.
Wählen Sie "ASP.NET Web Application" als Vorlage und benennen Sie das Projekt DHX.Gantt.Web. Wenn diese Vorlage nicht sichtbar ist, lesen Sie den Abschnitt Fehlerbehebung.
Wählen Sie die Leere Vorlage und aktivieren Sie die Optionen MVC und Web API.
Nachdem das leere Projekt bereit ist, besteht der nächste Schritt darin, das Gantt-Diagramm zu implementieren. Beginnen Sie damit, einen MVC-Controller hinzuzufügen, um die Gantt-Diagrammseite anzuzeigen.
Um den Controller hinzuzufügen, klicken Sie mit der rechten Maustaste auf den Ordner Controllers
und wählen Sie Hinzufügen -> Controller. Wählen Sie "MVC 5 Controller - Leere" und benennen Sie ihn "HomeController".
Der HomeController
enthält bereits die Methode Index()
der ActionResult
-Klasse, sodass keine zusätzliche Logik erforderlich ist. Der nächste Schritt besteht darin, eine Ansicht für diese Methode hinzuzufügen.
Controllers/HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace DHX.Gantt.Web.Controllers
{
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
}
}
Erstellen Sie nun die Index-Seite. Navigieren Sie zu Views/Home
und fügen Sie eine leere Ansicht namens Index
hinzu.
Öffnen Sie die neu erstellte Ansicht und fügen Sie den folgenden Code hinzu:
Views/Home/Index.cshtml
@{
Layout = null;
}
<!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) {
// Datumsformat festlegen
gantt.config.date_format = "%Y-%m-%d %H:%i";
// Gantt initialisieren
gantt.init("gantt_here");
// Datenladen initiieren
gantt.load("/api/data");
// dataProcessor initialisieren
var dp = new gantt.dataProcessor("/api/");
// und an gantt anhängen
dp.init(gantt);
// Den REST-Modus für dataProcessor einstellen
dp.setTransactionMode("REST");
});
</script>
</head>
<body>
<div id="gantt_here" style="width: 100%; height: 100vh;"></div>
</body>
</html>
Dieser Code macht Folgendes:
Die Datumsformatkonfiguration stellt sicher, dass der Client die Daten vom Server korrekt parsen kann:
Views/Home/Index.cshtml
gantt.config.date_format = "%Y-%m-%d %H:%i";
Das Gantt-Diagramm ist auch so konfiguriert, dass es mit einer RESTful API unter Verwendung von "/api/" als Standardroute funktioniert:
Views/Home/Index.cshtml
gantt.load("/api/data");
// dataProcessor initialisieren
var dp = new gantt.dataProcessor("/api/");
// und an gantt anhängen
dp.init(gantt);
// Den REST-Modus für dataProcessor einstellen
dp.setTransactionMode("REST");
An diesem Punkt können Sie die Anwendung ausführen, um das Gantt-Diagramm auf der Seite anzuzeigen.
Das Gantt-Diagramm benötigt ein Datenmodell, das aus Tasks und Links besteht. Das clientseitige Modell folgt einer spezifischen Namenskonvention, die von den Standard-C#-Konventionen abweicht. Einige Eigenschaften im clientseitigen Modell müssen möglicherweise nicht in der Datenbank gespeichert werden, sind jedoch für die Client- oder Backend-Logik nützlich.
Um dies zu handhaben, wird das Data Transfer Object-Muster verwendet. Separate Domänenmodellklassen werden für die Verwendung mit EF und innerhalb der App erstellt, zusammen mit DTO-Klassen für die Kommunikation der Web-API. Die Zuordnung zwischen diesen Modellen wird ebenfalls implementiert.
Beginnen Sie mit dem Erstellen einer Klasse für Task
mit der folgenden Struktur:
Models/Task.cs
using System;
namespace DHX.Gantt.Web.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; }
}
}
Für eine vollständige Liste der verfügbaren Eigenschaften für das Task-Objekt siehe die Dokumentation.
Erstellen Sie als nächstes die Link
-Klasse mit der folgenden Struktur:
Models/Link.cs
namespace DHX.Gantt.Web.Models
{
public class Link
{
public int Id { get; set; }
public string Type { get; set; }
public int SourceTaskId { get; set; }
public int TargetTaskId { get; set; }
}
}
Um die Datenbank zu verwalten, installieren Sie das Entity Framework, indem Sie diesen Befehl in der Package Manager Console ausführen:
Install-Package EntityFramework
Erstellen Sie eine GanttContext
-Klasse, um eine Sitzung mit der Datenbank darzustellen. Dieser Kontext ermöglicht das Abrufen und Speichern von Daten.
Fügen Sie dem Ordner Models
eine neue Klasse namens "GanttContext" mit folgendem Inhalt hinzu:
Models/GanttContext.cs
using System.Data.Entity;
namespace DHX.Gantt.Web.Models
{
public class GanttContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
public DbSet<Link> Links { get; set; }
}
}
Um die Datenbank mit Anfangsdaten zu füllen, konfigurieren Sie das Entity Framework so, dass es die Datenbank automatisch erstellt und füllt, wenn die Anwendung läuft.
Erstellen Sie einen Datenbankinitialisierer, indem Sie eine neue Klasse im Ordner App_Start
hinzufügen. Nennen Sie sie "GanttInitializer" und erben Sie sie von der DropCreateDatabaseIfModelChanges
-Klasse. Überschreiben Sie die Seed()
-Methode, um die Datenbank mit Testdaten zu füllen.
Hier ist der vollständige Code für die GanttInitializer
-Klasse:
App_Start/GanttInitializer.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace DHX.Gantt.Web.Models
{
public class GanttInitializer : DropCreateDatabaseIfModelChanges<GanttContext>
{
protected override void Seed(GanttContext context)
{
List<Task> tasks = new List<Task>()
{
new Task()
{
Id = 1,
Text = "Project #2",
StartDate = DateTime.Today.AddDays(-3),
Duration = 18,
Progress = 0.4m,
ParentId = null
},
new Task()
{
Id = 2,
Text = "Task #1",
StartDate = DateTime.Today.AddDays(-2),
Duration = 8,
Progress = 0.6m,
ParentId = 1
},
new Task()
{
Id = 3,
Text = "Task #2",
StartDate = DateTime.Today.AddDays(-1),
Duration = 8,
Progress = 0.6m,
ParentId = 1
}
};
tasks.ForEach(s => context.Tasks.Add(s));
context.SaveChanges();
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.SaveChanges();
}
}
}
Öffnen Sie schließlich die Datei Global.asax
und fügen Sie den erforderlichen Namespace und die Initialisierer-Einrichtung in der Application_Start()
-Methode hinzu:
Global.asax.cs
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Http;
using System.Data.Entity;
using DHX.Gantt.Web.Models;
namespace DHX.Gantt.Web
{
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
// Code, der beim Start der Anwendung ausgeführt wird
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
Database.SetInitializer(new GanttInitializer());
}
}
}
Um loszulegen, müssen Sie DTO-Klassen für die Web-API einrichten. Für das Mapping zwischen dem Modell und dem DTO halten wir es einfach, indem wir einen expliziten Konvertierungsoperator für diese Klassen definieren.
Hier ist, wie die TaskDto
-Klasse definiert ist:
Models/TaskDto.cs
using System;
namespace DHX.Gantt.Web.Models
{
public class TaskDto
{
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 TaskDto(Task task)
{
return new TaskDto
{
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(TaskDto task)
{
return new Task
{
Id = task.id,
Text = task.text,
StartDate = DateTime.Parse(
task.start_date,
System.Globalization.CultureInfo.InvariantCulture),
Duration = task.duration,
ParentId = task.parent,
Type = task.type,
Progress = task.progress
};
}
}
}
Als Nächstes folgt die Struktur der LinkDto
-Klasse:
Models/LinkDto.cs
namespace DHX.Gantt.Web.Models
{
public class LinkDto
{
public int id { get; set; }
public string type { get; set; }
public int source { get; set; }
public int target { get; set; }
public static explicit operator LinkDto(Link link)
{
return new LinkDto
{
id = link.Id,
type = link.Type,
source = link.SourceTaskId,
target = link.TargetTaskId
};
}
public static explicit operator Link(LinkDto link)
{
return new Link
{
Id = link.id,
Type = link.type,
SourceTaskId = link.source,
TargetTaskId = link.target
};
}
}
}
Zuletzt folgt das Modell für die Datenquelle:
Models/GanttDto.cs
using System.Collections.Generic;
namespace DHX.Gantt.Web.Models
{
public class GanttDto
{
public IEnumerable<TaskDto> data { get; set; }
public IEnumerable<LinkDto> links { get; set; }
}
}
Der nächste Schritt beinhaltet die Implementierung der API. Basierend auf den API-Details benötigen Sie drei Controller: einen für Aufgaben, einen für Links und einen weiteren für die 'Daten laden'-Aktion, um das gemischte Ergebnis zu verarbeiten.
Um einen neuen Controller zu erstellen:
Hier ist die Implementierung für grundlegende CRUD-Aktionen für Aufgabeneinträge:
Controllers/TaskController.cs
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web.Http;
using System.Web.UI.WebControls;
using DHX.Gantt.Web.Models;
namespace DHX.Gantt.Web.Controllers
{
public class TaskController : ApiController
{
private GanttContext db = new GanttContext();
// GET api/Task
public IEnumerable<TaskDto> Get()
{
return db.Tasks
.ToList()
.Select(t => (TaskDto)t);
}
// GET api/Task/5
[System.Web.Http.HttpGet]
public TaskDto Get(int id)
{
return (TaskDto)db
.Tasks
.Find(id);
}
// PUT api/Task/5
[System.Web.Http.HttpPut]
public IHttpActionResult EditTask(int id, TaskDto taskDto)
{
var updatedTask = (Task)taskDto;
updatedTask.Id = id;
db.Entry(updatedTask).State = EntityState.Modified;
db.SaveChanges();
return Ok(new
{
action = "updated"
});
}
// POST api/Task
[System.Web.Http.HttpPost]
public IHttpActionResult CreateTask(TaskDto taskDto)
{
var newTask = (Task)taskDto;
db.Tasks.Add(newTask);
db.SaveChanges();
return Ok(new
{
tid = newTask.Id,
action = "inserted"
});
}
// DELETE api/Task/5
[System.Web.Http.HttpDelete]
public IHttpActionResult DeleteTask(int id)
{
var task = db.Tasks.Find(id);
if (task != null)
{
db.Tasks.Remove(task);
db.SaveChanges();
}
return Ok(new
{
action = "deleted"
});
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
Die Logik hier ist ziemlich einfach:
Als Nächstes richten Sie den Controller für Links ein.
Erstellen Sie einen neuen leeren Web API-Controller für Links. So sollte er aussehen:
Controllers/LinkController.cs
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web.Http;
using DHX.Gantt.Web.Models;
namespace DHX.Gantt.Web.Controllers
{
public class LinkController : ApiController
{
private GanttContext db = new GanttContext();
// GET api/Link
[System.Web.Http.HttpGet]
public IEnumerable<LinkDto> Get()
{
return db
.Links
.ToList()
.Select(l => (LinkDto)l);
}
// GET api/Link/5
[System.Web.Http.HttpGet]
public LinkDto Get(int id)
{
return (LinkDto)db
.Links
.Find(id);
}
// POST api/Link
[System.Web.Http.HttpPost]
public IHttpActionResult CreateLink(LinkDto linkDto)
{
var newLink = (Link)linkDto;
db.Links.Add(newLink);
db.SaveChanges();
return Ok(new
{
tid = newLink.Id,
action = "inserted"
});
}
// PUT api/Link/5
[System.Web.Http.HttpPut]
public IHttpActionResult EditLink(int id, LinkDto linkDto)
{
var clientLink = (Link)linkDto;
clientLink.Id = id;
db.Entry(clientLink).State = EntityState.Modified;
db.SaveChanges();
return Ok(new
{
action = "updated"
});
}
// DELETE api/Link/5
[System.Web.Http.HttpDelete]
public IHttpActionResult DeleteLink(int id)
{
var link = db.Links.Find(id);
if (link != null)
{
db.Links.Remove(link);
db.SaveChanges();
}
return Ok(new
{
action = "deleted"
});
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
Fügen Sie schließlich einen Controller hinzu, um die Datenaktion zu bearbeiten:
Controllers/DataController.cs
using System.Web.Http;
using DHX.Gantt.Web.Models;
namespace DHX.Gantt.Web.Controllers
{
public class DataController : ApiController
{
// GET api/
[System.Web.Http.HttpGet]
public GanttDto Get()
{
return new GanttDto
{
data = new TaskController().Get(),
links = new LinkController().Get()
};
}
}
}
Sobald alles eingerichtet ist, sollte das Ausführen der Anwendung ein voll funktionsfähiges Gantt-Diagramm anzeigen:
Sehen Sie sich die Demo auf GitHub an.
Um Ausnahmen in CRUD-Aktionen zu behandeln und clientlesbare Antworten zurückzugeben, können Sie Exception Filters verwenden.
So können Sie es einrichten:
GanttAPIExceptionFilterAttribute
:App_Start/GanttAPIExceptionFilterAttribute.cs
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;
namespace DHX.Gantt.Web
{
public class GanttAPIExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = context.Request.CreateResponse(
HttpStatusCode.InternalServerError, new
{
action = "error",
message = context.Exception.Message
}
);
}
}
}
Controllers/DataController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter] public class DataController : ApiController
Controllers/LinkController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter] public class LinkController : ApiController
Controllers/TaskController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter] public class TaskController : ApiController
Dies stellt sicher, dass alle von den Web API-Controllern ausgelösten Ausnahmen zu einer Fehlerantwort führen, die das clientseitige Gantt verarbeiten kann.
Das Gantt-Diagramm unterstützt Aufgaben-Neuanordnungen per Drag & Drop. Wenn Sie diese Funktion verwenden, müssen Sie die Reihenfolge in der Datenbank speichern. Hier ist ein allgemeiner Leitfaden, wie Sie dies handhaben können.
Um Benutzern die Neuanordnung von Aufgaben in der Benutzeroberfläche zu ermöglichen, sind einige Aktualisierungen erforderlich.
Öffnen Sie die Index-Ansicht und passen Sie die Gantt-Konfiguration an:
Views/Home/Index.cshtml
gantt.config.order_branch = true;gantt.config.order_branch_free = true;
// Datumsformat festlegen
gantt.config.date_format = "%Y-%m-%d %H:%i";
// Gantt initialisieren
gantt.init("gantt_here");
Diese Änderungen müssen auch im Backend widergespiegelt werden.
Um die Reihenfolge zu speichern, wird dem Task-Klasse eine neue Eigenschaft namens SortOrder
hinzugefügt:
Models/Task.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace DHX.Gantt.Web.Models
{
public class Task
{
public int Id { get; set; }
[MaxLength(255)]
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; } }
}
Der TaskController
benötigt ebenfalls einige Updates:
SortOrder
-Wert an den Client gesendet werden:Controllers/TaskController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter]
public class TaskController : ApiController
{
private GanttContext db = new GanttContext();
// GET api/Task
public IEnumerable<TaskDto> Get()
{
return db.Tasks
.OrderBy(t => t.SortOrder) .ToList()
.Select(t => (TaskDto)t);
}
SortOrder
erhalten:Controllers/TaskController.cs
namespace DHX.Gantt.Web.Controllers
{
[System.Web.Http.HttpPost]
public IHttpActionResult CreateTask(TaskDto taskDto)
{
var newTask = (Task)taskDto;
newTask.SortOrder = db.Tasks.Max(t => t.SortOrder) + 1;
db.Tasks.Add(newTask);
db.SaveChanges();
return Ok(new
{
tid = newTask.Id,
action = "inserted"
});
}
SortOrder
sollte aktualisiert werden, wenn die Aufgabenreihenfolge auf dem Client geändert wird.Wenn Aufgaben neu angeordnet werden, sendet Gantt eine PUT-Anfrage mit der neuen Position in der ['target'](Serverseitige Integration)
-Eigenschaft zusammen mit anderen Aufgabendetails. Um dies zu handhaben, wird der TaskDto
-Klasse eine zusätzliche Eigenschaft hinzugefügt:
Models/TaskDto.cs
namespace DHX.Gantt.Web.Models
{
public class TaskDto
{
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 string target { get; set; }
...
}
}
Schließlich wird die Neuanordnungslogik in der EditTask
-Aktion implementiert:
Controllers/TaskController.cs
// PUT api/Task/5
[System.Web.Http.HttpPut]
public IHttpActionResult EditTask(int id, TaskDto taskDto)
{
var updatedTask = (Task)taskDto;
updatedTask.Id = id;
if (!string.IsNullOrEmpty(taskDto.target))
{
// Neuanordnung erfolgte
this._UpdateOrders(updatedTask, taskDto.target); }
db.Entry(updatedTask).State = EntityState.Modified;
db.SaveChanges();
return Ok(new
{
action = "updated"
});
}
private void _UpdateOrders(Task updatedTask, string orderTarget) {
int adjacentTaskId;
var nextSibling = false;
var targetId = orderTarget;
// Die ID der angrenzenden 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 = db.Tasks.Find(adjacentTaskId);
var startOrder = adjacentTask.SortOrder;
if (nextSibling)
startOrder++;
updatedTask.SortOrder = startOrder;
var updateOrders = db.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++);
}
HTTP PUT- und DELETE-Anfragen können 405- oder 401-Fehler zurückgeben, wenn die App auf IIS ausgeführt wird. Dies kann aufgrund eines Konflikts mit dem WebDAV-Modul auftreten. Um dies zu beheben, deaktivieren Sie das Modul in der web.config-Datei. Weitere Details finden Sie hier.
Gantt enthält keinen integrierten Schutz gegen Bedrohungen wie SQL-Injection, XSS oder CSRF-Angriffe. Die Sicherstellung der Anwendungssicherheit liegt in der Verantwortung der Entwickler, die am Backend arbeiten. Weitere Informationen finden Sie in diesem Artikel.
Wenn die Projektvorlage "ASP.NET Web Application" in Visual Studio 2022 nicht verfügbar ist, führen Sie die folgenden Schritte aus:
Suchen Sie Visual Studio Community 2022 und klicken Sie auf Ändern.
Gehen Sie im Installer zu Individuelle Komponenten, aktivieren Sie die Option ".NET Framework Project and item templates" und klicken Sie auf Ändern.
Danach sollten Sie Visual Studio 2022 erneut öffnen und die Vorlage sollte verfügbar sein.
Wenn es ein Problem mit dem DropCreateDatabaseIfModelChanges
-Initializer gibt, der keine neue Datenbank erstellt, aktualisieren Sie GanttInitializer.cs, um stattdessen DropCreateDatabaseAlways
zu verwenden:
App_Start/GanttInitializer.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace DHX.Gantt.Web.Models
{
public class GanttInitializer : DropCreateDatabaseAlways<GanttContext> {
...
}
}
Starten Sie dann die Anwendung neu.
Wenn Aufgaben und Links nach Abschluss der Schritte nicht auf der Seite angezeigt werden, lesen Sie den Fehlerbehebungsleitfaden im Artikel Fehlerbehebung bei Backend-Integrationsproblemen für Tipps zur Problemerkennung.
Ihr Gantt ist jetzt voll funktionsfähig. Der vollständige Code ist auf GitHub verfügbar, wo Sie ihn für Ihre Projekte klonen oder herunterladen können.
Für weitere Funktionen und Anleitungen schauen Sie sich die Feature-Dokumentation oder Tutorials zur Integration von Gantt mit anderen Backend-Frameworks an.
Zurück nach oben