В этом руководстве описан процесс настройки диаграммы Gantt с использованием ASP.NET Core на стороне сервера.
Также доступны руководства для других серверных платформ:
Для работы с базой данных используется Entity Framework Core. Проект разрабатывается с помощью Visual Studio 2022.
Полный исходный код доступен на GitHub.
Запустите Visual Studio 2022 и создайте новый проект, выбрав Create a new project.

Затем выберите "ASP.NET Core Web App" и задайте имя проекта DHX.Gantt.



После создания проекта можно приступить к добавлению разметки и скриптов, необходимых для Gantt.
Перейдите в папку wwwroot и создайте новый файл с именем index.html.


В этом файле создайте простую страницу для отображения диаграммы Gantt.
Обратите внимание, что в этом примере файлы Gantt загружаются с CDN. Если у вас есть Professional-версия, потребуется добавить файлы Gantt в проект вручную.
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) {
// specifying the date format
gantt.config.date_format = "%Y-%m-%d %H:%i";
// initializing gantt
gantt.init("gantt_here");
// initiating data loading
gantt.load("/api/data");
// initializing dataProcessor
var dp = new gantt.dataProcessor("/api/");
// and attaching it to gantt
dp.init(gantt);
// setting the REST mode for dataProcessor
dp.setTransactionMode("REST");
});
</script>
</head>
<body>
<div id="gantt_here" style="width: 100%; height: 100vh;"></div>
</body>
</html>
При загрузке страницы диаграмма Gantt инициализируется, и сразу начинается загрузка данных через gantt.load(). Также настраивается dataProcessor, чтобы все изменения пользователя на диаграмме сохранялись на сервере. Поскольку backend еще не настроен, полная функциональность станет доступна после его реализации.
Далее откройте Program.cs и настройте приложение для обслуживания страницы index.html. Для этого включите раздачу статических файлов из папки wwwroot, добавив app.UseDefaultFiles().
Подробнее об этом можно прочитать здесь.
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();
Метод app.UseDefaultFiles() включает раздачу файлов по умолчанию, и ищет их в папке wwwroot:
Можно использовать любое из этих имён файлов; в этом руководстве используется "index.html".
Обратите внимание, что UseDefaultFiles() только переписывает URL, но не раздает файлы самостоятельно. Для реальной раздачи статических файлов также необходимо добавить UseStaticFiles().
После выполнения этих шагов, при запуске приложения отобразится пустая диаграмма Gantt. Сообщение "Invalid data" в правом верхнем углу появляется потому, что вызывается gantt.load(), но backend еще не реализован для предоставления данных. После создания контроллера диаграмма Gantt будет корректно отображать задачи и связи.

Когда базовая настройка завершена, следующим шагом будет создание backend. Начните с определения классов моделей, затем переходите к созданию WebAPI-контроллера.
Начните с определения моделей данных. Типичная модель данных для Gantt включает в себя ссылки и задачи. dhtmlxGantt использует имена свойств, отличающиеся от соглашений .NET. Кроме того, некоторые свойства используются только на клиенте или для логики backend и не должны храниться в базе данных.
Для решения этой задачи будет применён паттерн Data Transfer Object (DTO). Будут созданы два типа моделей:
Также будет реализовано сопоставление между этими моделями.
Добавьте новую папку Models в директорию проекта. В этой папке будут размещаться классы моделей и контекст EF.
Создайте класс для представления задач. Добавьте новый файл с именем Task.cs в папку Models, кликнув по папке правой кнопкой мыши и выбрав Add->Class.
Класс должен выглядеть следующим образом:
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; }
}
}
Полный список свойств объекта Task можно посмотреть здесь.
Добавьте еще один файл для связей:
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; }
}
}
После создания моделей следующим шагом будет настройка подключения к базе данных.
Выполните следующие шаги для настройки подключения к базе данных:
Entity Framework Core будет использоваться для работы с базой данных. Для установки:

Либо используйте Package Manager Console:
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore
PM> Install-Package Microsoft.EntityFrameworkCore.Design
Эти пакеты предоставят необходимые инструменты для взаимодействия с базой данных.
Далее определите сессию с базой данных для загрузки и сохранения данных, создав класс контекста:
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;
}
}
Пора заполнить базу данных начальными записями. Для этого создайте инициализатор базы данных, который добавит задачи и связи. Внутри папки Models определите класс с именем GanttSeeder. Этот класс будет содержать метод Seed(), отвечающий за добавление задач и связей в базу данных.
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; // DB has been seeded
}
using (var transaction = context.Database.BeginTransaction())
{
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.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();
}
}
}
}
Далее необходимо зарегистрировать базу данных в Program.cs. Для этого сначала потребуется строка подключения.
Эта строка подключения будет храниться в JSON-файле в настройках приложения.
Создайте файл appsettings.json (или откройте его, если он уже существует) и добавьте строку подключения к базе данных:
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;
Database=GanttDatabase;Trusted_Connection=True;"
}
}
Контекст базы данных будет зарегистрирован с использованием
внедрения зависимостей.
Добавьте следующие пространства имён в Program.cs:
Program.cs
using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;
Регистрация будет выглядеть следующим образом:
Program.cs
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
options => options.UseSqlServer(connectionString));
Для включения контроллеров добавляется метод services.AddControllers():
Program.cs
builder.Services.AddControllers();
И вызывается app.MapControllers() для регистрации маршрутов контроллеров:
Program.cs
app.MapControllers();
Ниже приведено полное содержимое файла Program.cs:
Program.cs
using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
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();
В завершение, база данных должна быть инициализирована и наполнена начальными данными при запуске приложения. Хотя обычно для этого используются миграции, в данном примере для простоты они не применяются.
Для начала создайте класс, в котором будет происходить инициализация. Добавьте файл GanttInitializerExtension.cs в папку Models:
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;
}
}
}
Затем вызовите InitializeDatabase() следующим образом:
Program.cs
app.InitializeDatabase();
Как уже упоминалось, в этом учебнике не используются миграции, вместо них применяется EnsureCreated и наполнение начальными данными.
На этом данный этап завершён. Далее возвращаемся к работе с диаграммой Gantt.
Теперь необходимо создать классы DTO, которые будут использоваться Web API.
Начнём с DTO-класса для Task. В папке Models создайте файл и определите класс 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
};
}
}
}
Далее приведён класс DTO для Link, определённый в WebApiLink.cs в папке Models:
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
};
}
}
}
После выполнения этого шага структура папок должна выглядеть следующим образом:

На этом этапе рекомендуется запустить приложение и убедиться, что всё настроено корректно. Если ошибок выполнения не появляется, значит настройка прошла успешно.
Теперь пришло время реализовать REST API.
Создайте папку Controllers и добавьте три пустых API-контроллера: для задач (Tasks), связей (Links) и всего набора данных:

Ниже приведён контроллер для управления задачами, реализующий базовые CRUD-операции для задач Gantt.
Как это работает:
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"
});
}
}
}
Далее приведён контроллер для связей:
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"
});
}
}
}
Наконец, вот контроллер для работы с данными:
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)
};
}
}
}
Теперь всё готово к запуску приложения, и вы увидите полностью работоспособный Gantt-диаграмму.

Полный исходный код также доступен на GitHub.
Для эффективного управления ошибками рекомендуется создать специальный middleware-класс, который будет перехватывать исключения во время выполнения и отправлять соответствующие ответы. Этот middleware затем добавляется в конвейер обработки запросов приложения. Вот как это сделать:
1. Добавьте middleware-класс в проект, используя шаблон.

2. Установите JSON-фреймворк для ASP.NET Core. Это можно сделать через менеджер пакетов NuGet:

Или через консоль диспетчера пакетов:
PM> Install-Package NewtonSoft.JSON
3. Найдите метод Invoke в middleware. Так как некоторые обработчики могут выбрасывать исключения, оберните вызов _next в блок try-catch и обработайте ошибки по мере их возникновения.
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. Добавьте следующий namespace в GanttErrorMiddleware.cs:
using Newtonsoft.Json;
5. Теперь middleware готов. Далее откройте Program.cs и зарегистрируйте middleware, добавив:
Program.cs
using DHX.Gantt;
Затем включите middleware в конвейер с помощью следующей строки:
Program.cs
app.UseGanttErrorMiddleware();
Когда пользователь меняет порядок задач с помощью drag and drop на клиенте, новый порядок должен сохраняться в базе данных. Подробности смотрите в этой секции.
Вот как включить хранение порядка задач в вашем Gantt.
Сначала включите изменение порядка задач на клиенте, добавив следующие строки в index.html:
wwwroot/index.html
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
// specifying the date format
gantt.config.date_format = "%Y-%m-%d %H:%i";
// initializing gantt
gantt.init("gantt_here");
Затем обновите backend, чтобы отражать текущий порядок задач. Добавьте новое свойство в модель Task:
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; } }
}
Контроллеры также требуют некоторых изменений.
1. Клиент должен получать задачи, отсортированные по SortOrder. Добавьте отмеченную строку в DataController:
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)
};
}
2. При создании новых задач убедитесь, что им присваивается значение SortOrder по умолчанию:
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"
});
}
3. sortOrder необходимо обновлять при изменении порядка задач на клиенте. Когда задачи переставляются, gantt отправляет PUT-запрос с новой позицией в свойстве 'target', вместе с остальными данными задачи.
Добавьте свойство target в класс WebApiTask.cs:
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 { }
}
}
Теперь реализуйте логику изменения порядка в действии PUT (EditTask). Обновите метод Put в контроллере задач:
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)) { // reordering happened this._UpdateOrders(dbTask, apiTask.target); }
_context.SaveChanges();
return Ok(new
{
action = "updated"
});
}
Добавьте вспомогательный метод для корректировки порядка задач:
Controllers/TaskController.cs
private void _UpdateOrders(Models.Task updatedTask, string orderTarget)
{
int adjacentTaskId;
var nextSibling = false;
var targetId = orderTarget;
// adjacent task id may come as '{id}' or 'next:{id}' indicating
// whether it's the next or previous sibling
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++);
}
Gantt сам по себе не обеспечивает защиту от угроз, таких как SQL-инъекции, XSS или CSRF. Ответственность за безопасность приложения лежит на разработчиках backend. Подробнее см. соответствующую статью.
Один из простых способов — кодировать текстовые поля перед отправкой их на клиент.
Например, в примере ниже используется встроенный HtmlEncoder для экранирования HTML в текстах задач. Таким образом, в базе данных остаются исходные данные, а клиент получает безопасные значения для task.text.
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
};
}
В качестве альтернативы можно использовать специализированную библиотеку, например, HtmlAgilityPack, чтобы полностью удалять HTML при сохранении или загрузке задач.
Если вы выполнили все шаги по интеграции Gantt с ASP.NET Core, но задачи и связи не отображаются на странице, обратитесь к статье Устранение проблем интеграции с backend. В ней описаны распространённые проблемы и способы их устранения.
На этом этапе у вас есть рабочая реализация gantt. Полный исходный код доступен на GitHub — вы можете клонировать или скачать его для использования в своих проектах.
Также вы можете ознакомиться с руководствами по многим возможностям gantt или туториалами по интеграции Gantt с другими backend-фреймворками.
К началу